OpenFeign 服务接口调用
Feign简介
Feign 是一个声明式的 Web 服务客户端,让编写 Web 服务客户端变得非常容易,只需要创建一个接口并在接口上添加注解即可。Feign 是 Spring Cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端 Feign 内置了 Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign 的使用方式是:使用 Feign 的注解定义接口,调用这个接口就可以调用服务注册中心的服务。
前面在使用 Ribbon + RestTemplate 时,利用 RestTemplate 对 http 请求的封装处理, 形成了一套模版化的调用方法。但是在实际开发中,**由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。**在Feign的实现下,我们只需创建一 个接口并使用注解的方式来配置它( 以前是 Dao 接口上面标注 Mapper 注解,现在是一 个微服务接口上面标注一 个 Feign 注解即可 ),即可完成对服务提供方的接口绑定,简化了使用 Spring cloud Ribbon 时,自动封装服务调用客户端的开发量。
Feign 集成了 Ribbon
利用 Ribbon 维护了 Payment 的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与 Ribbon 不同的是,通过 Feign 只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
OpenFeign 简介
OpenFeign 是 SpringCloud 在 Feign 的基础上支持了 SpringMVC 的注解,如 @RequesMapping 等等。OpenFeign 的 @FeignClient 可以解析 SpringMVC 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
OpenFeign 服务调用
理论知识了解完之后我们就可以开始编写代码了!首先先带大家使用 OpenFeign 编写一个简单的服务调用
-
新建一个名为 cloud-consumer-feign-order80 的子工程
-
修改 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>SpringCloud2020</artifactId> <groupId>com.lyang.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-consumer-feign-order80</artifactId> <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> <!-- 引入自己定义的api通用包,可以使用 Payment 支付 Entity --> <dependency> <groupId>com.lyang.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency> <!-- web --> <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>
-
编写 yml 文件
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
-
编写启动类
/** * @EnableFeignClients: 启用 Feign 客户端 */ @SpringBootApplication @EnableFeignClients public class OrderFeignMain80 { public static void main(String[] args) { SpringApplication.run(OrderFeignMain80.class,args); } }
记得加上 @EnableFeignClient 注解!
-
编写业务类
业务逻辑层
/** * @FeignClient: 把修饰的类或接口纳入进spring容器中管理。 * @FeignClient: 表示 FeignClient注解的作用目标在接口上 * 这里只是通过这个注解去 Eureka 中找到对应的微服务,然后下面的方法就是该微服务中的某个方法的调用地址 */ @Component @FeignClient(value = "CLOUD-PAYMENT-SERVICE") public interface PaymentFeignService { //直接调用对应的地址(这里调的是对应服务中的方法地址) @GetMapping(value = "/payment/get/{id}") public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id); }
这里的方法名没有要求可以自己修改,但是返回类型和参数一定要与路径对应的方法一致!!!我这里为了方便所以就直接从8001中复制过来了,所以方法名和对应得方法一致。
控制层
@RestController @Slf4j public class PaymentFeignController { @Resource private PaymentFeignService paymentFeignService; @GetMapping(value = "/consumer/payment/{id}") public CommonResult<Payment> getPayemntById(@PathVariable("id") Long id) { //这里直接调用方法即可,因为在业务逻辑层已经获取到了8001服务中的某个方法 return paymentFeignService.getPaymentById(id); } }
这里就只使用 service 和 controller 即可
-
测试
这里也是会有轮询的效果的
-
小总结
红色部分对应红色部分,蓝色部分对应蓝色部分,看完这个图然后在去看一下刚才编写的代码,相信大家应该就都能够懂得为什么要这样写。
到此,OpenFeign 的服务调用就写完了!接下来我们在来了解一下 OpenFeign 的超时控制(因为消费者去调用服务者的时候因为是不同的服务调用,所以一定会发生超时的现象!)
OpenFeign 超时控制
因为不同服务之间调用会出现超时导致的超时报错,所以我们就需要来控制它的超时时间
我们先来写一个故意报错的程序
-
在 8001 的控制层中创建一个方法,作用为暂停线程
/** * @RestController: 作用等同于@Controller + @ResponseBody * @Slf4j: 使用前需要下载 Lombok 插件,日志 */ @RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; //获取端口号 @Value("${server.port}") private String serverPort; //获取相关服务信息 @Resource private DiscoveryClient discoveryClient; //...此处省略其他方法 @GetMapping(value = "/payment/feign/timeout") public String paymentFeignTimeout() { //暂停几秒钟线程 try { //单位为秒 TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return serverPort; } }
在 openfeign-ribbon 中客户端默认等待 1秒,我们这里给他设置为 3 秒
-
在 feign80 中调用该方法
先在接口中把方法拿过来
/** * @FeignClient: 把修饰的类或接口纳入进spring容器中管理。 * @FeignClient: 表示 FeignClient注解的作用目标在接口上 * 这里只是通过这个注解去 Eureka 中找到对应的微服务,然后下面的方法就是该微服务中的某个方法的调用地址 */ @Component @FeignClient(value = "CLOUD-PAYMENT-SERVICE") public interface PaymentFeignService { //直接调用对应的地址(这里调的是 8001 中的方法地址) @GetMapping(value = "/payment/get/{id}") public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id); //暂停线程几秒钟(对应8001中的方法) @GetMapping(value = "/payment/feign/timeout") public String paymentFeignTimeout(); }
然后在 feign80 中实现
@RestController @Slf4j public class PaymentFeignController { @Resource private PaymentFeignService paymentFeignService; @GetMapping(value = "/consumer/payment/{id}") public CommonResult<Payment> getPayemntById(@PathVariable("id") Long id) { return paymentFeignService.getPaymentById(id); } @GetMapping(value = "/consumer/payment/feign/timeout") public String paymentFeignTimeout(){ /** * OpenFeign-Ribbon,客户端一般默认等待 1 秒,我这里设置为 3 秒 */ return paymentFeignService.paymentFeignTimeout(); } }
-
测试
在浏览器中访问 localhost/consumer/payment/feign/timeout ,我们就会看到以下画面
运行后我们可以发现这个新的方法在运行过程中就会报错,原因就是我们将线程加载时间设置为3秒,但是 OpenFeign 中默认为 1 秒,超过了 1 秒它就不会在继续等待,所以就会报错。
我们在来运行一下其他的方法,比如 localhost:8001/payment/feign/timeout 就可以发现,这个是不会报错的,但是需要等待三秒钟才会执行出来
如果我们想要正常执行 localhost/consumer/payment/feign/timeout 的话,我们可以在 feign80 的配置文件中设置超时时间(也就是我们不使用它默认的超时时间了)
修改 feign80 服务的 yml 配置文件
# 设置 Feign 客户端超时时间(OpenFeign 默认支持 Ribbon) ribbon: # 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间(单位:毫秒) ReadTimeout: 5000 # 指的是建立连接后从服务器读取到可用资源所用的时间(单位:毫秒) ConnectTimeout: 5000
我们这里把它的连接时间和读取时间都设置为 5 秒
设置时间后我们再次运行,会发现这个时候的效果和 localhost:8001/payment/feign/timeout 效果差不多,但是执行完一次之后的 5 秒内不能再次执行,如果再次执行也会导致报错!
OpenFeign 日志增强
Feign 提供了日志打印功能,我们可以通过配置类调整日志级别,从而了解 Feign 中 Http 请求的细节。
简单来说就是对 Feign 接口的调用情况进行监控和输出。
日志级别
NONE: 默认的,不显示任何日志。
BASIC: 仅记录请求方法、URL、响应状态码以及执行时间。
HEADERS: 除了 BASIC 中定义的信息之外,还有请求和响应的头信息。
FULL: 除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
这次我们就使用 FULL 来进行操作
日志实现
-
在 Feign80 服务中创建一个 config 包,然后编写一个日志的配置类
package com.atguigu.springcloud.config; import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; } }
注意: 包不要导错了!!!
-
修改 yml 文件
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka # 设置 Feign 客户端超时时间(OpenFeign 默认支持 Ribbon) ribbon: # 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间(单位:毫秒) ReadTimeout: 5000 # 指的是建立连接后从服务器读取到可用资源所用的时间(单位:毫秒) ConnectTimeout: 5000 # 开启日志 logging: level: # Feign 日志以什么级别监控哪个接口 com.atguigu.springcloud.service.PaymentFeignService: debug
-
测试
因为之前的方法需要等待三秒才能访问,我们这里节约时间就使用 http://localhost/consumer/payment/1 这个路径。
我们运行之后再浏览器中看不出什么变化,但是我们再去看控制台打印时就可以发现,它会将这些数据打印在控制台中,如下图