1. openFeign简介
Feign是一个声明式的Web Service客户端,简单可以理解为封装了Ribbon+RestTemplate的模板代码。
在Spring Cloud中使用Feign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。Spring Cloud Open Feign对Feign进行增强支持Spring MVC注解,可以像Spring Web一样使用HttpMessageConverters等。
OpenFeign的功能:
- 1.支持Hystrix和它的Fallback。
- 2.支持Ribbon的负载均衡。
- 3.支持日志
- 4.支持错误重试
下面截图可以看到,openfeign支持了Ribbon和Hystrix的核心功能。
@EnableFeignClients和@FeignClient 注解介绍
@FeignClient 标注用于声明Feign客户端可访问的Web服务。@EnableFeignClients 声明在应用启动类上,扫描应用中声明的Feign客户端可访问的Web服务。
@FeignClient关键属性介绍
- value/name:指定远程调用的微服务的名称
- fallback:指定客户端降级处理的类,fallback指定的class必须实现当前 @FeignClient 声明的接口,并且是spring容器中的bean。
下面通过代码,演示openFeign的负载均衡,超时配置,fallback处理和日志配置。
在工程006SpringCloud中用到了5个模块, eureka-server-7001 和 eureka-server-7002 作为注册中心;provider-payment9001和provider-payment9002作为服务提供者,两个实例用于演示负载均衡;consumer-openFeign-order8002服务消费者,也是openFeign的客户端。
006SpringCloud目前的工程结构如图所示:
2. 服务提供者paymentService提供的接口
服务提供者 paymentService 有两个服务实例,分别对应 provider-payment9001 和 provider-payment9002。
在微服务 paymentService 的两个服务实例中,都提供了3个接口:
- “/get/{id}”:根据id查询payment实例;
- “/timeout”:通过 TimeUnit.SECONDS.sleep(3) 模拟业务处理超时的请求。
- “/excep”:模拟业务处理异常
PaymentController.java
@RestController
@RequestMapping("/payment")
public class PaymentController {
private Logger logger = LoggerFactory.getLogger(PaymentController.class);
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
if (id != null && id % 2 == 0) {
Payment payment = Payment.newInstance().setSerial(UUID.randomUUID().toString()).setId(id);
return CommonResult.newInstance().setCode(200).setMessage("查询成功,serverPort:" + serverPort).setData(payment);
} else {
return CommonResult.newInstance().setCode(444).setMessage("没有对应记录,查询ID: " + id).setData(null);
}
}
/**
* 模拟请求超时
*
* @return
* @throws InterruptedException
*/
@GetMapping(value = "/timeout")
public CommonResult<Payment> timeout() throws InterruptedException {
this.logger.info("timeout-----" + LocalDateTime.now());
//模拟业务处理时间,线程睡眠3s
TimeUnit.SECONDS.sleep(3);
return CommonResult.newInstance().setCode(200).setMessage("查询成功,serverPort:" + serverPort).setData(null);
}
/**
* 模拟请求异常
*
* @return
*/
@GetMapping(value = "/excep")
public CommonResult<Payment> excep() {
this.logger.info("excep-----" + LocalDateTime.now());
//模拟业务处理异常
int div = 1 / 0;
return CommonResult.newInstance().setCode(200).setMessage("查询成功,serverPort:" + serverPort).setData(null);
}
}
3. 服务消费者orderService
服务消费者orderService对应的是consumer-openFeign-order8002模块。
pom.xml,集成 spring-cloud-starter-openfeign 组件
<?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>006SpringCloud</artifactId>
<groupId>com.xander</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer-openFeign-order8002</artifactId>
<dependencies>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--common-api-->
<dependency>
<groupId>com.xander</groupId>
<artifactId>common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
application.yml
server:
port: 8002
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 集群版-
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
# 服务实例id
instance-id: order8002
#访问路径可以显示IP地址
prefer-ip-address: true
spring:
application:
# 微服务名称
name: orderService
启动类ConsumerOrder8002,添加@EnableFeignClients注解
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients // 开启 OpenFeign 功能,扫描 @FeignClient 标注的类
public class ConsumerOrder8002 {
public static void main(String[] args) {
SpringApplication.run(ConsumerOrder8002.class, args);
}
}
PaymentOpenFeignService配置@FeignClient注解,指定调用的服务名: paymentService
@FeignClient(value = "paymentService")
public interface PaymentOpenFeignService {
@GetMapping(value = "/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") String id);
/**
* 模拟请求超时
*
* @return
*/
@GetMapping(value = "/payment/timeout")
CommonResult<Payment> timeout();
/**
* 模拟请求异常
*
* @return
*/
@GetMapping(value = "/payment/excep")
CommonResult<Payment> excep();
}
OrderController像本地调用一样直接调用PaymentOpenFeignService,不用关心底层的网络请求过程
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private PaymentOpenFeignService paymentOpenFeignService;
@GetMapping("/getPayment/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") String id) {
return this.paymentOpenFeignService.getPaymentById(id);
}
/**
* 模拟请求超时接口
*
* @return
*/
@GetMapping(value = "/timeout")
public CommonResult<Payment> timeout() {
return this.paymentOpenFeignService.timeout();
}
/**
* 模拟请求异常接口,用于演示 openFeign 的fallback属性配置服务降级处理
*
* @return
*/
@GetMapping(value = "/excep")
public CommonResult<Payment> excep() {
return this.paymentOpenFeignService.excep();
}
}
4. 测试负载均衡
分别启动Eureka注册中心,服务提供者provider-payment9001 和 provider-payment9002,服务消费者consumer-openFeign-order8002。
通过浏览器调用 http://localhost:8002/order/getPayment/2 ,可以看到openFeign默认实现了负载均衡。
5. 模拟超时请求测试和 ribbon 的超时配置
接着调用模拟超时接口 http://localhost:8002/order/timeout ,发生了超时,这是因为openFeign封装了ribbon,而ribbon的默认 ConnectTimeout 和 ReadTimeout 都是 1000ms。
paymentService上的 “/payment/timeout” 接口的业务处理时间为 3000ms,所以这里我们配置ribbon的读取超时时间 ReadTimeout 为 5000ms。
在consumer-openFeign-order8002的 application.yml中添加ribbon的配置
### 全局Ribbon 配置
ribbon:
#启用rest client配置
restclient:
enabled: true
# http建立socket超时时间,毫秒,默认是1000
ConnectTimeout: 1000
# http读取响应socket超时时间,默认是1000毫秒
ReadTimeout: 5000
重启 consumer-openFeign-order8002 服务,再次请求 http://localhost:8002/order/timeout ,没有超时,读取成功。
6. fallback配置客户端的降级处理
我们发送 http://localhost:8002/order/excep 请求,发现响应异常了,这是因为服务提供者的业务接口异常,给服务消费者响应了无法处理的异常响应,紧接着服务消费者也抛出了异常。
consumer-openFeign-order8002中配置openFeign的降级处理
服务有问题(异常或超时)时,向调用方返回一个符合预期的,可处理的备选响应,这就叫做降级处理。
前面介绍了@FeignClient的fallback属性用于指定客户端降级处理的类,fallback指定的class必须实现当前 @FeignClient 声明的接口,并且是spring容器中的bean。
定义类PaymentOpenFeignServiceFallback 实现接口 PaymentOpenFeignService
@Component
public class PaymentOpenFeignServiceFallback implements PaymentOpenFeignService {
@Override
public CommonResult<Payment> getPaymentById(String id) {
return CommonResult.newInstance().setCode(200).setMessage("openFeign fallback 降级处理:getPaymentById");
}
@Override
public CommonResult<Payment> timeout() {
return CommonResult.newInstance().setCode(200).setMessage("openFeign fallback 降级处理:timeout");
}
@Override
public CommonResult<Payment> excep() {
return CommonResult.newInstance().setCode(200).setMessage("openFeign fallback 降级处理:excep");
}
}
PaymentOpenFeignService的@FeignClient注解配置fallback属性,指定为PaymentOpenFeignServiceFallback.class
@FeignClient(value = "paymentService",fallback = PaymentOpenFeignServiceFallback.class)
public interface PaymentOpenFeignService {
......
}
在application.yml中添加feign.hystrix.enabled=true,在 feign 中启动 Hystrix 支持
feign:
hystrix:
enabled: true # 在 feign 中启动 Hystrix 支持
重启 consumer-openFeign-order8002 服务,再次请求 http://localhost:8002/order/excep ,客户端没有抛出异常,客户端降级处理成功。
7. openFeign的日志配置
openFeign日志默认输出级别是 debug 级别,所以我们得要在debug级别,才能看到openFeign相关的日志输出。
openFeign的内容打印级别又分为了四个级别,可以在 feign.Logger.Level 中看到:
public static enum Level {
NONE,
BASIC,
HEADERS,
FULL;
private Level() {
}
}
- NONE: 不开启日志(默认)
- BASIC: 记录请求方法、URL、响应状态、执行时间
- HEADERS: 在BASIC基础上,加上请求头和响应头信息
- FULL: 在HEADERS基础上,加上请求和响应的正文及元数据
配置 open feign 的日志打印级别
方式一:feign.client.config.微服务id.loggerLevel
,微服务id: default代表全局默认配置。
在application.yml中添加日志配置:
logging:
level:
# 配置 PaymentOpenFeignService 的日志级别为 debug,这样才能打印 openFeign 相关的日志
com.xander.order.service.PaymentOpenFeignService: debug
feign:
client:
config:
default: # 微服务id: default代表全局默认配置
loggerLevel: FULL # 配置 open feign 的日志打印级别
hystrix:
enabled: true # 在 feign 中启动 Hystrix 支持
发送请求 http://localhost:8002/order/getPayment/2 ,可以看到 consumer-openFeign-order8002 模块上打印处 openFeign 的详细请求日志
2021-05-21 15:58:43.919 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] ---> GET http://paymentService/payment/get/2 HTTP/1.1
2021-05-21 15:58:43.919 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] ---> END HTTP (0-byte body)
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] <--- HTTP/1.1 200 (5ms)
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] connection: keep-alive
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] content-type: application/json
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] date: Fri, 21 May 2021 07:58:43 GMT
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] keep-alive: timeout=60
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] transfer-encoding: chunked
2021-05-21 15:58:43.925 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById]
2021-05-21 15:58:43.926 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] {"code":200,"message":"查询成功,serverPort:9001","data":{"id":2,"serial":"c9912a75-729f-4d00-af17-56d024a54c50"}}
2021-05-21 15:58:43.926 DEBUG 20184 --- [nio-8002-exec-2] c.x.o.service.PaymentOpenFeignService : [PaymentOpenFeignService#getPaymentById] <--- END HTTP (119-byte body)
方式二:配置 feign.Logger.Level 的 Bean 实例
@Configuration
public class AppConfig {
/**
* 配置 open feign 的日志打印级别
* - NONE: 不开启日志(默认)
* - BASIC: 记录请求方法、URL、响应状态、执行时间
* - HEADERS: 在BASIC基础上,加上请求头和响应头信息
* - FULL: 在HEADERS基础上,加上请求和响应的正文及元数据
*
* @return
*/
@Bean
Logger.Level feignLogLervel() {
return Logger.Level.FULL;
}
}