SpringBoot中间件—封装超时熔断器

需求背景

如果一个服务中有很多涉及需要服务间熔断的地方,就会出现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的框架包装在中间件中,屏蔽调用逻辑,让开发者更加关注于业务本身。

方案设计

SpringBoot中间件—封装超时熔断器_第1张图片

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 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毫秒,当前线程睡一秒
SpringBoot中间件—封装超时熔断器_第2张图片

 方法调用大于500毫秒:
 

{"code":"0","info":"调用超500毫秒"}

将线程睡一秒干掉,方法调用小于500毫秒:
 

{"name":"xxx","age":20,"address":"xxx"}

总结

通过对中间件的设计屏蔽掉底层应用的复杂性,让整个功能服务的业务代码更加纯粹,同时可以让使用此功能的研发不会过多的参与到插件的使用中,把更多的关心放在业务逻辑开发中

你可能感兴趣的:(SpringBoot中间件,spring,boot,后端,java,中间件)