【设计模式】JAVA Design Patterns——Circuit Breaker(断路器模式)

news2024/11/17 20:28:47

🔍目的


以这样一种方式处理昂贵的远程服务调用,即单个服务/组件的故障不会导致整个应用程序宕机,我们可以尽快重新连接到服务

🔍解释


真实世界例子

想象一个 Web 应用程序,它同时具有用于获取数据的本地文件/图像和远程服务。 这些远程服务有时可能健康且响应迅速,或者由于各种原因可能在某 个时间点变得缓慢和无响应。因此,如果其中一个远程服务缓慢或未成功响应,我们的应用程序将尝试使用多个线程/进程从远程服务获取响应,很快它们都会挂起(也称为 [线程饥饿]thread starvationopen in new window)导致我们的整个 Web 应用程序崩溃。我们应该能够检测到这种情况并向用户显示适当的消息,以便他/她可以探索不受远程服务故障影响的应用程序的其他部分。 同时,其他正常工作的服务应保持正常运行,不受此故障的影响。

通俗描述

断路器允许优雅地处理失败的远程服务。当我们应用程序的所有部分彼此高度解耦时,它特别有用,一个组件的故障并不意味着其他部分将停止工作。

维基百科

断路器是现代软件开发中使用的一种设计模式。 它用于检测故障并封装防止故障不断重复发生、维护期间、临时外部系统故障或意外系统困难的逻辑。

程序示例

So, how does this all come together? With the above example in mind we will imitate the
functionality in a simple example. A monitoring service mimics the web app and makes both local and remote calls.

在一个简单的例子中模仿这个功能。 监控服务模仿 Web 应用程序并进行本地和远程调用。

最终用户程序

@Slf4j
public class App {

  private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

  /**
   * Program entry point.
   *
   * @param args command line args
   */
  public static void main(String[] args) {

    var serverStartTime = System.nanoTime();

    var delayedService = new DelayedRemoteService(serverStartTime, 5);
    var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
        2000 * 1000 * 1000);

    var quickService = new QuickRemoteService();
    var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
        2000 * 1000 * 1000);

    // 创建一个可以进行本地和远程调用的监控服务对象
    var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
        quickServiceCircuitBreaker);

    // 获取本地资源
    LOGGER.info(monitoringService.localResourceResponse());

    // 从延迟服务中获取响应 2 次,以满足失败阈值
    LOGGER.info(monitoringService.delayedServiceResponse());
    LOGGER.info(monitoringService.delayedServiceResponse());

    // 在超过故障阈值限制后获取延迟服务断路器的当前状态
    // 现在是打开状态
    LOGGER.info(delayedServiceCircuitBreaker.getState());

     // 同时,延迟服务宕机,从健康快速服务获取响应
    LOGGER.info(monitoringService.quickServiceResponse());
    LOGGER.info(quickServiceCircuitBreaker.getState());

    // 等待延迟的服务响应
    try {
      LOGGER.info("Waiting for delayed service to become responsive");
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    // 检查延时断路器的状态,应该是HALF_OPEN
    LOGGER.info(delayedServiceCircuitBreaker.getState());

    // 从延迟服务中获取响应,现在应该是健康的
    LOGGER.info(monitoringService.delayedServiceResponse());
    // 获取成功响应后,它的状态应该是关闭。
    LOGGER.info(delayedServiceCircuitBreaker.getState());
  }
}

监控服务类

public class MonitoringService {

  private final CircuitBreaker delayedService;

  private final CircuitBreaker quickService;

  public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) {
    this.delayedService = delayedService;
    this.quickService = quickService;
  }

  // 假设:本地服务不会失败,无需将其包装在断路器逻辑中
  public String localResourceResponse() {
    return "Local Service is working";
  }

  /**
   * Fetch response from the delayed service (with some simulated startup time).
   *
   * @return response string
   */
  public String delayedServiceResponse() {
    try {
      return this.delayedService.attemptRequest();
    } catch (RemoteServiceException e) {
      return e.getMessage();
    }
  }

  /**
   * Fetches response from a healthy service without any failure.
   *
   * @return response string
   */
  public String quickServiceResponse() {
    try {
      return this.quickService.attemptRequest();
    } catch (RemoteServiceException e) {
      return e.getMessage();
    }
  }
}

