在深入研究熔断器之前,我们需要先看一下Hystrix的几个重要的默认配置,这几个配置在HystrixCommandProperties

//时间窗(ms)
static final Integer default_metricsRollingStatisticalWindow = 10000;
//最少请求次数
private static final Integer default_circuitBreakerRequestVolumeThreshold = 20;
//熔断器打开后开始尝试半开的时间间隔
private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000;
//错误比例
private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;

这几个属性共同组成了熔断器的核心逻辑,即:

  1. 每10秒的窗口期内,当请求次数超过20次,且出错比例超过50%,则触发熔断器打开
  2. 当熔断器5秒后,会尝试放过去一部分流量进行试探
熔断器初始化

熔断器的初始化是在HystrixCircuitBreaker.FactorygetInstance方法

        private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap<String, HystrixCircuitBreaker>();

        public static HystrixCircuitBreaker getInstance(HystrixCommandKey key, HystrixCommandGroupKey group, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
// this should find it for all but the first time
HystrixCircuitBreaker previouslyCached = circuitBreakersByCommand.get(key.name());
if (previouslyCached != null) {
return previouslyCached;
} // if we get here this is the first time so we need to initialize // Create and add to the map ... use putIfAbsent to atomically handle the possible race-condition of
// 2 threads hitting this point at the same time and let ConcurrentHashMap provide us our thread-safety
// If 2 threads hit here only one will get added and the other will get a non-null response instead.
HystrixCircuitBreaker cbForCommand = circuitBreakersByCommand.putIfAbsent(key.name(), new HystrixCircuitBreakerImpl(key, group, properties, metrics));
if (cbForCommand == null) {
// this means the putIfAbsent step just created a new one so let's retrieve and return it
return circuitBreakersByCommand.get(key.name());
} else {
// this means a race occurred and while attempting to 'put' another one got there before
// and we instead retrieved it and will now return it
return cbForCommand;
}
}

由上方代码可知,每一个熔断器都是由HystrixCircuitBreakerImpl实现的,而所有的熔断器都维护在circuitBreakersByCommand这个ConcurrentHashMap

熔断器实现
构造方法
class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker {
private final HystrixCommandProperties properties;
private final HystrixCommandMetrics metrics; enum Status {
CLOSED, OPEN, HALF_OPEN
} private final AtomicReference<Status> status = new AtomicReference<Status>(Status.CLOSED);
private final AtomicLong circuitOpened = new AtomicLong(-1);
private final AtomicReference<Subscription> activeSubscription = new AtomicReference<Subscription>(null); protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, final HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
this.properties = properties;
this.metrics = metrics; //On a timer, this will set the circuit between OPEN/CLOSED as command executions occur
Subscription s = subscribeToStream();
activeSubscription.set(s);
}
}

先介绍一下几个比较基础的属性:

  1. HystrixCommandProperties:当前熔断器的配置
  2. HystrixCommandMetrics: 请求统计组件
  3. Status:熔断器状态枚举,一共包含三种,关闭、打开和半开
  4. status:当前熔断器的状态
  5. circuitOpened:当前熔断器的打开时间
  6. activeSubscription:订阅请求统计的处理函数
请求统计处理
private Subscription subscribeToStream() {
/*
* This stream will recalculate the OPEN/CLOSED status on every onNext from the health stream
*/
return metrics.getHealthCountsStream()
.observe()
.subscribe(new Subscriber<HealthCounts>() {
@Override
public void onCompleted() { } @Override
public void onError(Throwable e) { } @Override
public void onNext(HealthCounts hc) {
// check if we are past the statisticalWindowVolumeThreshold
if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
// we are not past the minimum volume threshold for the stat window,
// so no change to circuit status.
// if it was CLOSED, it stays CLOSED
// if it was half-open, we need to wait for a successful command execution
// if it was open, we need to wait for sleep window to elapse
} else {
if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
//we are not past the minimum error threshold for the stat window,
// so no change to circuit status.
// if it was CLOSED, it stays CLOSED
// if it was half-open, we need to wait for a successful command execution
// if it was open, we need to wait for sleep window to elapse
} else {
// our failure rate is too high, we need to set the state to OPEN
if (status.compareAndSet(Status.CLOSED, Status.OPEN)) {
circuitOpened.set(System.currentTimeMillis());
}
}
}
}
});
}

