需求背景
如果一个服务中有很多涉及需要服务间熔断的地方,就会出现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 {@Overridepublic 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 ""; // 失败结果 JSONint 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)));}@Overridepublic 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();}@Overrideprotected Object run() throws Exception {try {return jp.proceed();} catch (Throwable throwable) {return null;}}@Overrideprotected 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"}
总结
通过对中间件的设计屏蔽掉底层应用的复杂性,让整个功能服务的业务代码更加纯粹,同时可以让使用此功能的研发不会过多的参与到插件的使用中,把更多的关心放在业务逻辑开发中