直接调用获取本地资源,但它将对远程(昂贵)服务的调用包装在断路器对象中,防止故障如下:

public class DefaultCircuitBreaker implements CircuitBreaker {

    private final long timeout;
    private final long retryTimePeriod;
    private final RemoteService service;
    long lastFailureTime;
    private String lastFailureResponse;
    int failureCount;
    private final int failureThreshold;
    private State state;
    private final long futureTime = 1000 * 1000 * 1000 * 1000;

    /**
     * Constructor to create an instance of Circuit Breaker.
     *
     * @param timeout          Timeout for the API request. Not necessary for this simple example
     * @param failureThreshold Number of failures we receive from the depended service before changing
     *                         state to 'OPEN'
     * @param retryTimePeriod  Time period after which a new request is made to remote service for
     *                         status check.
     */
    DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
                          long retryTimePeriod) {
        this.service = serviceToCall;
        //  我们从关闭状态开始希望一切都是正常的
        this.state = State.CLOSED;
        this.failureThreshold = failureThreshold;
        // API的超时时间.
        // 用于在超过限制时中断对远程资源的调用
        this.timeout = timeout;
        this.retryTimePeriod = retryTimePeriod;
        //An absurd amount of time in future which basically indicates the last failure never happened
        this.lastFailureTime = System.nanoTime() + futureTime;
        this.failureCount = 0;
    }

    // 重置所有
    @Override
    public void recordSuccess() {
        this.failureCount = 0;
        this.lastFailureTime = System.nanoTime() + futureTime;
        this.state = State.CLOSED;
    }

    @Override
    public void recordFailure(String response) {
        failureCount = failureCount + 1;
        this.lastFailureTime = System.nanoTime();
        // Cache the failure response for returning on open state
        this.lastFailureResponse = response;
    }

    // 根据 failureThreshold、failureCount 和 lastFailureTime 评估当前状态。
    protected void evaluateState() {
        if (failureCount >= failureThreshold) { //Then something is wrong with remote service
            if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
                // 我们已经等得够久了,应该尝试检查服务是否已启动
                state = State.HALF_OPEN;
            } else {
                // 服务可能仍会出现故障
                state = State.OPEN;
            }
        } else {
            // 一切正常
            state = State.CLOSED;
        }
    }

    @Override
    public String getState() {
        evaluateState();
        return state.name();
    }

    /**
     * Break the circuit beforehand if it is known service is down Or connect the circuit manually if
     * service comes online before expected.
     *
     * @param state State at which circuit is in
     */
    @Override
    public void setState(State state) {
        this.state = state;
        switch (state) {
            case OPEN -> {
                this.failureCount = failureThreshold;
                this.lastFailureTime = System.nanoTime();
            }
            case HALF_OPEN -> {
                this.failureCount = failureThreshold;
                this.lastFailureTime = System.nanoTime() - retryTimePeriod;
            }
            default -> this.failureCount = 0;
        }
    }

    /**
     * Executes service call.
     *
     * @return Value from the remote resource, stale response or a custom exception
     */
    @Override
    public String attemptRequest() throws RemoteServiceException {
        evaluateState();
        if (state == State.OPEN) {
            // 如果电路处于打开状态,则返回缓存的响应
            return this.lastFailureResponse;
        } else {
            // 如果电路未打开,则发出 API 请求
            try {
                //在实际应用程序中,这将在线程中运行,并且将利用断路器的超时参数来了解服务
                // 是否正在工作。 在这里,我们根据服务器响应本身模拟
                var response = service.call();
                // api 响应正常,重置所有。
                recordSuccess();
                return response;
            } catch (RemoteServiceException ex) {
                recordFailure(ex.getMessage());
                throw ex;
            }
        }
    }
}