直接看onNext方法里的处理方式:

  1. 时间窗内的请求数量是否达标,按默认配置就是10秒钟的请求数是否超过20次,如果不达标不能开启熔断器
  2. else中首先判断错误比例是否达到比例,按默认就是50%
  3. 满足打开条件,使用CAS修改状态为打开,并记录打开时间circuitOpened为当前时间

当记录了当前应用的统计数据之后,在每次请求的时候就可以根据这些数据来判断是否应该打开熔断器了

请求过滤

不知你是否还记得在系列文章第一篇中曾经提到了一个方法applyHystrixSemantics,在这个方法中就包含了判断是否应该熔断的逻辑,如果熔断器打开的情况下会直接进入降级逻辑。这个判断的方法如下:

        public boolean attemptExecution() {
if (properties.circuitBreakerForceOpen().get()) {
return false;
}
if (properties.circuitBreakerForceClosed().get()) {
return true;
}
if (circuitOpened.get() == -1) {
return true;
} else {
if (isAfterSleepWindow()) {
if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
//only the first request after sleep window should execute
return true;
} else {
return false;
}
} else {
return false;
}
}
}
  1. 第一个if,如果配置强制熔断则返回false表示开启熔断器进入降级逻辑
  2. 第二个,如果配置强制关闭则返回正常不进行后续的判断
  3. 第三个,打开时间为空则肯定没打开过
  4. 第四个,判断是否满足尝试时间,默认是5秒钟。时间计算方式如下:
private boolean isAfterSleepWindow() {
final long circuitOpenTime = circuitOpened.get();
final long currentTime = System.currentTimeMillis();
final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get();
return currentTime > circuitOpenTime + sleepWindowTime;
}
  1. 当满足尝试时则使用CAS方式修改熔断器为半开状态

而当请求成功的时候则会调用如下方法清除统计数据,更改熔断器状态为关闭

 public void markSuccess() {
if (status.compareAndSet(Status.HALF_OPEN, Status.CLOSED)) {
//This thread wins the race to close the circuit - it resets the stream to start it over from 0
metrics.resetStream();
Subscription previousSubscription = activeSubscription.get();
if (previousSubscription != null) {
previousSubscription.unsubscribe();
}
Subscription newSubscription = subscribeToStream();
activeSubscription.set(newSubscription);
circuitOpened.set(-1L);
}
}

请求失败则再次打开熔断器,并更新打开时间

        public void markNonSuccess() {
if (status.compareAndSet(Status.HALF_OPEN, Status.OPEN)) {
//This thread wins the race to re-open the circuit - it resets the start time for the sleep window
circuitOpened.set(System.currentTimeMillis());
}
}

原文地址

