转载自  线上防雪崩利器——熔断器设计原理与实现

本文来自作者投稿,作者林湾村龙猫,这是一篇他根据工作中遇到的问题总结出的最佳实践。

上周六,我负责的业务在凌晨00-04点的支付全部失败了。

结果一查,MD,晚上银行维护,下游支付系统没有挂维护公告,在此期间一直请求维护中的银行,当然所有返回就是失败了,有种欲哭无泪的感觉,锅让业务来背。

为了杜绝在此出现这种大面积批量的支付失败情况发生,保障系统的健壮性。我需要个在集中性异常的时候可以终止请求,当服务恢复,恢复请求。

我想了一些方式,最后,觉得熔断器比较适合干这种事情。

状态模式

在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

我们已一个开关为例

/*** User: Rudy Tan* Date: 2018/9/22*/public class Main{public static void main(String[] args){Context context = new Context();context.state = new CloseState();context.switchState();context.switchState();context.switchState();context.switchState();context.switchState();}
}/*** 状态的抽象*/
interface State{void switchState(Context context);
}/*** 状态上下文*/
class Context{public State state;void switchState(){state.switchState(this);}
}/*** 开状态**/
class OpenState implements State{public void switchState(Context context) {System.out.println("当前状态:开");context.state = new CloseState();}
}/*** 关状态**/
class CloseState implements State{public void switchState(Context context) {System.out.println("当前状态:关");context.state = new OpenState();}
}

在每一种状态下,context不必关心每一种状态下的行为。交给每一种状态自己处理。

熔断器基本原理

熔断器是当依赖的服务已经出现故障时,为了保证自身服务的正常运行不再访问依赖的服务,防止雪崩效应

熔断器本身就是一个状态机。

  1. 关闭状态:熔断器的初始化状态,该状态下允许请求通过。当失败超过阀值,转入打开状态

  2. 打开状态:熔断状态,该状态下不允许请求通过,当进入该状态经过一段时间,进入半开状态

  3. 半开状态:在半开状态期间,允许部分请求通过,在半开期间,观察失败状态是否超过阀值。如果没有超过进入关闭状态,如果超过了进入打开状态。如此往复。

之前,查了一些资料,网上所有的资料几乎都是针对Hystrix的。这个只是针对分布式系统的接口请求,并不能运用于我们的系统中,因此这种情况下,根据原理自己实现了一个基本的分布式熔断器,数值与计数器存放在redis中,因为redis的操作客户端不一样,我就以本地熔断器为例,讲解熔断器实现。

希望我的文章能对于理解熔断器,以及需要熔断器的人有所帮助。

简单的本地熔断器实现

一个基本的本地熔断器。

对外暴露接口

熔断器对外暴露接口

/*** 熔断器接口*/
public interface CircuitBreaker {/*** 重置熔断器*/void reset();/*** 是否允许通过熔断器*/boolean canPassCheck();/*** 统计失败次数*/void countFailNum();
}

熔断器状态对外暴露接口

/*** 熔断器状态*/
public interface CBState {/*** 获取当前状态名称*/String getStateName();/*** 检查以及校验当前状态是否需要扭转*/void checkAndSwitchState(AbstractCircuitBreaker cb);/*** 是否允许通过熔断器*/boolean canPassCheck(AbstractCircuitBreaker cb);/*** 统计失败次数*/void countFailNum(AbstractCircuitBreaker cb);
}

三种状态

关闭状态实现:

