当前位置: 首页 > article >正文

服务熔断-熔断器设计

文章目录

  • 服务为什么需要熔断
  • 熔断器设计思想
  • 熔断器代码实现

服务为什么需要熔断

对于服务端采用的保护机制为服务限流。 对于服务调用端是否存在保护机制?
假如要发布一个服务 B,而服务 B 又依赖服务 C,当一个服务 A 来调用服务 B 时,服务 B 的业务逻辑调用服务 C,而这时服务 C 响应超时了,由于服务 B 依赖服务 C,C 超时直接导致 B 的业务逻辑一直等待,而这个时候服务 A 在频繁地调用服务 B,服务 B 就可能会因为堆积大量的请求而导致服务宕机。
在这里插入图片描述
在这里插入图片描述

由此可见,服务 B 调用服务 C,服务 C 执行业务逻辑出现异常时,会影响到服务 B,甚至可能会引起服务 B 宕机。这还只是 A->B->C 的情况,试想一下 A->B->C->D->……呢?在整个调用链中,只要中间有一个服务出现问题,都可能会引起上游的所有服务出现一系列的问题,甚至会引起整个调用链的服务都宕机,这是非常恐怖的。
所以说,在一个服务作为调用端调用另外一个服务时,为了防止被调用的服务出现问题而影响到作为调用端的这个服务,这个服务也需要进行自我保护。而最有效的自我保护方式就是熔断。

熔断器设计思想

熔断状态实现包括三个:闭合、断开、半开,分别对应于正常、故障、检测故障是否已被修复的场景。

  1. 闭合:正常情况,后台会对调用失败次数进行积累,到达一定阈值或比例时则自动启动熔断机制。
  2. 断开:一旦对服务的调用失败次数达到一定阈值时,熔断器就会打开,这时候对服务的调用将直接返回一个预定的错误,而不执行真正的网络调用。同时,熔断器需要设置一个固定的时间间隔,当处理请求达到这个时间间隔时会进入半熔断状态。
  3. 半开:在半开状态下,熔断器会对通过它的部分请求进行处理,如果对这些请求的成功处理数量达到一定比例则认为服务已恢复正常,就会关闭熔断器,反之就会打开熔断器。

熔断设计的一般思路是,在请求失败 N 次后在 X 时间内不再请求,进行熔断;然后再在 X 时间后恢复 M% 的请求,如果 M% 的请求都成功则恢复正常,关闭熔断,否则再熔断 Y 时间,依此循环。在熔断的设计中,根据 Netflix 的开源组件 hystrix 的设计,我们可以仿照以下二个模块:熔断请求判断算法、熔断恢复机制:
熔断请求判断机制算法:根据事先设置的在固定时间内失败的比例来计算。
熔断恢复:对于被熔断的请求,每隔 X 时间允许部分请求通过,若请求都成功则恢复正常。

熔断器代码实现

在这里插入图片描述

package main.version4.v1.client.circuitBreaker;


import java.util.concurrent.atomic.AtomicInteger;

/**
 * 管理熔断器的状态,并根据请求的成功和失败的情况进行状态转移
 */
public class CircuitBreaker {
    // 当前状态
    private CircuitBreakerStatus status = CircuitBreakerStatus.CLOSED;
    private AtomicInteger failureCount = new AtomicInteger(0);
    private AtomicInteger successCount = new AtomicInteger(0);
    private AtomicInteger requestCount = new AtomicInteger(0);

    // 失败次数阈值
    private final int failureThreshold;
    // 半开启-》关闭状态的成功次数的比例
    private final double halfOpenSuccessRate;
    // 恢复时间
    private final long retryTimePeriod;
    // 上一次失败时间
    private long lastFailureTime = 0;
    public CircuitBreaker(int failureThreshold, double halfOpenSuccessRate, long retryTimePeriod){
        this.failureThreshold = failureThreshold;
        this.halfOpenSuccessRate = halfOpenSuccessRate;
        this.retryTimePeriod = retryTimePeriod;
    }

