FeignClient Hystrix超时重试降级讲了Hystrix和feign各自的超时,重试,降级策略。然后Hystrix其实已经不再维护了,社区推荐Resilience4j,阿里有一个开源的sentinel也可以做到熔断限流等功能。网上有一个表格图片,对比了三者的不同。
我自己在实际使用中的情况是,服务间调用使用Feign,没有用其他RPC框架,然后想做熔断策略,又不想用Hystrix,毕竟不维护了,所以在考虑Resilience4j 和sentinel。然后对比了之后发现sentinel可以无缝切换Hystrix+Feign。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled", matchIfMissing = true)
public Feign.Builder sentinelHystrixBuilder() {
return SentinelFeign.builder();
}
但是我们进入SentinelFeign里面会发现,这种配置只实现了基础的fallback,如果想做到限流,熔断,还需要配合其dashboard,但是现实情况是,我们的熔断并不怎么变化,只是一种以防万一,不需要动态配置。那么虽然dashboard很轻量,还是有点不合适。
注:这里说的sentinel只能用dashboard做熔断,限流配置是因为使用SentinelFeign,实际上的sentinel是可以使用代码写死规则的
然后考虑Resilience4j,这个网上搜了一下,没啥实际使用的感觉,至少在我这个实用主义来看,那些demo代码都用不上,然后看了github,有个Resilience4j-feign,然后我就试用了一下。
那么果然可以在代码里配置熔断的策略,可以配置
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Resilience4jFeign.Builder feignResilience4jBuilder() {
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName");
RateLimiter rateLimiter = RateLimiter.ofDefaults("backendName");
FeignDecorators decorators = FeignDecorators.builder()
.withRateLimiter(rateLimiter)
.withCircuitBreaker(circuitBreaker)
.build();
return Resilience4jFeign.builder(decorators);
}
那么就想更进一步,能不能利用上次FeignClient Hystrix超时重试降级中的MyHystrixAnno 注解,增加一些自定义的配置呢?至少git上官方代码不行了。然后就对比学习了一下HystrixFeign,SentinelFeign,Resilience4jFeign,发现了一些共同点,然后自己改造了一波,记录如下。
另外:Resilience4jFeign里很多类的访问级别是包内,或者是final class的,所以我改造的时候只能copy出来改造。
使用Resilience4j-feign需要添加如下maven配置
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-feign</artifactId>
<version>1.1.0</version>
</dependency>
1、从FeignClientFactoryBean#feign方法来看,feign的一些设置参数可以通过Builder来设置,这一点可以从以上三个框架里看出来
HystrixFeign
public static final class Builder extends Feign.Builder
SentinelFeign
public static final class Builder extends feign.Feign.Builder implements ApplicationContextAware
Resilience4jFeign
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public ZpzengResilience4jFeign.Builder feignResilience4jBuilder() {
return ZpzengResilience4jFeign.builder();
}
public static final class Builder extends Feign.Builder
ZpzengResilience4jFeign
public class ZpzengResilience4jFeign{
public static ZpzengResilience4jFeign.Builder builder() {
return new ZpzengResilience4jFeign.Builder();
}
public static final class Builder extends Feign.Builder implements ApplicationContextAware {
private ZpzengCircuitBreakerFactory circuitBreakerFactory = new ZpzengCircuitBreakerFactory.Default();
private ApplicationContext applicationContext;
private FeignContext feignContext;
/**
* Will throw an {@link UnsupportedOperationException} exception.
*/
@Override
public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public Feign build() {
super.invocationHandlerFactory(
(target, dispatch) -> new ZpzengDecoratorInvocationHandler(target, dispatch, circuitBreakerFactory,feignContext));
return super.build();
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
this.feignContext = (FeignContext)this.applicationContext.getBean(FeignContext.class);
}
}
}
2、有一个实现InvocationHandler接口的调用处理器
Hystrix:HystrixInvocationHandler
Resilience4j:DecoratorInvocationHandler
sentinel:
ZpzengResilience4jFeign:ZpzengDecoratorInvocationHandler主要是copy了Resilience4j:DecoratorInvocationHandler,修改了其中的构造方法,以及decorateMethodHandlers方法。
decorateMethodHandlers方法和HystrixInvocationHandler中的toSetters方法有异曲同工之妙,是通过类target,方法method来获取不同的配置。而sentinel就没有这个,所以除了下面的ZpzengDecoratorInvocationHandler,还仿照Hystrix的SetterFactory写了ZpzengCircuitBreakerFactory
public class ZpzengDecoratorInvocationHandler implements InvocationHandler {
private final Target<?> target;
private final Map<Method, CheckedFunction1<Object[], Object>> decoratedDispatch;
public ZpzengDecoratorInvocationHandler(Target<?> target,
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
ZpzengCircuitBreakerFactory circuitBreakerFactory,
FeignContext feignContext) {
this.target = checkNotNull(target, "target");
checkNotNull(dispatch, "dispatch");
this.decoratedDispatch = decorateMethodHandlers(dispatch, circuitBreakerFactory, target,feignContext);
}
private Map<Method, CheckedFunction1<Object[], Object>> decorateMethodHandlers(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
ZpzengCircuitBreakerFactory circuitBreakerFactory, Target<?> target,
FeignContext feignContext) {
final Map<Method, CheckedFunction1<Object[], Object>> map = new HashMap<>();
for (final Map.Entry<Method, InvocationHandlerFactory.MethodHandler> entry : dispatch.entrySet()) {
final Method method = entry.getKey();
final InvocationHandlerFactory.MethodHandler methodHandler = entry.getValue();
map.put(method, circuitBreakerFactory.create(target,method,feignContext).decorate(methodHandler::invoke, method, methodHandler, target));
}
return map;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
switch (method.getName()) {
case "equals":
return equals(args.length > 0 ? args[0] : null);
case "hashCode":
return hashCode();
case "toString":
return toString();
default:
break;
}
return decoratedDispatch.get(method).apply(args);
}
@Override
public boolean equals(Object obj) {
Object compareTo = obj;
if (compareTo == null) {
return false;
}
if (Proxy.isProxyClass(compareTo.getClass())) {
compareTo = Proxy.getInvocationHandler(compareTo);
}
if (compareTo instanceof ZpzengDecoratorInvocationHandler) {
final ZpzengDecoratorInvocationHandler other = (ZpzengDecoratorInvocationHandler) compareTo;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
}
值得注意的是,我这里使用FeignContext是为了获取fallback,如果不需要fallback的话就不用了;那么如果不配置fallback,触发了熔断的时候会报错的,说该方法调用处于OPEN状态,不会和Feign共用一个fallback,这点和Hystrix不一样;同时这里处理可以配置熔断,还可以配置限流策略等等。
public interface ZpzengCircuitBreakerFactory {
FeignDecorator create(Target<?> target,Method method, FeignContext feignContext);
final class Default implements ZpzengCircuitBreakerFactory {
@Override
public FeignDecorator create(Target<?> target,Method method, FeignContext feignContext) {
String name = target.type().getName();
//TODO 从MyHystrixAnno 中获取配置多种参数
//MyHystrixAnno hystrix = target.type().getAnnotation(SubaoHystrix.class);
MyHystrixAnno hystrix = method.getAnnotation(SubaoHystrix.class);
CircuitBreakerConfig circuitBreakerConfig = new CircuitBreakerConfig.Builder()
.failureRateThreshold(50)//熔断器关闭状态和半开状态使用的同一个失败率阈值
.slidingWindow(5,2, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.enableAutomaticTransitionFromOpenToHalfOpen()//如果置为true,当等待时间结束会自动由打开变为半开,若置为false,则需要一个请求进入来触发熔断器状态转换
.build();
//获取fallback
FeignClient client = target.type().getAnnotation(FeignClient.class);
return FeignDecorators.builder()
.withCircuitBreaker(CircuitBreaker.of(name, circuitBreakerConfig))
.withFallback(feignContext.getInstance(name,client.fallback()))
.build();
}
}
}
3、然后我们来梳理一下整个执行流程。
@FunctionalInterface
public interface FeignDecorator {
CheckedFunction1<Object[], Object> decorate(CheckedFunction1<Object[], Object> invocationCall, Method method, MethodHandler methodHandler,
Target<?> target);
}
public Builder withCircuitBreaker(CircuitBreaker circuitBreaker) {
decorators.add((fn, m, mh, t) -> CircuitBreaker.decorateCheckedFunction(circuitBreaker, fn));
return this;
}
private Map<Method, CheckedFunction1<Object[], Object>> decorateMethodHandlers(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
SubaoCircuitBreakerFactory circuitBreakerFactory, Target<?> target,
FeignContext feignContext) {
final Map<Method, CheckedFunction1<Object[], Object>> map = new HashMap<>();
for (final Map.Entry<Method, InvocationHandlerFactory.MethodHandler> entry : dispatch.entrySet()) {
final Method method = entry.getKey();
final InvocationHandlerFactory.MethodHandler methodHandler = entry.getValue();
//circuitBreakerFactory#create方法创建了不同的FeignDecorators
//decoreate方法是循环执行FeignDecorators中的decorator,
map.put(method, circuitBreakerFactory.create(target,feignContext).decorate(methodHandler::invoke, method, methodHandler, target));
}
return map;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
//获取FeignDecorators
return decoratedDispatch.get(method).apply(args);
}
4、来测试一把,可以看到前两次请求有MonitorInterceptor日志,因为我在Service上加了aop拦截,后面两次直接走到fallback中,说明请求被拦截下来,没有走Service。
5、以上都是在resilience4j-feign的基础上弄的,支持的功能也只是熔断,限流,Fallback,那如果要去实现其他组件的功能,比如限时timelimiter,需要额外新加maven,然后copy一堆代码出来,既然如此可以fork一下这个分支,在自己的分支上去做改造。改造点除了上面罗列的,还有就是FeignDecorators,增加timerlimiter包装成Decorate的方法,类似于
public Builder withCircuitBreaker(CircuitBreaker circuitBreaker) {
decorators.add((fn, m, mh, t) -> CircuitBreaker.decorateCheckedFunction(circuitBreaker, fn));
return this;
}