目录

1、背景介绍

2、本篇博客的目的

3、模块的文件结构

4、POM文件的内容

5、application文件内容

6、service层接口

7、controller层类

8、为了打印日志而建的一个配置类

9、主启动类

10、启动运行


1、背景介绍

目前,我本人正在学习微服务的有关的知识,已经学习了Eureka,ZooKeeper,Consul服务注册中心,并且进行了简单的实践。我在写微服务模块之间的互相调用代码的时候,用的还都是RestTemplate类的方式。使用这种方式的时候,需要有一个配置类,也就是使用@Configuration和@Bean注解。当搭建集群的时候呢,还要在配置类中赋予RestTemplate负载均衡。这样就可以实现负载均衡的功能啦,默认采用的是轮询的方式。

2、本篇博客的目的

我在我的上一篇博客当中简单的介绍了OpenFeign。首先我想先说一下为什么会出现OpenFeign?原先开发人员使用的都是Ribbon+RestTemplate的方式进行RPC远程调用的。但是这种方式需要我们自己手写URL字符串,并且随着后期系统的复杂,这种方式显得越来越麻烦了,虽然这种方式已经模板熟练化了。所以,为了解决这些问题,出现了Feign。Feign就是把我们上面用到的模板熟练化的操作进一步的做封装,使得我们通过接口+注解的方式就能够简单的实现RPC。再后来,Feign停更了,SpringCloud官方就推出了Feign的加强优化版,OpenFeign。

微服务模块超时配置 微服务模块调用_微服务模块超时配置

我的这一篇博客会通过一个简单的模块代码演示OpenFeign的简单应用,帮助理解

3、模块的文件结构

这个模块是在父项目之下新建的一个子模块,模块的结构如下所示:

微服务模块超时配置 微服务模块调用_java_02

 模块的结构还是比较的简单的,首先是必须的POM文件和application配置文件,然后是主启动类,还有是在config文件夹中的配置类,后面就是controller层和service层。

4、POM文件的内容

新建了一个子模块以后,肯定是需要编辑一下POM文件的,将必要的依赖导入进来。下面是这个模块的POM文件的内容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2023</artifactId>
        <groupId>com.lanse.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-order80</artifactId>
    <!--openfeign-->
    <dependencies>
        <!--下面这个依赖就是引入了openfeign的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--我们还是使用的Eureka作为服务注册中心,因此需要使用client的这个依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.lanse.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>


</project>

首先<parent></parent>标签是自动生成的,包括<modelVersion></modelVersion>,<artifactId></artifactId>都是自动生成的。

下面就是<dependencies></dependencies>标签,里面就是具体的依赖:

首先第一个依赖:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

这个依赖是使用OpenFeign所必须的依赖

然后是第二个依赖:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

我们使用Eureka作为服务注册中心,这个是一个微服务模块,自然要作为Client注册进Eureka服务注册中心。所以就需要有这个依赖。

还有是第三个依赖:

<dependency>
            <groupId>com.lanse.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

这个依赖是我将我所有模块会使用到的实体类剥离了出来,单独组成了一个模块以后,使用Maven的package和install命令打成了jar包以后,依赖了进来。

后面的依赖就是一些比较常见的必须的依赖了,像是Web的,actuator的,还有测试的,热部署的,LomBok插件的。

5、application文件内容

server:
  port: 90
eureka:
  client:
    register-with-eureka: true
    service-url:
      defaultZone:  http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
ribbon:
  ReadTimeout: 5000   #这里的单位是毫秒,不知道为什么这里没有提示
  ConnectTimeout: 5000  #这里指的是建立连接所用的时间
                #还有一个是指建立连接以后,从服务器读取到可用资源所用的时间
logging:
  level:
    com.springcloud.service.PaymentFeignService: debug
    # 上面这个是设置  Feign日志以什么级别监控那个接口

上面的就是这个模块的具体application文件的内容

先是设置了使用的端口号,设置 server.port 属性为 90

由于我们这个是比较简单的,所以就没有设置  spring.application.name属性,没有设置微服务的名字

然后下面是eureka.client.register-with-eureka属性的设置,这个属性描述的是是否把自己当做微服务注册进服务注册中心,当然设置为true

再往下面是 eureka.client.service-url.defaultZone属性,这个属性描述的就是要注册进的Eureka服务注册中心的地址, 这里我采用了集群的方式,当然就可以把集群中的每一个EurekaServer的地址写上来。这里我对我们的EurekaServer的URL在配置的时候做了更改,就是设置了EurekaServer模块的 eureka.instance.hostname属性的值为 eureka7001.com   这一点我在我以前的博客中是有讲解记录的(这个操作还需要更改一个系统的host配置文件)。

