微信搜索【小二说码】关注,定期发布文章,欢迎关注讨论
Feign
我来说Feign
- 本文的cloud的版本为:Greenwich.RELEASE
- Feign(不就是大粪么!!_ 这个东西可不简单哦)是什么?根据官网解释:
- 客户端组件,提供客户端和服务端之间调用。
- 可以通过
@Configuration
注解或者配置文件来加载配置 - 支持SpringMVC注解,并使用HttpMessageConverters作为消息转换器,比如@GetMapping
- 两大重量级护法:Ribbon来实现负载和重试;Hystrix来实现降级和熔断
- FeignClient通过JDK动态代理来构建Client对象,并封装方法和参数到对象
Map<Method, MethodHandler>
,调用的时候通过方法名和参数来调用
服务远程调用方式
- 方式1:
LoadBalanceClient
的方式 http://ip:port/path(LoadBalanceClient+RestTemplate)
@Autowired
private LoadBalancerClient loadBalancerClient;
@Resource
private RestTemplate restTemplate;
// 调用 这里示例
restTemplate.getForObject(http:ip+port+path,xxx);
- 方式2:
Ribbon
: http://servicaName/path(RestTemplate+Ribbon)
// 引入Bean
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
// 直接在类中注入
@Resource
private RestTemplate restTemplate;
// 引用
// 调用 这里示例
restTemplate.getForObject(serviceId+path,xxx);
- 方式3:本章的大杀器
Feign
通过集成Ribbon,代理集成功能实现接口方式的调用,@FeignClient("service-name")
// 直接在类中注入标注了@FeignClient注解的 orderService
@Autowired
private OrderService orderService;
- 前面两种方式调用都比较麻烦,需要注入对象并且在调用的时候需要拼接http,而第三种Feign的方式是直接通过注入对象的方式来处理,使用起来相当方便,像使用本地的Service,所以重点讲解
Feign开发
入门使用
- ①引入包,②启动类增加
@EnableFeignClients
③配置@FeigntClient
,具体可以参照官网:spring-cloud-starter-openfeign
高级使用
自定义配置(日志、拦截器、编码和解码)
- 日志bean配置方式
- 在FeignClient中增加configuration
@FeignClient(name = "storage", fallback = StorageFallback.class, configuration = FeignConfiguration.class)
- 覆盖默认配置的方式1:
FeignConfiguration
配置bean,重写默认的Feign配置,这个时候类不要配置@Configuration
注解,避免覆盖了其它Feign的默认配置
- 在FeignClient中增加configuration
// 千万不要配置@Configuration注解
@Slf4j
public class FeignConfiguration {
// 配置Bean
@Bean
public Logger.Level level() {
return Logger.Level.FULL;
}
}
// 注意如果日志不生效需要配置指定client的日志级别到debug
logging:
level:
com.feign.XXXClient: debug
- 覆盖默认配置的方式2:配置文件(yml)配置方式
feign:
client:
config:
default:
loggerLevel: full
requestInterceptors:
# 可以用来传递token,跟踪id等需要通过header传递的信息
- com.frank.interceptor.StorageFeignInterceptor
- 如果Bean配置方式和配置文件同时存在,配置文件的方式会优先
- 拦截器、编码和解码的配置也同样支持上面两种方式
眼花缭乱的时间配置
- 先来个求极限的算法吧!!相信很多:好汉都被难倒了,那么最大值怎么算出来的呢?咚咚咚。。。不卖关子了。最大时间=
(ReadTimeout?+ConnectTimeout) * (MaxAutoRetries + 1) * (MaxAutoRetriesNextServer + 1)
(?表示0次或者1次),是不是比数据的极限算法简单很多呢!高数各种极限求法眼花缭乱,早就还给老师了,哈哈!注意:ribbon默认的情况下是单台机器重试次数一次,重试一台机器
# ribbon超时时间
# 每台机器最大重试次数,默认是一次,就是说如果没有配置情况下请求超时会重试一次
storage.ribbon.MaxAutoRetries=2
# 再重试几台机器
storage.ribbon.MaxAutoRetriesNextServer=2
# 连接超时,连接超时可设置较小
storage.ribbon.ConnectTimeout=1000
# 业务处理超时
storage.ribbon.ReadTimeout=2000
# 在所有HTTP Method进行重试,默认配置是false,生产环境如果配置
# 为true需要设置幂等
storage.ribbon.OkToRetryOnAllOperations=true
- 上述配置的优先级别低于fegin.client.config.default.xxxTimeout的时间配置,这个配置会覆盖ribbon的时间配置,上面ribbon配置的时间和重试参数统统不生效
feign:
client:
config:
# 指定自己的feign的名字,默认default对所有的feign生效
default:
connectTimeout: 5000
readTimeout: 5000
- 配置超时时间合理的情况下,建议hystix=connectTimeout+readTimeout,生效时间优先级 feign > hystrix > ribbon > http
降级&熔断
- 降级的配置需要在client来实现
- 开启配置
feign:
hystrix:
enabled: true
hystrix:
command:
# 这里是全局配置,可以针对每个commandKey来配置,也可以通过类#方法
# 获取类#方法(args)的快捷方式:Feign.configKey(target.type(), method)
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
- 降级配置:在
@FeignClient
增加fallBack配置,fallBack配置类实现client并加入到spring上下文中去,当然也可以实现factory接口
@FeignClient(name = "storage", fallback = StorageClientFallback.class)
protected interface StorageClient{
@GettMapping("/fallback")
String fallback(Integer flag);
}
// 内部类的不用添加Bean上下文注解,如果单独实现需要配置@Component注解
static class StorageClientFallback implements StorageClient{
@Override
public String fallback(Integer flag) {
return "fallback";
}
}
- 如果在默认的5秒钟内出现20次错误的情况下,断路器会打开,所有请求直接熔断
- hystrix command 配置command
源码阅读
Feign源码之@EnableFeignClients
- 加载在启动类的配置给人的第一直觉就是初始化组件用的,没错这里的
@EnableFeignClients
的功能就是加载Feign的定义,具体定义了什么内容呢?-
FeignClientsRegistrar
类实现了ImportBeanDefinitionRegistrar
接口,在方法中注册了Feign的配置以及FeignClient的定义 - 画外音:很多组件都是通过这种方式来加载的,所以平常在组件开发的过程中也可以通过实现
ImportBeanDefinitionRegistrar
接口来实现自定义组件的加载
-
Feign源码之FeignClientFactoryBean
看到XXXFactoryBean对象(画外音:创建spring对象的方式之一,可以想象对象创建的其它方式),可以猜测是通过工厂Bean的方式来创建对象。在Spring上下文初始化的时候,需要加载Client的时候,通过FactoryBean来创建Client对象的
-
大致流程
核心创建方法如下:
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
// 方法和方法处理Handler的映射关系
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 创建InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
// 创建动态代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
你们的点赞和关注是我创作的最大动力,有什么不足和错误的地方欢迎留言,微信搜索关注小二说码