Hystrix核心熔断器的更多相关文章

  1. 分布式RPC框架Dubbo实现服务治理:集成Kryo实现高速序列化,集成Hystrix实现熔断器

    Dubbo+Kryo实现高速序列化 Dubbo RPC是Dubbo体系中最核心的一种高性能,高吞吐量的远程调用方式,是一种多路复用的TCP长连接调用: 长连接: 避免每次调用新建TCP连接,提高调用的 ...

  2. 【一起学源码-微服务】Hystrix 源码三:Hystrix核心流程:Hystix降级、熔断等原理剖析

    说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一讲我们讲解了Hystrix在配合feign的过程中,一 ...

  3. Hystrix核心基础 - 滑动窗口创建过程及demo

    前言 RxJava可能有些小伙伴没有听过是什么东西,可能是因为大家平时在做业务需求的时候对异步编程了解得比较少,而RxJava就是这么一个响应式编程框架,RxJava在安卓上面用得非常多,做安卓的朋友 ...

  4. Spring Cloud Hystrix Dashboard熔断器-Turbine集群监控(六)

    序言 上一篇说啦hystrix的使用方法与配置还有工作流程及为何存在,我去,上一篇这么屌,去看看吧,没这么屌的话,我贴的有官方文档,好好仔细看看 hystrix除啦基本的熔断器功能之外,还可以对接口的 ...

  5. 【一起学源码-微服务】Hystrix 源码二:Hystrix核心流程:Hystix非降级逻辑流程梳理

    说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一讲我们讲了配置了feign.hystrix.enabl ...

  6. hystrix(3) 熔断器

    讲完metrics我们就来了解一下熔断器的执行情况,熔断器的判断取决metrics数据. hystrix在执行命令前需要经过熔断器判断,如果服务被熔断,则执行fallback流程,熔断判断逻辑如下: ...

  7. SpringCloud学习笔记:熔断器Hystrix(5)

    1. Hystrix简介 在分布式系统中,服务与服务之间相互依赖,一种不可避免的情况是某些服务会出现故障,导致依赖于它们的其他服务出现远程调度的线程阻塞. Hystrix提供熔断器功能,能够阻止分布式 ...

  8. 熔断器---Hystrix

    Hystrix:熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力. 说到熔断器,先要引入另外一个词,雪崩效应. 雪崩效应,百度百科的解释是这样的: ...

  9. 6、Spring Cloud -熔断器Hystrix

    6.1.什么是Hystrix 在分布式系统中.服务与服务之间的依赖错综复杂,一种不可避免的情况就是某些服务 出现故障,导致依赖于它们的其他服务出现远程调度的线程阻塞.   Hystrix是Netfli ...

随机推荐

  1. verdaccio启动命令

    1.启动项目 根目录下 verdaccio 2.npm 源管理 nrm 下载 3.nrm 源列表 nrm ls 4.nrm 源增加 nrm add verdaccio http://localhost ...

  2. 【微信小程序】App.js生命周期

    1.小程序的生命周期-App.js App() 必须在 app.js 中注册,且不能注册多个.所以App()方法在一个小程序中有且仅有一个. App({ onLaunch: function () { ...

  3. 安装爬虫 scrapy 框架前提条件

    安装爬虫 scrapy 框架前提条件 (不然 会 报错) pip install pypiwin32

  4. JAVA学习方法之——费曼学习法

    理查德·费曼 费曼简介 理查德·菲利普斯·费曼(Richard Phillips Feynman),出生于1918年5月11日,是美籍犹太裔物理学家,曾在1965年获得诺贝尔物理学奖,也被认为是继爱因 ...

  5. 初学JavaScript正则表达式(六)

    JavaScript预定义类 ab+数字+任意字符 ab[0-9][^\r\n] 等价于 ab\d. '@123@abc@'.replace(/@./g,'Q') Q23Qbc@ 将"@加任 ...

  6. linux passwd批量修改用户密码

    linux passwd批量修改用户密码 对系统定期修改密码是一个很重要的安全常识,通常,我们修改用户密码都使用 passwd user 这样的命令来修改密码,但是这样会进入交互模式,即使使用脚本也不 ...

  7. 攻防世界web-NaNNaNNaNNaN-Batman

    先把附件下载下来,然后打开,一堆js乱码 把最后的eval修改成alert,再将文件后缀修改成html,用浏览器打开,乱码变正常了 之后把js代码对齐一下,这里用这个网站进行对齐 https://be ...

  8. LeetCode 回文串问题

    5. Longest Palindromic Substring 647. Palindromic Substrings 解法一:从中心一点向两边扩展,需要考虑中心为一点,中心为两点. 解法二:马拉车 ...

  9. SCOI 2005 互不侵犯

    洛谷 P1896 [SCOI2005]互不侵犯 洛谷传送门 题目描述 在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案.国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一 ...

  10. luoguP4248 [AHOI2013]差异

    题意 考虑式子前面那段其实是\((n-1)*\frac{n*(n+1)}{2}\),因为每个后缀出现了\(n-1\)次,后缀总长为\(\frac{n*(n+1)}{2}\). 现在考虑后面怎么求: \ ...