package com.hirudy.cb.state;import com.hirudy.cb.cb.AbstractCircuitBreaker;
import java.util.concurrent.atomic.AtomicInteger;/*** User: Rudy Tan* Date: 2018/9/21** 熔断器-关闭状态*/
public class CloseCBState implements CBState {/*** 进入当前状态的初始化时间*/private long stateTime = System.currentTimeMillis();/*** 关闭状态,失败计数器,以及失败计数器初始化时间*/private AtomicInteger failNum = new AtomicInteger(0);private long failNumClearTime = System.currentTimeMillis();public String getStateName() {// 获取当前状态名称return this.getClass().getSimpleName();}public void checkAndSwitchState(AbstractCircuitBreaker cb) {// 阀值判断,如果失败到达阀值,切换状态到打开状态long maxFailNum = Long.valueOf(cb.thresholdFailRateForClose.split("/")[0]);if (failNum.get() >= maxFailNum){cb.setState(new OpenCBState());}}public boolean canPassCheck(AbstractCircuitBreaker cb) {// 关闭状态,请求都应该允许通过return true;}public void countFailNum(AbstractCircuitBreaker cb) {// 检查计数器是否过期了,否则重新计数long period = Long.valueOf(cb.thresholdFailRateForClose.split("/")[1]) * 1000;long now = System.currentTimeMillis();if (failNumClearTime + period <= now){failNum.set(0);}// 失败计数failNum.incrementAndGet();// 检查是否切换状态checkAndSwitchState(cb);}
}

打开状态

package com.hirudy.cb.state;import com.hirudy.cb.cb.AbstractCircuitBreaker;/*** User: Rudy Tan* Date: 2018/9/21** 熔断器-打开状态*/
public class OpenCBState implements CBState {/*** 进入当前状态的初始化时间*/private long stateTime = System.currentTimeMillis();public String getStateName() {// 获取当前状态名称return this.getClass().getSimpleName();}public void checkAndSwitchState(AbstractCircuitBreaker cb) {// 打开状态,检查等待时间是否已到,如果到了就切换到半开状态long now = System.currentTimeMillis();long idleTime = cb.thresholdIdleTimeForOpen * 1000L;if (stateTime + idleTime <= now){cb.setState(new HalfOpenCBState());}}public boolean canPassCheck(AbstractCircuitBreaker cb) {// 检测状态checkAndSwitchState(cb);return false;}public void countFailNum(AbstractCircuitBreaker cb) {// nothing}
}

半开状态

package com.hirudy.cb.state;import com.hirudy.cb.cb.AbstractCircuitBreaker;import java.util.concurrent.atomic.AtomicInteger;/*** User: Rudy Tan* Date: 2018/9/21** 熔断器-半开状态*/
public class HalfOpenCBState implements CBState {/*** 进入当前状态的初始化时间*/private long stateTime = System.currentTimeMillis();/*** 半开状态,失败计数器*/private AtomicInteger failNum = new AtomicInteger(0);/*** 半开状态,允许通过的计数器*/private AtomicInteger passNum = new AtomicInteger(0);public String getStateName() {// 获取当前状态名称return this.getClass().getSimpleName();}public void checkAndSwitchState(AbstractCircuitBreaker cb) {// 判断半开时间是否结束long idleTime = Long.valueOf(cb.thresholdPassRateForHalfOpen.split("/")[1]) * 1000L;long now = System.currentTimeMillis();if (stateTime + idleTime <= now){// 如果半开状态已结束,失败次数是否超过了阀值int maxFailNum = cb.thresholdFailNumForHalfOpen;if (failNum.get() >= maxFailNum){// 失败超过阀值,认为服务没有恢复,重新进入熔断打开状态cb.setState(new OpenCBState());}else {// 没超过,认为服务恢复,进入熔断关闭状态cb.setState(new CloseCBState());}}}public boolean canPassCheck(AbstractCircuitBreaker cb) {// 检查是否切换状态checkAndSwitchState(cb);// 超过了阀值,不再放量int maxPassNum = Integer.valueOf(cb.thresholdPassRateForHalfOpen.split("/")[0]);if (passNum.get() > maxPassNum){return false;}// 检测是否超过了阀值if (passNum.incrementAndGet() <= maxPassNum){return true;}return false;}public void countFailNum(AbstractCircuitBreaker cb) {// 失败计数failNum.incrementAndGet();// 检查是否切换状态checkAndSwitchState(cb);}
}

熔断器

抽象熔断器

package com.hirudy.cb.cb;import com.hirudy.cb.state.CBState;
import com.hirudy.cb.state.CloseCBState;/*** User: Rudy Tan* Date: 2018/9/21** 基础熔断器*/
public abstract class AbstractCircuitBreaker implements CircuitBreaker {/*** 熔断器当前状态*/private volatile CBState state = new CloseCBState();/*** 在熔断器关闭的情况下,在多少秒内失败多少次进入,熔断打开状态(默认10分钟内,失败10次进入打开状态)*/public String thresholdFailRateForClose = "10/600";/*** 在熔断器打开的情况下,熔断多少秒进入半开状态,(默认熔断30分钟)*/public int thresholdIdleTimeForOpen = 1800;/*** 在熔断器半开的情况下, 在多少秒内放多少次请求,去试探(默认10分钟内,放10次请求)*/public String thresholdPassRateForHalfOpen = "10/600";/*** 在熔断器半开的情况下, 试探期间,如果有超过多少次失败的,重新进入熔断打开状态,否者进入熔断关闭状态。*/public int thresholdFailNumForHalfOpen = 1;public CBState getState() {return state;}public void setState(CBState state) {// 当前状态不能切换为当前状态CBState currentState = getState();if (currentState.getStateName().equals(state.getStateName())){return;}// 多线程环境加锁synchronized (this){// 二次判断currentState = getState();if (currentState.getStateName().equals(state.getStateName())){return;}// 更新状态this.state = state;System.out.println("熔断器状态转移:" + currentState.getStateName() + "->" + state.getStateName());}}
}

本地熔断器

package com.hirudy.cb.cb;import com.hirudy.cb.state.CloseCBState;/*** User: Rudy Tan* Date: 2018/9/22** 本地熔断器(把它当成了工厂了)*/
public class LocalCircuitBreaker extends AbstractCircuitBreaker {public LocalCircuitBreaker(String failRateForClose,int idleTimeForOpen,String passRateForHalfOpen, int failNumForHalfOpen){this.thresholdFailRateForClose = failRateForClose;this.thresholdIdleTimeForOpen = idleTimeForOpen;this.thresholdPassRateForHalfOpen = passRateForHalfOpen;this.thresholdFailNumForHalfOpen = failNumForHalfOpen;}public void reset() {this.setState(new CloseCBState());}public boolean canPassCheck() {return getState().canPassCheck(this);}public void countFailNum() {getState().countFailNum(this);}
}

测试例子

import com.hirudy.cb.cb.CircuitBreaker;
import com.hirudy.cb.cb.LocalCircuitBreaker;import java.util.Random;
import java.util.concurrent.CountDownLatch;/*** User: Rudy Tan* Date: 2018/8/27*/
public class App {public static void main(String[] args) throws InterruptedException {final int maxNum = 200;final CountDownLatch countDownLatch = new CountDownLatch(maxNum);final CircuitBreaker circuitBreaker = new LocalCircuitBreaker("5/20", 10, "5/10", 2);for (int i=0; i < maxNum; i++){new Thread(new Runnable() {public void run() {// 模拟随机请求try {Thread.sleep(new Random().nextInt(20) * 1000);} catch (InterruptedException e) {e.printStackTrace();}try{// 过熔断器if (circuitBreaker.canPassCheck()){// do somethingSystem.out.println("正常业务逻辑操作");// 模拟后期的服务恢复状态if (countDownLatch.getCount() >= maxNum/2){// 模拟随机失败if (new Random().nextInt(2) == 1){throw new Exception("mock error");}}} else {System.out.println("拦截业务逻辑操作");}}catch (Exception e){System.out.println("业务执行失败了");// 熔断器计数器circuitBreaker.countFailNum();}countDownLatch.countDown();}}).start();// 模拟随机请求try {Thread.sleep(new Random().nextInt(5) * 100);} catch (InterruptedException e) {e.printStackTrace();}}countDownLatch.await();System.out.println("end");}
}

结果

线上防雪崩利器——熔断器设计原理与实现相关推荐

  1. Java熔断器比较_线上防雪崩利器——熔断器设计原理与实现

    前言 这是一篇根据工作中遇到的问题总结出的最佳实践. 上周六,我负责的业务在凌晨00-04点的支付全部失败了. 结果一查,MD,晚上银行维护,下游支付系统没有挂维护公告,在此期间一直请求维护中的银行, ...

  2. 线上宠物销售系统的设计与实现

    摘要 计算机网络如果结合使用信息管理系统,能够提高管理员管理的效率,改善服务质量.优秀的线上宠物销售系统能够更有效管理宠物销售业务规范,帮助管理者更加有效管理宠物销售,可以帮助提高克服人工管理带来的错 ...

  3. HTML网页设计基础期末作业——仿Coco线上订奶茶饮料网站设计与实现6个页面(HTML+CSS+JavaScript)...

    HTML网页设计基础期末作业--仿Coco线上订奶茶饮料网站设计与实现 6个页面(HTML+CSS+JavaScript) 临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?HTM ...

  4. Springboot毕设项目线上售楼系统的设计与实现 fm258java+VUE+Mybatis+Maven+Mysql+sprnig)

    Springboot毕设项目线上售楼系统的设计与实现  fm258java+VUE+Mybatis+Maven+Mysql+sprnig) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 ...

