1、OpenFeign引言
Ribbon 提供了客户端的负载均衡问题,RestTemplate 封装了 HTTP 的通讯,简化了发送请求过程。两者相辅相成构建了服务间的高可用通信。
但在使用RestTemplate后发现,它对 HTTP 的封装,如URL、请求参数、请求头、请求体这些处理非常繁琐
Feign和OpenFeign
- Netflix Feign:开源声明式 WebService 客户端,采用“接口+注解”的方式开发,屏蔽网络通信的细节,但使用舒适度一般。
- Spring Cloud OpenFeign 是Netflix Feign 的基础上进行的封装,用于简化服务间通信,结合原有 Spring MVC 的注解,对 Spring Cloud 微服务通信提供了良好的支持,大幅简化了服务间高可用通信处理过程。
2、OpenFeign的使用
2.1、OpenFeign的使用步骤
a. 导入依赖
<!-- 引入openFeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
b. 在启动类上添加@EnableFeignClients,启动Feign
@SpringBootApplication @EnableFeignClients//不可省略 public class xxxServiceApplication { public static void main(String[] args) { SpringApplication.run(xxxServiceApplication.class, args); } }
c. 开发Fegin的服务远程调用客户端,该接口形似对应的controller
/* value/name:指定提供者的微服务名称 url:直接指定请求的路径地址 decode404:是否应该编码或者抛出FeignException异常 configuration:配置feign.codec.Decoder、feign.codec.Encoder、feign.Contract fallback:指定发送异常调用或者超时时应该调用那个类来执行备用方法 fallbackFactory:提供统一的异常熔断处理,避免重复代码的编写 path:当服务提供者使用了server.context.path时,对应提供者的项目路径。 contextId:用来定义spring bean的名称,给service层注入调用 */ @FeignClient(value="user-service",contextId = "userClient") @RequestMapping("users") public interface UserClient { @GetMapping("{id}") User queryUserById(@PathVariable("id")Long id); } //注: 可使用的注解:路径的@pathVariable,请求方式,参数的注解等 //对应的远程controller: @RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public User queryUserById(@PathVariable("id") Long id){ return userService.queryUserById(id); } }
d. 通过Spring工程获取上一步定义的Fegin客户端,使用客户端远程调用服务
2.2、GET请求多参数传递
一个服务方法需要接收多个请求参数时
- 若服务接口参数时会使用实体类接收,使用OpenFeign定义客户端方法时保持相同即可。
- 但在使用OpenFeign时,服务方法多个参数是零散接收的,OpenFeign客户端方法希望使用实体类传输数据,如何实现?
示例:
- 服务端user-service定义一个零散收集多个参数的接口:
@RestController @RequestMapping("/user") public class UserController { @GetMapping("login") public String login(String username,String password){ System.out.println("username = " + username + ", password = " + password); return "success"; } }
- 此时要使用对象传递多个参数,定义一个dto封装参数,使用@SpringQueryMap注解。
@FeignClient(value="user-service",contextId = "userClient") @RequestMapping("user") public interface UserClient { @GetMapping("login") //等同于 String login(String username,String password); String login(@SpringQueryMap UserDTO userDTO); }
- service层注入userClient对象,调用其方法即可
2.4、Fegin日志配置
当 API 调用失败后,需要有详细的请求信息来分析失败原因,我们可以设置 Feign 的日志级别来输出详细的请求信息,Feign 的日志级别有四种:
- NONE 表示不输出日志。
- BASIC 表示只输出请求方法的 URL 和响应的状态码以及执行的时间。
- HEADERS 将 BASIC 信息和请求头信息输出。
- FULL 会输出全部完整的请求信息。
#注意:要将springboot提供的日志对远程服务客户端设为debug,配置才能生效 logging: level: 包名.服务接口名: debug feign: client: config: #默认全局配置 default: loggerLevel: basic #特定的配置:要调用的Feign客户端的contextId userClient: loggerLevel: full
2.5、负载均衡策略
OpenFeign 工作时默认会引用 Ribbon 实现客户端负载均衡,配置方式与之前 Ribbon+RestTemplate 方案完全相同
服务名: ribbon: #设置对应的负载均衡类 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
2.6、数据压缩功能
在 OpenFeign 中,默认没有开启数据压缩功能。
- 若服务间单次传递数据超过 1K 字节,强烈推荐开启数据压缩功能。默认使用 Gzip 方式压缩,对大文本通常压缩后尺寸是原数据的 10%~30%,极大提高了带宽利用率。
- 但如果应用属于计算密集型,CPU 负载长期超过 70%,因数据压缩、解压缩都需要 CPU 运算,开启数据压缩功能反而会给 CPU 增加额外负担,导致系统性能降低,并不可取。
feign: compression: request: # 开启请求数据的压缩功能 enabled: true # 压缩支持的MIME类型 mime-types: text/xml,application/xml,application/json # 数据压缩下限 1024表示传输数据大于1024 才会进行数据压缩(最小压缩值标准) min-request-size: 1024 # 开启响应数据的压缩功能 response: enabled: true
2.7、okhttp替换默认通信组件
OpenFeign 和RestTemplate默认使用 Java 自带的 URLConnection 对象创建 HTTP 请求,但如果更换为 Apache HttpClient、OKHttp等专用通信组件,基于这些组件自带的连接池,可以更好地对 HTTP 连接对象进行重用与管理。
a. 引入feign-okhttp依赖包
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
b. 通过Java Config初始化OkHttpClient对象
@Bean public okhttp3.OkHttpClient okHttpClient(){ return new okhttp3.OkHttpClient.Builder() //读取超时时间 .readTimeout(10, TimeUnit.SECONDS) //连接超时时间 .connectTimeout(10, TimeUnit.SECONDS) //写超时时间 .writeTimeout(10, TimeUnit.SECONDS) //设置连接池 .connectionPool(new ConnectionPool()) .build(); }
c. 在yaml中启用OkHttp
feign: okhttp: enabled: true
2.8、超时配置
和RestTemplate中类似,在OpenFeign中也可以使用Ribbon实现超时的失败重试,具体配置步骤如下:
a. 引入spring-retry依赖
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
b. 在application.yml中根据client的id配置超时时间
feign: client: config: #要调用的Feign客户端的contextId userClient: #这里的配置将覆盖代码对okhttp的超时配置 readTimeout: 1000 #配置读取数据超时 connectTimeout: 2000 #配置连接超时
c. 在application.yml中配置重试机制
- 全局的重试配置
ribbon: MaxAutoRetries: 1 #单服务器重试次数(不包含第一次请求),这里失败则更换服务器 MaxAutoRetriesNextServer: 2 #更换服务器的次数(不包含第一次) OkToRetryOnAllOperations: false #默认只对GET请求重试, 当设置为true时, 对POST等所有类型请求都重试
- 特定服务的重试配置
user-service: #服务名 ribbon: MaxAutoRetries: 2 MaxAutoRetriesNextServer: 1 OkToRetryOnAllOperations: false
2.9、拦截器
OpenFeign提供了拦截器机制,允许我们对Feign的请求过程进行拦截处理。
a. 自定义拦截器:实现RequestInterceptor接口
public class MyFeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { System.out.println("MyFeignRequestInterceptor.apply"); } }
b. 配置拦截器
- 全部配置(配置类加入bean,或者在拦截器加入@Component)
@Bean public MyFeignRequestInterceptor feignRequestInterceptor(){ return new MyFeignRequestInterceptor(); }
- 局部配置:针对特定OpenFeign客户端
feign: client: config: default: loggerLevel: basic #要调用的Feign客户端的contextId userClient: loggerLevel: full #配置拦截器(数组形式) requestInterceptors: - com.baizhi.cloud.alibaba.feign.interceptor.MyFeignRequestInterceptor