再向下,是ribbon.ReadTimeout属性。这里我想先说明微服务中默认使用Ribbon做负载均衡,进行RPC的时候,存在的问题。当系统逐渐变得复杂的时候,调用链路变得越来越长的时候,消费者发送一个HTTP请求以后,可能需要等好久才能得到结果。但是等好久具体是要等多久呢?建立HTTP连接,从链路的服务提供者一方获得资源结果并返回,这些操作都是需要时间的。那总得有个时间限制吧,不能不顾一切的等下去吧。所以为了解决这个问题,Ribbon就有了接下来要记录到的属性。接下来的这两个属性其实也是可以不设置的,如果不设置的话,它们是有默认值的,默认值好像是1秒。如果不设置,那就采用默认值,如果链路过长的话,在默认值1秒内,没有返回结果,那么服务调用端就会报错(这有点类似于服务降级),下图就是我演示了一下在默认时间内没有返回结果的情况下,就直接会在浏览器的界面出现错误:

微服务模块超时配置 微服务模块调用_spring_03

 

首先要明白的是,这个属性设置的是时间,并且单位是毫秒。这个属性描述的是建立链接以后从服务端读取到可用资源需要的时间。这里我设置成了5秒

跟它类似的是  ribbon.ConnectTimeout属性    这个属性描述的是建立连接所用的时间的限制。这里我也设置成了5秒。

最后一个是属性:logging.level

这个属性是和日志有关的。首先我说一下OpenFeign的另外的日志功能(这一点我在我的上一篇博客当中没有介绍)。OpenFeign提供了日志打印的功能,我们可以通过配置调整日志的级别,从而了解OpenFeign中HTTP请求的细节(我觉得这一点是很有必要的,因为都做到了封装嘛)。也就是对Feign接口的调用情况进行监控并且输出监控到的内容(为什么这里会出现接口的身影呢?我在我的上一篇博客中陈述过,Feign为了避免那些麻烦,做了进一步的封装,我们只需创建一个接口并使用注解(@FeignClient,@RequestMapping,@Repository)的方式来配置它,即可完成对服务提供方的接口绑定)。这个Feign接口就是我们这个模块中需要在service层创建的一个接口,后面我会记录到的。也正是这个接口(外加注解)的作用,才做到了跟服务提供者之间的绑定。

我再陈述一下我的逻辑:OpenFeign做到了封装,但是呢,封装的太完善了,以至于我们无法直接就看到内部HTTP请求执行的情况,于是OpenFeign就给了一个监控的功能,并且可以将监控到的情况打印出来,这就是打印日志。既然需要监控,就要有监控的对象吧,这个对象就是OpenFeign中使用到的绑定服务提供者的接口。现在需要使用的对象和功能都有了,还差配置的一步,就是把它们组装起来。

所以  logging.level属性就是配置打印哪里的日志,以什么样的方式去打印。首先打印哪里的日志(监控那个接口):这个属性的属性值我设置了  com.springcloud.service.PaymentFeignService,这是被监控接口的  在项目中的路径(我记得是可以直接复制过来的,具体操作我忘记了,但也完全可以手打的,只不过可能会打错)。

后面还跟了 debug  这个是日志的打印级别。这一点我就不做过多的记录了,本来我也就是不理解。

6、service层接口

使用OpenFeign比较核心的我认为就是这个接口了,因为要通过这个接口来绑定(这里我提醒一下,一个被调用的微服务需要对应着一个绑定接口),进而进行RPC。    我的这个项目代码由于非常的简单,因此service文件夹中只有一个接口,接口代码如下所示:

package com.springcloud.service;

import com.springcloud.entities.CommonResult;
import com.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

//保证它能够扫描的到
@Repository
//下面这个注解保证能够下面的这个接口作为一个Feign功能实现的接口,加上了提供服务的名称
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    //我在课程上面看到的说,controller层的@GetMapping的value的值应当和这个的值一样
    //但是我的这个就不一样,可以正确的运行
    //但是
    //这两个层次的函数所用到的形参的注解必须要一样,都要使用PathVariable
    //上面的这个路径是不能少的,我也不知道为什么就不能少
    //上面的这个路劲需要和服务提供者,也就是8001,8002中的controller中的路径一样
    //程序就是根据这个路径去调用的
    CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);

    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout();

}

首先就是@Repository注解,这个注解跟@Service注解是一样的。

然后就是@FeignClient注解。这个注解是使用OpenFeign所必须的。这个注解能够保证这个接口绑定服务提供者微服务模块。然后这个注解有value属性,需要设置为被调用的服务提供者的application文件中的spring.application.name属性的值。    