  5. iOS线上防Crash处理并上传未发生的崩溃日志,降低线上APP崩溃率

    线上APP的崩溃率一直是衡量APP用户体验的重要条件之一,所以,我们很有必要做一些安全防护,让APP尽可能少的产生Crash,提高用户体验.在以前的项目中零零散散做过一些防护,这次专门为平台封装了一个 ...

  6. ui设计线上培训怎么样?ui设计线上与线下的区别?

    ui设计线上培训怎么样?这是很多想学ui设计人群关心的问题,现在随着ui设计的大热,随之而来的就是各种参差不齐的培训机构,那当然在选择机构的时候也要多去比较一下再选择.我18年学习的时候,就是去听了很 ...

  7. 基于 Vue 的学生社团线上管理平台开发与设计

    0 引言 近年来, 各高校为丰富学生的校园生活. 培养学生的个性, 社团与社团人数增加迅速, 需要处理的各类信息也层出不穷, 传统的管理方式已经不利于快捷地处理这些问题. 因此管理不便. 信息错综复杂 ...

  8. 沐歌保健院线上预约按摩系统的设计与实现(论文+源码)_kaic

    目    录 摘  要 第一章  绪论 1.1项目研究的背景 1.2开发意义 1.3项目研究现状及内容 1.4论文结构 第二章  开发技术介绍 2.1JSP技术 2.2Eclipse环境配置 2.3M ...

