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

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

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

上周六,我负责的业务在凌晨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");}
}

结果

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

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

相关文章

ASP.NET Core 菜鸟之路:从Startup.cs说起

1.前言 本文主要是以Visual Studio 2017 默认的 WebApi 模板作为基架&#xff0c;基于Asp .Net Core 1.0&#xff0c;本文面向的是初学者&#xff0c;如果你有 ASP.NET Core 相关实践经验&#xff0c;欢迎在评论区补充。与早期版本的 ASP.NET 对比&#xff0c;最显著的变化之一…

el表达式与jstl的用法

课上顺便整理了下java中的El表达式和jstl的用法&#xff0c;下面以举例的方式来阐述各个标签的作用&#xff1a;一、 使用el表达式将Map集合中的数据显示出来&#xff1a;先给Map集合里面放一些数据库&#xff0c;通过EL表达式显示在页面中&#xff1a;<%Map names new Has…

业务太复杂?教你如何降低软件的复杂性

转载自 业务太复杂&#xff1f;教你如何降低软件的复杂性 John Ousterhout 是斯坦福大学计算机系教授&#xff0c;也是 Tcl 语言的创造者。 今年四月&#xff0c;他出版了一本新书《软件设计的哲学》&#xff08;A Philosophy of Software Design&#xff09;。这是课程讲稿…

[翻译]在 .NET Core 中的并发编程

原文地址:http://www.dotnetcurry.com/dotnet/1360/concurrent-programming-dotnet-core 今天我们购买的每台电脑都有一个多核心的 CPU&#xff0c;允许它并行执行多个指令。操作系统通过将进程调度到不同的内核来发挥这个结构的优点。然而&#xff0c;还可以通过异步 I/O 操作…

JS中函数和变量声明的提升

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>01_变量提升与函数提升</title> </head> <body> <!-- 1. 变量声明提升* 通过var定义(声明)的变量, 在定义语句之前就可以访问到*…

记录学生的日常

最近比较忙&#xff0c;都没时间更新公众号了&#xff0c;粉丝每天都在减&#xff0c;哈哈哈。最近19级的学生们在做网页设计静态网页项目&#xff0c;从上周五到现在&#xff0c;班内除了两个小组比较慢之外&#xff0c;其余的进度都还可以&#xff0c;从做项目中就可以看出来…

Mono新突破:CentOS 7.2下安装Mono 5.0

微软Build2017大会期间.NET领域的.NET core之外&#xff0c;就是Visual Studio For Mac&#xff0c;大家都知道Visual Studio For Mac 是基于Mono运行的&#xff0c;Mono 5.0也是闪亮登场&#xff0c;Mono 5.0是一个非常重要的里程碑版本&#xff0c;支持Windows 64位部署&…

搞定 JVM 垃圾回收就是这么简单

转载自 搞定 JVM 垃圾回收就是这么简单 JVM的垃圾回收机制是Java中比较重要的知识点&#xff0c;也是面试官常考的问题&#xff0c;本文主要围绕以下面试题来讲解JVM的垃圾回收机制。 问题答案在文中都有提到 如何判断对象是否死亡&#xff08;两种方法&#xff09;。 简单…

一份感动到哭的成绩单……

今天对班级内进行了测试&#xff0c;这是自实行周周考以来&#xff0c;第三次测试了&#xff0c;还记得第一次的测试&#xff0c;几分的&#xff0c;十几分的&#xff0c;几十分的五花八门&#xff0c;成绩可算是惨不忍睹啊。第二次测试&#xff0c;开发的进步了好多&#xff0…

深刻理解:C#中的委托、事件

C#中的事件还真是有点绕啊&#xff0c;以前用JavaScript的我&#xff0c;理解起来还真是废了好大劲&#xff01;刚开始还真有点想不明白为什么这么绕&#xff0c;想想和JS的区别&#xff0c;最后终于恍然大悟&#xff01; C#中事件绕的根本原因&#xff1a; C#的方法&#xff…

