hystrix 断路器实现思路及代码

断路器实现图解

在这里插入图片描述
先实现一个接口,用于测试,断路器

@RestController
public class FishController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("doRpc")
    @MyFish
    public String doRpc(){
        String result = restTemplate.getForObject("http://localhost:8989/abc", String.class);
        return result;
    }
}

这里开发一个注解,用于拦截这个接口

/**
 * 熔断器切面注解
 */
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyFish {

}

构造断路器状态枚举

/**
 * 断路器状态开关
 */
public enum FishStatus {
    CLOSE,
    OPEN,
    HALF_OPEN
    ;
}

根据熔断器的构造思路,需要构建以下的模型

/**
 * 断路器模型
 */
@Data
public class Fish {
    /**
     * 窗口时间
     */
    public static final Integer WINDOW_TIME = 20;
    /**
     * 最大失败次数
     */
    public static final Integer MAX_FAIL_COUNT = 3;

    //断路器自己的状态
    private FishStatus status = FishStatus.CLOSE;

    /**
     * 线程池
     */
    private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
            2,
            Runtime.getRuntime().availableProcessors(),
            30,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(2000),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
    );
    private Object lock = new Object();
    {
        poolExecutor.execute(() -> {
            //定期删除
            //每隔一个时间窗口清零
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(WINDOW_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //如果断路器是开的 不会调用
                if (this.status.equals(FishStatus.CLOSE)){
                    this.currentFailCount.set(0);
                }else {
                    //不要清零
                    synchronized (lock){
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
        });
    }
    /**
     * 当前断路器失败了几次
     */
    private AtomicInteger currentFailCount = new AtomicInteger(0);


    public void addFailCount() {
        int i = currentFailCount.incrementAndGet(); //++i
        if (i >= MAX_FAIL_COUNT){
            //说明失败次数到达阈值
            this.setStatus(FishStatus.OPEN);

            //等待一个时间窗口,让断路器贬称半开
            poolExecutor.execute(() -> {
                try {
                    TimeUnit.SECONDS.sleep(WINDOW_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //修改为半开
                this.setStatus(FishStatus.HALF_OPEN);
                //重置失败次数
                this.currentFailCount.set(0);
            });


        }

    }
}

使用切面的形式,做断路器的模拟


@Component
@Aspect
public class FishAspect {

//    public static final String POINT_CUT = "execution (* com.zhou.controller.FishController.doRpc(..))";

    //因为一个消费者可以调用多个提供者
    /**
     *断路器容器集合
     */
    public static Map<String, Fish> fishMap = new HashMap<>();
    static {
        fishMap.put("order-service",new Fish());
    }
    Random random = new Random();
    /**
     * 这个就类比拦截器
     * 就是判断当前断路器的状态,从而决定是否执行目标方法
     * @param joinPoint
     * @return
     */
    @Around(value = "@annotation(com.zhou.anno.MyFish)")
    public Object fishArount(ProceedingJoinPoint joinPoint) {
        Object result = null;
        //获取当前提供者的断路器
        Fish fish = fishMap.get("order-service");
        FishStatus status = fish.getStatus();
        switch (status) {
            case CLOSE:
                //正常调用 执行目标方法
                try {
                    result = joinPoint.proceed();
                    return result;
                } catch (Throwable throwable) {
                    //调用失败
                    fish.addFailCount();
                    return "备胎";
                }

            case OPEN:
                //不能调用
                return "备胎";
            case HALF_OPEN:
                //少许流量调用
                int i = random.nextInt(5);
                if (i == 1) {
                    //尝试调用
                    //正常调用 执行目标方法
                    try {
                        result = joinPoint.proceed();
                        //成功
                        fish.setStatus(FishStatus.CLOSE);
                        synchronized (fish.getLock()){
                            fish.getLock().notifyAll();
                        }
                        return result;
                    } catch (Throwable throwable) {
                        //调用失败
                        return "备胎";
                    }
                }
            default:
                return "备胎";
        }
    }
}
主要思路
  • 创建一个集合,用来模拟多个服务提供者 fishMap
  • 使用环绕通知,做接口的拦截
    • 先获取到当前提供者的断路器,以及当前的状态
    • 然后对当前的状态进行判断
      • 如果是关闭的,则正常执行,如果执行失败则调用断路器中失败的方法,并执行备用的方法
      • 如果是打开的,则直接执行备用的方法
      • 如果是半开的,则进行少量流量的调用,即设计为随机数0-4,等于1时调用,即20%的几率
        • 若调用成功,则更改断路器状态为 关闭CLOSE,并唤醒定期删除次数方法
        • 若调用失败,则继续执行备用方法
  • 调用失败的方法addFailCount
    • 如果调用失败,则失败次数加1
    • 如果次数达到MAX_FAIL_COUNT,则将断路器状态改为打开OPEN,然后等待一个时间窗口WINDOW_TIME再将断路器的状态改为半开HALF_OPEN,并将次数重置为0,这里使用线程池异步操作
  • 在模型中构造一个代码块,用于做定期的删除,即定期清零。如果断路器是开的 不会调用,只有断路器状态是关的时才会进行调用,如果状态时开或者半开直接阻塞,不进行调用。因为状态改为开时,会在下一个时间窗口将状态改为半开,半开时进行少许流量访问,没有必要记录次数,只有在关闭CLOSE时才需要记录次数,这样节约资源。

gitee地址:https://gitee.com/zql1455890112/demo.git
/springcloud-demo/04-hystrix/03-myfish

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龘龍龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值