  9. 基于Django的B2C线上电子产品销售平台设计与实现

    近几年来,网络信息技术日新月异,整个网络环境相较于前几年有了深刻的变革,这些网络信息相关的技术不仅让我们更加实质性地享受到互联网带给我们的便利.让互联网更加泛化和深化,同时也让我们和互联网相关的开发人 ...

最新文章

  1. Present ViewController详解
  2. 一文应用 AOP | 最全选型考量 + 边剖析经典开源库边实践,美滋滋
  3. Android菜鸟的成长笔记(13)——异步任务(Async Task)
  4. 用Java编写模仿的太阳系(九星行旋转)--原创
  5. 交换机组最常见的8大故障及解决方法
  6. Visual Studio Team System 2008 Team Suite 简体中文正式版
  7. ABP入门系列(11)——编写单元测试
  8. LeetCode 79. 单词搜索(回溯DFS)
  9. 为什么envi镶嵌老是出错_10个数学考试老出错的根源和解决办法,你值得拥有
  10. pyqt5 实现右键自定义_Python界面(GUI)编程PyQt5事件和信号
  11. 【软件测试】测试用例详解
  12. 代码走查和代码审查_代码审查是个好主意的其他原因
  13. 永洪BI开发——文本参数日期格式
  14. php无限分类算法,php递归算法 php递归函数无限级分类
  15. 创建新环境出现报错Collecting package metadata (current_repodata.json): failed.问题成功解决。
  16. vue3 provide和 reject
  17. VC++通过查看ReactOS开源代码,解决完整路径dll加载失败问题(附源码)
  18. 三维地图打造数、实融合底座
  19. [个人思考] 所思所想
  20. JAVA 给图片添加文字水印

热门文章

  1. python函数体中可以不写返回值语句_python让函数不返回结果的方法
  2. leetcode714.买卖股票的
  3. 计算机软件在矿井地质中的应用,(完整版)遥感导论知识点整理(梅安新版)
  4. LeetCode 04检查平衡性-简单
  5. C++实现链式存储二叉树
  6. python比较三个数_python经典练习题(三)
  7. node.js安装部署
  8. django mysql connector,MySQL Connector / python在Django中不起作用
  9. Eclipse下Tomcat服务器配置和使用
  10. 主席树有关的一些题目(持续更新)