注意上面的两个注解都是放在接口上面的。

然后进入到接口的内部,就是两个方法。这两个方法的方法名我在这里建议和被调用的服务提供者微服务模块的具体执行的方法的方法名是一样的。这样以后,同样是需要设置@GetMapping()或者是@PostMapping注解的。具体使用那一个注解也需要和被调用的服务提供者微服务模块的具体执行的方法一样,包括注解里面的value属性的值,也必须是一样的。这里我想多说一点的是:假如被调用的服务提供者微服务模块的具体执行controller类的上面也有@GetMapping注解或者是@PostMapping注解,这个接口上面也是需要有一模一样的注解的,并且注解的value值也需要是一样的。

这个接口也正是application文件中设置的那个被日志监控的接口。

7、controller层类

已经在service层使用OpenFeign,使一个接口和被调用的服务提供者的微服务模块做了绑定,并且很详细的绑定了对应的方法和接口的方法。

下面就可以在controller层中调用service层接口的方法了,依旧是由于我的模块的代码比较的简单,所以我的controller中只有一个类,下面是具体的代码:

package com.springcloud.controller;

import com.springcloud.entities.CommonResult;
import com.springcloud.entities.Payment;
import com.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderFeignController {
    @Resource
    private PaymentFeignService paymentFeignService;
    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentById(id);
    }

    @GetMapping("/consumer/payment/feign/timeout")
    public String paymentFeignTimeout(){
        //在默认情况下,OpenFeigen默认是等待一秒钟的
        return paymentFeignService.paymentFeignTimeout();
    }
}

首先就是@RestController注解,这个注解加在controller层类的外面是必须的,我不再多解释了。

然后是@Slf4j注解,这个注解属于LomBok插件,跟日志有关,就比如你使用log.info()方法,就需要这个注解。

然后进入到类的内部,使用@Resource注解将刚才的接口注入进来。

下面就是最简单的业务逻辑,也就是方法的调用。这个时候的方法你是可以随便写方法名和@GetMapping()注解的value的值。 我的里面的连个方法都是比较简单的,这里我就不再多解释什么了。

我唯一想要解释的是  CommonResult<Payment>这个类。这是我定义的一个泛型类,是为了采用前后端分离的开发方式而写的。Payment就是一个更加简单的实体类而已。这两个类我都剥离了出来,放在了我的一个公共模块当中,然后打成了jar包。

8、为了打印日志而建的一个配置类

我在application文件中进行了下面的配置:

logging:
  level:
    com.springcloud.service.PaymentFeignService: debug

但这还是不够的,还需要有一个配置类。因此,我建了一个configuration文件夹,这个文件夹中只有一个配置类。代码如下所示:

package com.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//明显的这是一个配置类,类似于在SpringMVC中的xml文件
//这个配置类中的内容是为了打印日志所必须的内容
@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

首先就是@Configuration注解,这个是必须的,我就不再多陈述什么了

然后进入到类的内部,就是一个@Bean注解(这里有没有想到RestTemplate类的配置?)

到日志等级有 4 种,分别是:

  • NONE:不输出日志。
  • BASIC:只输出请求方法的 URL 和响应的状态码以及接口执行的时间。
  • HEADERS:将 BASIC 信息和请求头信息输出。
  • FULL:输出完整的请求信息
  •  

日志的四种级别如上所示。

9、主启动类

全部的业务逻辑代码,配置的代码都写完了,不要忘记了最后的  主启动类(也就是Main方法)

主启动类代码如下所示:

package com.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
//下面的这个注解说明我们使用的是Feign技术,可以理解为开启这个注解包含的所有注解的使用
@EnableFeignClients
@EnableEurekaClient//这个是使用Eureka作为服务注册中心所必须的注解
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}

 首先是@SpringBootApplication注解,这个注解是必须的,我这里就不再多说了

然后由于使用的是Eureka作为服务注册中心,因此需要使用到@EnableEurekaClient注解

最后,就是我们使用的OpenFeign。在那个接口上面我们使用了@FeignClient注解,这个注解的使用有一个前提条件,就是需要在主启动类上面使用@EnableFeignClients注解,这样以后接口上面使用的注解才起作用。

10、启动运行

启动EurekaServer集群,再启动被调用的名称为cloud-payment-service的微服务模块,最后启动这个90为端口号的服务消费者模块。启动以后,地址栏内可以正常访问。控制台的输出如下所示:

微服务模块超时配置 微服务模块调用_微服务模块超时配置_04