    // 查看当前熔断器是否允许请求通过
    public boolean allowRequest(){
        long currentTime = System.currentTimeMillis();
        switch (status){
            case OPEN:
                // 当前处于打开状态,超过一定时间,进入半开状态
                if(currentTime - lastFailureTime > retryTimePeriod){
                    // 细化锁的粒度,将锁从allowRequest判断中移除
                    synchronized (this){
                        if(status == CircuitBreakerStatus.OPEN){
                            status = CircuitBreakerStatus.HALF_OPEN;
                            resetCounts();
                            return true;
                        }
                    }
                }
                return false;
            case HALF_OPEN:
                // 在半开状态下,熔断器允许请求通过,并根据请求的成功率决定是否恢复到闭合状态或重新进入打开状态。
                requestCount.incrementAndGet();
                return true;
            case CLOSED:
            default:
                return true;
        }
    }

    // 记录成功
    public synchronized void recordSuccess(){
        if(status == CircuitBreakerStatus.HALF_OPEN) {
            // 半开状态下,统计成功的请求数量,当成功请求数量达到阈值后,关闭熔断器
            successCount.incrementAndGet();
            if (successCount.get() >= halfOpenSuccessRate * requestCount.get()) {
                status = CircuitBreakerStatus.CLOSED;
                resetCounts();
            }
        }else if(status == CircuitBreakerStatus.CLOSED){
            // 当熔断器关闭时,不清空数据
        }else{
            resetCounts();
        }
    }
    // 记录失败
    public synchronized void recordFailure(){
        failureCount.incrementAndGet();
        lastFailureTime = System.currentTimeMillis();
        if(status == CircuitBreakerStatus.HALF_OPEN){
            // 在半开状态下,请求再次失败,此时再次打开熔断器
            status = CircuitBreakerStatus.OPEN;
            lastFailureTime = System.currentTimeMillis();
        }else if(failureCount.get() > failureThreshold){
            // 失败次数达到阈值,熔断器进入打开状态,拒绝请求
            status = CircuitBreakerStatus.OPEN;
        }
    }

    // 重置次数
    public void resetCounts(){
        failureCount.set(0);
        successCount.set(0);
        requestCount.set(0);
    }
    // 获取状态
    public CircuitBreakerStatus getStatus(){
        return status;
    }
}

enum CircuitBreakerStatus{
    // 关闭、开启、半开启
    CLOSED, OPEN, HALF_OPEN
}


http://www.kler.cn/a/419215.html

相关文章:

  • 两个生活中的例子反向理解正/反向代理?
  • 王道考研编程题总结
  • Fortran mpi在Linux的安装
  • Electron builder打包配置
  • 【linux学习指南】Linux进程信号产生(三) 硬件异常除零出错?野指针异常?core文件
  • .net6.0 mvc 传递 model 实体参数(无法对 null 引用执行运行时绑定)
  • 【JavaEE】JavaEE、web 开发、框架(Spring) 、Maven
  • SAP-ABAP开发-第二代增强示例
  • Day1——GitHub项目共同开发
  • Vue进阶之单组件开发与组件通信
  • Unity中实现A*寻路算法参考
  • Scala 的知识点
  • Linux中pcap_loop()函数
  • 算法的复杂度
  • LeetCode:19.删除链表倒数第N个节点
  • 解决jupyter notebook 新建或打开.ipynb 报500 : Internal Server Error(涉及jinja2兼容性问题)
  • 蓝桥-希尔排序模板题
  • 【Java基础面试题004】封装、继承、重载、多态、接口和抽象类是什么?
  • 《JavaEat:探索 Java 在美食世界的奇妙之旅》
  • 无星的微前端之旅(四)——qiankun线上服务代理到本地
  • 后端-一对一的数据封装的两种写法对比
  • Android 图形系统之六:BufferQueue
  • 信息网络安全考试gjdw
  • 网络安全运维——高级 题库一 50题
  • vue3 + vite + antdv 项目中自定义图标
  • 华为OD机试真题---幼儿园篮球游戏