需求背景
如果一个服务中有很多涉及需要服务间熔断的地方,就会出现N多下述代码:
1.N个fegnClient接口
@FeignClient(name = "hello-world-service", fallback = HelloWorldFallback.class)
public interface HelloWorldService {
@GetMapping("/hello")
String sayHello();
}
2.N个降级结果类
@Component
public class HelloWorldFallback implements HelloWorldService {
@Override
public String sayHello() {
return "fallback";
}
}
feign调用接口上都要加上fallback降级类,只是想简单方便且不需要关心创建及返回结果,并且可以把hystrix的框架包装在中间件中,屏蔽调用逻辑,让开发者更加关注于业务本身。
方案设计
1.使用注解和切面技术,拦截需要熔断保护的方法
2.继承com.netflix.hystrix.HystrixCommand.class(奈飞熔断器源码),实现自定义的超时熔断处理
代码实现
自定义注解DoHystrix
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoHystrix {
String returnJson() default ""; // 失败结果 JSON
int timeoutValue() default 0; // 超时熔断
}
熔断器的具体实现--HystrixValveImpl.class
public class HystrixValveImpl extends HystrixCommand<Object> implements IValveService {
private ProceedingJoinPoint jp;
private Method method;
private DoHystrix doHystrix;
public HystrixValveImpl() {
/*********************************************************************************************
* 置HystrixCommand的属性
* GroupKey: 该命令属于哪一个组,可以帮助我们更好的组织命令。
* CommandKey: 该命令的名称
* ThreadPoolKey: 该命令所属线程池的名称,同样配置的命令会共享同一线程池,若不配置,会默认使用GroupKey作为线程池名称。
* CommandProperties: 该命令的一些设置,包括断路器的配置,隔离策略,降级设置,以及一些监控指标等。
* ThreadPoolProperties:关于线程池的配置,包括线程池大小,排队队列的大小等
*********************************************************************************************/
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GovernGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GovernKey"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("GovernThreadPool"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(10))
);
}
@Override
public Object access(ProceedingJoinPoint jp, Method method, DoHystrix doHystrix, Object[] args) {
this.jp = jp;
this.method = method;
this.doHystrix = doHystrix;
// 设置熔断超时时间
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GovernGroup"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(doHystrix.timeoutValue()));
return this.execute();
}
@Override
protected Object run() throws Exception {
try {
return jp.proceed();
} catch (Throwable throwable) {
return null;
}
}
@Override
protected Object getFallback() {
return JSON.parseObject(doHystrix.returnJson(), method.getReturnType());
}
}
主要是对HystrixCommand的再次封装,通过继承父类构造函数来配置熔断器启动参数,包括熔断器工厂分组,key,线程隔离策略,线程池的核心线程数等
HystrixCommand.run()方法:返回正确方法调用结果
HystrixCommand.getFallback()方法:返回超时熔断降级结果
切面实现--DoHystrixPoint.class
@Aspect
@Component
public class DoHystrixPoint {
@Pointcut("@annotation(cn.bugstack.middleware.hystrix.annotation.DoHystrix)")
public void aopPoint() {
}
@Around("aopPoint() && @annotation(doGovern)")
public Object doRouter(ProceedingJoinPoint jp, DoHystrix doGovern) throws Throwable {
IValveService valveService = new HystrixValveImpl();
return valveService.access(jp, getMethod(jp), doGovern, jp.getArgs());
}
private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
Signature sig = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
}
}
切面中的逻辑已经在统一白名单中间件 文章中详细梳理过了,需要请移步
测试
在被调用方法上加自定义熔断注解,超时时长设置为500毫秒,当前线程睡一秒
方法调用大于500毫秒:
{"code":"0","info":"调用超500毫秒"}
将线程睡一秒干掉,方法调用小于500毫秒:
{"name":"xxx","age":20,"address":"xxx"}
总结
通过对中间件的设计屏蔽掉底层应用的复杂性,让整个功能服务的业务代码更加纯粹,同时可以让使用此功能的研发不会过多的参与到插件的使用中,把更多的关心放在业务逻辑开发中