Feign 服务调用
使用微服务必不可少会用到 Feign
。 为了方便大家无感知使用请认真熟读本章节
Spring Cloud OpenFeign
是Spring 官方为替代进入停更维护状态的 Feign 推出的另一款声明式服务调用与负载均衡组件。
它具有 Feign
的所有功能,并在其基础上增加了对 Spring MVC
注解的支持。
基础知识
添加下面依赖即可快速使用 Spring Cloud OpenFeign
与负载均衡
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
负载均衡
当需要用 RestTemplate
显示调用的时,需要给 RestTemplate
的实例上添加 @LoadBalanced
表示它是一个负载均衡的调用
@Bean
@Primary
@LoadBalanced
public RestTemplate lbRestTemplate() {
return new RestTemplate();
}
性能优化
Gzip 压缩
服务之间调用或者文件传输的时候开启压缩支持可以大大的提升响应效率
开启压缩可以有效节约网络资源,但是会增加CPU压力,建议把最小压缩的文档大小适度调大一点。
feign:
compression:
request:
# 开启请求压缩
enabled: true
# 配置压缩支持的 MIME TYPE
mime-types: text/xml,application/xml,application/json
# 配置压缩数据大小的下限
min-request-size: 2048
response:
# 开启响应压缩
enabled: true
连接池配置
开启 http-client
配置替换默认的 URLConnection
同时配置连接池和超时时间,提高响应速度
feign:
httpclient:
enabled: true # HttpClient的开关
connection-timeout: 3000 # 连接超时(毫秒)
time-to-live: 3 # 连接存活时长
time-to-live-unit: minutes # 连接存活时长单位
max-connections: 500 # 最大连接数
max-connections-per-route: 50 # 单个请求路径的最大连接数
详细日志
Bean 注入的方式
配置如下 @Bean
既可开启详细日志(不过在生产环境建议关掉,可以通过表达式控制 @Bean
在生产环境不注入)
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
配置文件方式
feign:
client:
config:
default: #这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL #日志级别
进阶知识
扩展封装
为了方便开发过程中减少繁琐的代码冗余问题,专门针对 Feign
请求做了下列功能的增强
- 数据解码
- 代理请求
- 请求透传
- 自动刷新 TOKEN
<dependency>
<groupId>com.wemirr.framework</groupId>
<artifactId>feign-plugin-spring-boot-starter</artifactId>
</dependency>
数据解码
这是一个很实用的功能,很多时候我们希望写 @FeignClient
的时候规避掉 Result.class、R.class
统一类型,只想拿结果集直接处理
// 配置全局解码之前
@GetMapping("/list")
Result<List<POJO>> list();
// 配置全局解码之后
@GetMapping("/list")
List<POJO> list();
上面的案例中,如果返回的统一对象还得先提取一下 List<POJO> list = result.getData()
还得判断空以免出现 NPE
的问题。
写多了同时会影响代码整洁,那么配置全局解码后即可忽略全局对象,同时默认采用 SpringDecoder
而非 FeignDefaultDecoder
更加贴合日常开发
注意
配置之后并不影响你写 Result<List<POJO>>
这样的代码,所以你无需担心,因为它是兼容的
@Bean
public Decoder feignDecoder(ObjectFactory<HttpMessageConverters> messageConverters,
ObjectProvider<HttpMessageConverterCustomizer> customizers) {
return new OptionalDecoder((new ResponseEntityDecoder(new FeignResponseDecoder(new SpringDecoder(messageConverters, customizers)))));
}
@Bean
public ErrorDecoder errorDecoder() {
return (s, response) -> {
log.warn("response status is:{}", response.status());
return new ErrorDecoder.Default().decode(s, response);
};
}
请求透传
这个还是很有用的,不然 FeignClient
在发送 HTTP 请求的时候默认是不会携带上游 Header 的,意味着你后续接口如果有 Token 限制就会访问受限
@Bean
@Order(-999999)
public FeignPluginInterceptor feignPluginInterceptor(FeignPluginProperties properties) {
return new FeignPluginInterceptor(properties);
}
代理请求
一个很实用的功能 它可以使你在微服务场景下向单机一样的去写代码,在也不用苦恼开发阶段需要开一堆服务机器内存不够了
可以让你在内网开发过程中,只需要启动编码的程序即可,其它服务会自动代理请求配置的 HTTP 地址去
举个例子:假设你跟很多小伙伴开发订单服务
,它需要依赖商品
、认证
、会员
等服务的接口,而你又不能将订单服务
注册到测试环境中去,这时候自己启动一堆服务机器又卡,开发效率也低下,那么就可以用这种方式啦
配置启用
extend:
feign:
plugin:
mock:
enabled: true
server-map:
baseserver:
# 真实的请求地址
server-url: http://192.168.1.1:5002/base/api
核心代码
@Bean
@Primary
@ConditionalOnProperty(prefix = MockProperties.MOCK_PREFIX, name = "enabled", havingValue = "true")
public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers,
MockProperties mockProperties) {
return new MockLoadBalancerFeignClient(new Client.Default(null, null),
loadBalancerClient, loadBalancerClientFactory, transformers, mockProperties);
}
自动刷新 Token
解决内部服务调用时没有 Token 的尴尬情况
比如定时任务调用访问受限的接口,这时候没有 Token
就自动根据配置的系统账号生成一个
配置启用
extend:
feign:
plugin:
token:
enabled: true
# 是否为负载均衡请求(最终还是被我改成真实请求了)
load-balance: true
# Feign 服务地址
uri: http://wemirr-platform-authority/oauth2/token
# 如果定时任务需要动态设置租户目前没想到好的方案,欢迎提供思路和建议
o-auth:
client-id: messaging-client
client-secret: 123456
username: admin
password: 123456
tenant-code: "0000"
scope: platform
核心代码
基于 FeignInterceptor 做了拦截处理,在请求之前模拟登陆将 Token
存在本地缓存中,提高性能
@Bean
@ConditionalOnProperty(prefix = AutoRefreshTokenProperties.TOKEN_PREFIX, name = "enabled", havingValue = "true")
public AutoRefreshTokenInterceptor feignTokenInterceptor(AutoRefreshTokenProperties properties) {
final AutoRefreshTokenProperties.Cache cache = properties.getCache();
Cache<String, String> tokenCache = CacheBuilder.newBuilder().initialCapacity(cache.getInitialCapacity())
.maximumSize(cache.getMaximumSize()).expireAfterWrite(cache.getExpire(), TimeUnit.SECONDS).build();
return new AutoRefreshTokenInterceptor(properties, tokenCache);
}