mybatis中,collection配置后查询只显示一条记录

描述一下问题&#xff1a; 已知有两个表&#xff0c;一个是user表&#xff0c;一个是address,一&#xff08;user&#xff09;对多(address)的关系&#xff0c;在user的实体类里面写属性&#xff1a; private List<Address> addressList new ArrayList<Address>(…

Java中的List你真的会用吗

转载自 Java中的List你真的会用吗 List是Java中比较常用的集合类&#xff0c;关于List接口有很多实现类&#xff0c;本文就来简单介绍下其中几个重点的实现ArrayList、LinkedList和Vector之间的关系和区别。 List List 是一个接口&#xff0c;它继承于Collection的接口。它…

Android 全局字体设置 例如楷体

1、在res下新建资源文件目录font&#xff0c;把字体文件拷贝到font文件夹中 2、在AndroidManifest.xml中的application节点下&#xff0c;设置全局style&#xff0c;引入字体文件 <item name"android:fontFamily">font/pingfang_sc_regular</item>或者

.Net Core中使用ref和Spanamp;lt;Tamp;gt;提高程序性能

一、前言 其实说到ref&#xff0c;很多同学对它已经有所了解&#xff0c;ref是C# 7.0的一个语言特性&#xff0c;它为开发人员提供了返回本地变量引用和值引用的机制。Span 也是建立在ref语法基础上的一个复杂的数据类型&#xff0c;在文章的后半部分&#xff0c;我会有一个例…

微服务为什么选Spring Cloud

转载自 微服务为什么选Spring Cloud 现如今微服务架构十分流行&#xff0c;而采用微服务构建系统也会带来更清晰的业务划分和可扩展性。同时&#xff0c;支持微服务的技术栈也是多种多样的&#xff0c;本系列文章主要介绍这些技术中的翘楚——Spring Cloud。这是序篇&#x…

压力与动力是否成正比?

昨天在班里测试了下&#xff0c;检测他们数据库学的怎么样&#xff0c;看他们平时在课堂上的互动挺棒。看了下题&#xff0c;不是很难&#xff0c;满怀着愉悦的心情去打印了50份&#xff0c;挨个分发下去&#xff0c;由于我18级那边有课要上&#xff0c;所以这边的考试就辛苦王…

[开源] 基于ABP,Hangfire的开源Sharepoint文件同步解决方案----SuperRocket.SPSync

&#xff08;一&#xff09;项目背景 Sharepoint是微软的一个产品&#xff0c;很多公司都在使用它&#xff0c;也有很多公司以前使用它&#xff0c;现在可能需要移植到别的平台&#xff0c;也可能只是移植其中的文件存储&#xff0c;比如说移植到微软云&#xff0c;或者亚马逊云…

永远不要、不要、不要、不要放弃

Never, never, never, never give up. 永远不要、不要、不要、不要放弃。今天来写一下18级学生们的状态吧&#xff0c;最近主要是解决了1班的三大问题&#xff0c;第一&#xff0c;上机测试问题。第二&#xff0c;周一到四期间学习任务安排问题。第三&#xff0c;学习氛围的进一…

JS的时间定时器

<script>var t null;t setTimeout(time, 1000); //開始运行function time() {clearTimeout(t); //清除定时器dt new Date();var y dt.getFullYear();var mt dt.getMonth() 1;var day dt.getDate();var h dt.getHours(); //获取时var m dt.getMinutes(); //获取分…

微软正式发布XAML Standard与.NET Standard 2.0:现已提供下载

微软在本月早些时候召开的 Build 2017 开发者大会上的披露的 XAML Standard 和 .NET Standard 2.0&#xff0c;现已正式发布。新工具旨在为开发者们带来“基于同一标准的跨平台 XAML 语言结构”&#xff08;基于 UWP 和 Xamarin.Forms&#xff09;&#xff0c;以及基于社区反馈…