我们可以通过上述实现这个有限状态机

1.使用某些参数初始化断路器对象:timeoutfailureThreshold 和 retryTimePeriod,这有助于确定 API 的弹性。

2.最初,我们处于“关闭”状态,没有发生对 API 的远程调用。

3.每次调用成功时,我们都会将状态重置为开始时的状态。

4.如果失败次数超过某个阈值,我们将进入“open”状态,这就像开路一样,阻止远程服务调用,从而节省资源。 (这里,我们从 API 返回名为 stale response 的响应)

5.一旦超过重试超时时间,我们就会进入“半开”状态并再次调用远程服务以检查服务是否正常工作,以便我们可以提供新鲜内容。 失败将其设置回“打开”状态,并在重试超时时间后进行另一次尝试,而成功将其设置为“关闭”状态,以便一切重新开始正常工作

🔍类图


🔍适用场景


在以下情况下使用断路器模式

  • 构建一个容错应用程序,其中某些服务的故障不应导致整个应用程序宕机。
  • 构建一个持续运行(永远在线)的应用程序,这样它的组件就可以在不完全关闭的情况下升级。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1708295.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

车载网络测试实操源码_使用CAPL脚本模拟发送符合协议要求(Counter和CRC)的CAN报文

系列文章目录 车载网络测试实操源码_使用CAPL脚本解析hex、S19、vbf文件 车载网络测试实操源码_使用CAPL脚本对CAN报文的Counter和CRC进行实时监控 车载网络测试实操源码_使用CAPL脚本模拟发送符合协议要求(Counter和CRC)的CAN报文 车载网络测试实操源码_使用CAPL脚本实现安全…

如何在Spring Boot中整合PageHelper实现分页功能

1.前言 在开发web应用程序时,经常会遇到需要对数据库中的数据进行分页查询的情况。为了简化分页查询的实现过程,我们可以利用PageHelper这个优秀的分页插件来实现分页功能。本文将介绍如何在Spring Boot项目中整合PageHelper,并演示如何使用它…

docker和containerd的区别

docker和containerd的区别 1、容器运行时 1.1 容器运行时概念 容器运行时(Container Runtime)是一种负责在操作系统层面创建和管理容器的软件工具或组件。它是容器化技术的核心组件之一,用于在容器内部运行应用程序,并提供隔离…

零基础,想做一名网络安全工程师,该怎么学习?

​ 相比IT类的其它岗位,网络工程师的学习方向是比较明亮的。想要成为网络工程师,华为认证就是最好的学习方法。而网络工程师的从零开始学习就是从华为认证的初级开始学起,也就是HCIA,也就是从最基本的什么是IP地址、什么是交换机这…

IDEA2024创建maven项目

1、new->project 2、创建后展示 3、生成resources文件夹 4、测试--编写一个hello文件

STM32——定时器

一、简介 *定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断 *16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时 *不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入…

Autosar Dcm配置-特定NRC实现方式-基于ETAS软件

文章目录 前言工具配置代码编写总结 前言 项目开发过程中,诊断服务一般客户需求或系统需求都会有特定NRC(一般为NRC22-条件不满足),也就会有特定的条件,需要手动加代码实现。本文介绍ETAS工具中配置的接口及简单实现。 工具配置 对于每一个…

Redis常用命令——Hash篇

前面我们讲述了String的相关操作命令。本篇文章主要讲解Redis中数据结构Hash的相关操作命令。希望会对你有所帮助。 目录 一、Hash哈希 二、命令 HSET HGET HEXISTS HDEL HKEYS HVALS HGETALL HMGET HLEN HSETNX HINCRBY 和 HINCRBYFLOAT 三、小结 🙋‍♂️ 作者&a…

css 渐变色边框

效果图&#xff1a; 代码&#xff1a; <style>:root{--br-radius: 12px;}.list{position: relative;}.list_tle{margin-top: 15px;margin-bottom: 5px;}.item{position: relative;display: inline-flex;} .br1 {padding: 10px 16px;clip-path: inset(0 round 6px);borde…

【C++】挑战与机遇并存的编程语言—前序

C&#xff1a;挑战与机遇并存的编程语言 一、C的难点与挑战二、C的学习方法与策略三、C的实战应用与代码实例 在当今科技飞速发展的时代&#xff0c;编程语言的选择对于每一个开发者来说都至关重要。C&#xff0c;作为一种历史悠久且功能强大的编程语言&#xff0c;一直在软件开…

el-date-picker限制时间选择,不能选择当前日期之后时间

要求&#xff1a;时间选择不能超过当前日期之后的 效果&#xff1a; 结构代码&#xff1a;&#xff1a; <el-form-item label"时间&#xff1a;"><el-date-pickerv-model"time"type"datetimerange"range-separator"至"start…

【算法】重建二叉树并进行后序遍历的Java实现

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

做抖店如何避免被同行内卷?这5点建议,可以解决这个问题

我是王路飞。 都说2024年的抖店不赚钱了&#xff0c;商家太多了&#xff0c;太内卷了&#xff0c;一点都不好做~ 那为什么依然有很多商家在坚持做呢&#xff1f;为什么依然有很多新手入局呢&#xff1f; 无非是抖店确实能带来可观的利润回报罢了。 那如何避免被同行内卷呢&…

Linux-应用编程学习笔记(二、文件I/O、标准I/O)

一、文件I/O基础 文件 I/O 指的是对文件的输入/输出操作&#xff0c;就是对文件的读写操作。Linux 下一切皆文件。 1.1 文件描述符 在 open函数执行成功的情况下&#xff0c; 会返回一个非负整数&#xff0c; 该返回值就是一个文件描述符&#xff08;file descriptor&#x…

前端开发框架Angular

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Angular概述 Angular是由Google开发并维护的一款开源前端开发框架。它最初被设计为用于构建单页面应用&#xff08;SPA&#xff09;&#xff0c;但随着版本的更新和发展&am…

ssm招聘信息管理系统-计算机毕业设计源码78049

摘 要 由于数据库和数据仓库技术的快速发展&#xff0c;招聘客户管理系统建设越来越向模块化、智能化、自我服务和管理科学化的方向发展。招聘客户系统对处理对象和服务对象&#xff0c;自身的系统结构&#xff0c;处理能力&#xff0c;都将适应技术发展的要求发生重大的变化。…

springboot集成nacos

springboot集成nacos 1.版本2. POM依赖3. nacos服务3.1 下载nacos压缩包3.2 启动nacos 4. yaml配置5.Demo5.1 配置中心简单格式获取方式普通方式还可以再启动类上添加注解完成5.2 获取json格式的demo5.2 自动注册根据yaml配置 1.版本 nacos版本:2.3.2 springboot版本&#xff…

OpenHarmony 实战开发——内核对象队列之算法详解

前言 OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09; LiteOS-M 内核是面向 IoT 领域构建的轻量级物联网操作系统内核&#xff0c;具有小体积、低功耗、高性能的特点。在嵌入式领域的开发工作中&#xff0c;无论是自研还是移植系统&#xff0c;均绕不开…

IT行业现状与未来趋势简单分析

你眼中的IT行业现状与未来趋势 随着技术的不断进步&#xff0c;IT行业已成为推动全球经济和社会发展的关键力量。从云计算、大数据、人工智能到物联网、5G通信和区块链&#xff0c;这些技术正在重塑我们的生活和工作方式。你眼中IT行业的现状及未来发展趋势是怎么样的&#xf…

AI视频教程下载:使用ChatGPT进行商务写作

你将学到什么&#xff1f; 学习如何将ChatGPT集成到你的写作过程中&#xff0c;并有效地将其用作商务写作的个人写作助手。 学习如何使用ChatGPT生成想法&#xff0c;提高你的书面沟通的结构、清晰度和连贯性。 你将学习使用ChatGPT的最佳实践&#xff0c;包括如何自定义其设…