转载:

https://www.jianshu.com/p/138f92aa83dc

Hystrix出现的原因:

hystrix是netflix开源的一个容灾框架,解决当外部依赖故障时拖垮业务系统、甚至引起雪崩的问题。

在大中型分布式系统中,通常系统很多依赖(HTTP,hession,Netty,Dubbo等),在高并发访问下,这些依赖的稳定性与否对系统的影响非常大,但是依赖有很多不可控问题:如网络连接缓慢,资源繁忙,暂时不可用,服务脱机等。

当依赖阻塞时,大多数服务器的线程池就出现阻塞(BLOCK),影响整个线上服务的稳定性,在复杂的分布式架构的应用程序有很多的依赖,都会不可避免地在某些时候失败。高并发的依赖失败时如果没有隔离措施,当前应用服务就有被拖垮的风险。

这个例子很好!

解决问题方案:对依赖做隔离。

2.2Hystrix设计理念

想要知道如何使用,必须先明白其核心设计理念,Hystrix基于命令模式,通过UML图先直观的认识一下这一设计模式

 
image.png

可见,Command是在Receiver和Invoker之间添加的中间层,Command实现了对Receiver的封装。那么Hystrix的应用场景如何与上图对应呢?

API既可以是Invoker又可以是reciever,通过继承Hystrix核心类HystrixCommand来封装这些API(例如,远程接口调用,数据库查询之类可能会产生延时的操作)。就可以为API提供弹性保护了。

2.3Hystrix如何解决依赖隔离

  • 1:Hystrix使用命令模式HystrixCommand(Command)包装依赖调用逻辑,每个命令在单独线程中/信号授权下执行。
  • 2:可配置依赖调用超时时间,超时时间一般设为比99.5%平均时间略高即可.当调用超时时,直接返回或执行fallback逻辑。
  • 3:为每个依赖提供一个小的线程池(或信号),如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。
  • 4:依赖调用结果分:成功,失败(抛出异常),超时,线程拒绝,短路。 请求失败(异常,拒绝,超时,短路)时执行fallback(降级)逻辑。
  • 5:提供熔断器组件,可以自动运行或手动调用,停止当前依赖一段时间(10秒),熔断器默认错误率阈值为50%,超过将自动运行。
  • 6:提供近实时依赖的统计和监控

2.4Hystrix流程结构解析

 
image.png
流程说明:
1:每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中.
2:执行execute()/queue做同步或异步调用.
3:判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果关闭进入步骤.
4:判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续步骤.
5:调用HystrixCommand的run方法.运行依赖逻辑
5a:依赖逻辑调用超时,进入步骤8.
6:判断逻辑是否调用成功
6a:返回成功调用结果
6b:调用出错,进入步骤8.
7:计算熔断器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态.
8:getFallback()降级逻辑.
以下四种情况将触发getFallback调用:
(1):run()方法抛出非HystrixBadRequestException异常。
(2):run()方法调用超时
(3):熔断器开启拦截调用
(4):线程池/队列/信号量是否跑满
8a:没有实现getFallback的Command将直接抛出异常
8b:fallback降级逻辑调用成功直接返回
8c:降级逻辑调用失败抛出异常
9:返回执行成功结果

2.5熔断器:Circuit Breaker

每个熔断器默认维护10个bucket,每秒一个bucket,每个bucket记录成功,失败,超时,拒绝的状态,

默认错误超过50%且10秒内超过20个请求进行中断拦截.

 
image.png

2.6Hystrix隔离分析

Hystrix隔离方式采用线程/信号的方式,通过隔离限制依赖的并发量和阻塞扩散.

  • (1)线程隔离
    把执行依赖代码的线程与请求线程(如:jetty线程)分离,请求线程可以自由控制离开的时间(异步过程)。
    通过线程池大小可以控制并发量,当线程池饱和时可以提前拒绝服务,防止依赖问题扩散。
    线上建议线程池不要设置过大,否则大量堵塞线程有可能会拖慢服务器。
  • (2)线程隔离的优缺点
    • 线程隔离的优点:
    • 线程隔离的缺点:
    • NOTE: Netflix公司内部认为线程隔离开销足够小,不会造成重大的成本或性能的影响。
    • Netflix 内部API 每天100亿的HystrixCommand依赖请求使用线程隔,每个应用大约40多个线程池,每个线程池大约5-20个线程。
  • (3)信号隔离
    • 信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请),
    • 如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销.
    • 信号量的大小可以动态调整, 线程池大小不可以.

线程隔离与信号隔离区别如下图:

 
image.png

3.接入方式

本文会重点介绍基于服务化项目(thrift服务化项目)的接入方式。

3.1添加hystrix依赖

关于版本问题:由于不同版本Compile Dependencies不同,在使用过程中可以针对具体情况修改版本,具体依赖关系http://mvnrepository.com/artifact/com.netflix.hystrix/hystrix-javanica

<hystrix-version>1.4.22</hystrix-version>

<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>${hystrix-version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
<version>${hystrix-version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>${hystrix-version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-servo-metrics-publisher</artifactId>
<version>${hystrix-version}</version>
</dependency>
<dependency>
<groupId>com.meituan.service.us</groupId>
<artifactId>hystrix-collector</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

3.2引入Hystrix Aspect

application-context.xml文件中

<aop:aspectj-autoproxy/>
<bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"></bean>
<context:component-scan base-package="com.***.***"/>
<context:annotation-config/>

注意:

  • 1)hystrixAspect的这两行配置一定要和下面的context:component-scan放在同一个文件
  • 2)Hystrix依赖的一些jar需要解决冲突问题,例如guava为15.0版本

3.3统计数据

需要注册plugin,直接从plugin中获取统计数据

新增初始化Bean


import com.meituan.service.us.collector.notifier.CustomEventNotifier;
import com.netflix.hystrix.contrib.servopublisher.HystrixServoMetricsPublisher;
import com.netflix.hystrix.strategy.HystrixPlugins;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean; /**
* Created by gaoguangchao on 16/7/1.
*/
public class HystrixMetricsInitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(HystrixMetricsInitializingBean.class); public void init() throws Exception {
LOGGER.info("HystrixMetrics starting...");
HystrixPlugins.getInstance().registerEventNotifier(CustomEventNotifier.getInstance());
HystrixPlugins.getInstance().registerMetricsPublisher(HystrixServoMetricsPublisher.getInstance());
}
}

application-context.xml文件中

<bean id="hystrixMetricsInitializingBean" class="com.***.HystrixMetricsInitializingBean" init-method="init"/>

3.4添加注解

本文使用同步执行方式,因此注解及方法实现都为同步方式,如果有异步执行、反应执行的需求,可以参考:官方注解说明[https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica]

@HystrixCommand(groupKey = "productStockOpLog", commandKey = "addProductStockOpLog", fallbackMethod = "addProductStockOpLogFallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "400"),//指定多久超时,单位毫秒。超时进fallback
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//判断熔断的最少请求数,默认是10;只有在一个统计窗口内处理的请求数量达到这个阈值,才会进行熔断与否的判断
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "10"),//判断熔断的阈值,默认值50,表示在一个统计窗口内有50%的请求处理失败,会触发熔断
}
)
public void addProductStockOpLog(Long sku_id, Object old_value, Object new_value) throws Exception {
if (new_value != null && !new_value.equals(old_value)) {
doAddOpLog(null, null, sku_id, null, ProductOpType.PRODUCT_STOCK, old_value != null ? String.valueOf(old_value) : null, String.valueOf(new_value), 0, "C端", null);
}
} public void addProductStockOpLogFallback(Long sku_id, Object old_value, Object new_value) throws Exception {
LOGGER.warn("发送商品库存变更消息失败,进入Fallback,skuId:{},oldValue:{},newValue:{}", sku_id, old_value, new_value);
}

示例:

@HystrixCommand(groupKey="UserGroup", commandKey = "GetUserByIdCommand",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100"),//指定多久超时,单位毫秒。超时进fallback
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//判断熔断的最少请求数,默认是10;只有在一个统计窗口内处理的请求数量达到这个阈值,才会进行熔断与否的判断
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "10"),//判断熔断的阈值,默认值50,表示在一个统计窗口内有50%的请求处理失败,会触发熔断
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "101"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440")
})

说明:
hystrix函数必须为public,fallback函数可以为private。两者需要返回值和参数相同 详情。

hystrix函数需要放在一个service中,并且,在类本身的其他函数中调用hystrix函数,是无法达到监控的目的的。

3.5参数配置

参数说明 备注
groupKey productStockOpLog group标识,一个group使用一个线程池
commandKey addProductStockOpLog command标识
fallbackMethod addProductStockOpLogFallback fallback方法,两者需要返回值和参数相同
超时时间设置 400ms 执行策略,在THREAD模式下,达到超时时间,可以中断 For most circuits, you should try to set their timeout values close to the 99.5th percentile of a normal healthy system so they will cut off bad requests and not let them take up system resources or affect user behavior.
统计窗口(10s)内最少请求数 10 熔断策略
熔断多少秒后去尝试请求 5s 熔断策略,默认值
熔断阀值 10% 熔断策略:一个统计窗口内有10%的请求处理失败,会触发熔断
线程池coreSize 10 默认值(推荐值).在当前项目中,需要做依赖隔离的方法为发送一条MQ消息,发送MQ消息方法的TP99耗时在1ms以下,近2周单机QPS最高值在18左右,经过灰度验证了午高峰后(当日QPS>上周末QPS),ActiveThreads<=2,rejected=0,经过压测后得出结论:线程池大小为10足以应对2000QPS,前提发送MQ消息时耗时正常(该部分为实际项目中的情况,在此不做详述)
线程池maxQueueSize -1 即线程池队列为SynchronousQueue

4.参数说明

其他参数可参见 https://github.com/Netflix/Hystrix/wiki/Con

分类 参数 作用 默认值 备注
基本参数 groupKey 表示所属的group,一个group共用线程池 getClass().getSimpleName();
基本参数 commandKey   当前执行方法名
Execution ( 控制HystrixCommand.run()的执行策略) execution.isolation.strategy 隔离策略,有THREAD和SEMAPHORE THREAD 以下几种可以使用SEMAPHORE模式: 只想控制并发度 外部的方法已经做了线程隔离 调用的是本地方法或者可靠度非常高、耗时特别小的方法(如medis)
Execution execution.isolation.thread.timeoutInMilliseconds 超时时间 1000ms 默认值:1000 在THREAD模式下,达到超时时间,可以中断 在SEMAPHORE模式下,会等待执行完成后,再去判断是否超时 设置标准: 有retry,99meantime+avg meantime 没有retry,99.5meantime
Execution execution.timeout.enabled 是否打开超时 true
Execution execution.isolation.thread.interruptOnTimeout 是否打开超时线程中断 true THREAD模式有效
Execution execution.isolation.semaphore.maxConcurrentRequests 信号量最大并发度 10 SEMAPHORE模式有效
Fallback ( 设置当fallback降级发生时的策略) fallback.isolation.semaphore.maxConcurrentRequests fallback最大并发度 10
Fallback fallback.enabled fallback是否可用 true
Circuit Breaker (配置熔断的策略) circuitBreaker.enabled 是否开启熔断 true
Circuit Breaker circuitBreaker.requestVolumeThreshold 一个统计窗口内熔断触发的最小个数/10s 20
Circuit Breaker circuitBreaker.sleepWindowInMilliseconds 熔断多少秒后去尝试请求 5000ms
Circuit Breaker circuitBreaker.errorThresholdPercentage 失败率达到多少百分比后熔断 50 主要根据依赖重要性进行调整
Circuit Breaker circuitBreaker.forceOpen 是否强制开启熔断
Circuit Breaker circuitBreaker.forceClosed 是否强制关闭熔断   如果是强依赖,应该设置为true
Metrics (设置关于HystrixCommand执行需要的统计信息) metrics.rollingStats.timeInMilliseconds 设置统计滚动窗口的长度,以毫秒为单位。用于监控和熔断器。 10000 滚动窗口被分隔成桶(bucket),并且进行滚动。 例如这个属性设置10s(10000),一个桶是1s。
Metrics metrics.rollingStats.numBuckets 设置统计窗口的桶数量 10 metrics.rollingStats.timeInMilliseconds必须能被这个值整除
Metrics metrics.rollingPercentile.enabled 设置执行时间是否被跟踪,并且计算各个百分比,50%,90%等的时间 true
Metrics metrics.rollingPercentile.timeInMilliseconds 设置执行时间在滚动窗口中保留时间,用来计算百分比 60000ms
Metrics metrics.rollingPercentile.numBuckets 设置rollingPercentile窗口的桶数量 6 metrics.rollingPercentile.timeInMilliseconds必须能被这个值整除
Metrics metrics.rollingPercentile.bucketSize 此属性设置每个桶保存的执行时间的最大值。 100 如果设置为100,但是有500次求情,则只会计算最近的100次
Metrics metrics.healthSnapshot.intervalInMilliseconds 采样时间间隔 500
Request Context ( 设置HystrixCommand使用的HystrixRequestContext相关的属性) requestCache.enabled 设置是否缓存请求,request-scope内缓存 true
Request Context requestLog.enabled 设置HystrixCommand执行和事件是否打印到HystrixRequestLog中    
ThreadPool Properties(配置HystrixCommand使用的线程池的属性) coreSize 设置线程池的core size,这是最大的并发执行数量。 10 设置标准:coreSize = requests per second at peak when healthy × 99th percentile latency in seconds + some breathing room 大多数情况下默认的10个线程都是值得建议的
ThreadPool Properties maxQueueSize 最大队列长度。设置BlockingQueue的最大长度 -1 默认值:-1 如果使用正数,队列将从SynchronousQueue改为LinkedBlockingQueue
ThreadPool Properties queueSizeRejectionThreshold 设置拒绝请求的临界值 5 此属性不适用于maxQueueSize = - 1时 设置设个值的原因是maxQueueSize值运行时不能改变,我们可以通过修改这个变量动态修改允许排队的长度
ThreadPool Properties keepAliveTimeMinutes 设置keep-live时间 1分钟 这个一般用不到因为默认corePoolSize和maxPoolSize是一样的。

5.性能测试

5.1测试情况

 
image.png

去除Cold状态的第一个异常点后,1-10测试场景的Hystrix平均耗时如上图所示, 可以得出结论:

  1. 单个HystrixCommand的额外耗时基本稳定处于0.3ms左右,和线程池大小无关,和client数量无关
  2. hystrix的额外耗时和执行的HystrixCommand数量有关系,随着command数量增多,耗时增加,但是增量较小,没有比例关系
  3. App刚启动时,第一个请求耗时300+ms,随后请求的耗时降低至1ms以下;刚启动的一小段时间内耗时略大于Hot状态时耗时,总体不超过1ms

作者:高广超
链接:https://www.jianshu.com/p/138f92aa83dc
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

使用Hystrix实现自动降级与依赖隔离-微服务的更多相关文章

  1. 转: 使用Hystrix实现自动降级与依赖隔离

    使用Hystrix实现自动降级与依赖隔离 原创 2017年06月25日 17:28:01 标签: 异步 / 降级 869 这篇文章是记录了自己的一次集成Hystrix的经验,原本写在公司内部wiki里 ...

  2. 自动化API之一 自动生成Mysql数据库的微服务API

        本文演示如何利用Uniconnector平台,自动生成Mysql数据库的API,节约开发人员编写后台API的时间.使用生成API的前提是开发者有 自己的数据库,有数据库的管理权限,并能通过外网 ...

  3. Hystrix入门与分析(二):依赖隔离之线程池隔离

    1.依赖隔离概述 依赖隔离是Hystrix的核心目的.依赖隔离其实就是资源隔离,把对依赖使用的资源隔离起来,统一控制和调度.那为什么需要把资源隔离起来呢?主要有以下几点: 1.合理分配资源,把给资源分 ...

  4. Spring Cloud (8) 服务容错保护-Hystrix依赖隔离

    依赖隔离 docker使用舱壁模式来实现进程的隔离,使容器与容器之间不会互相影响.而Hystrix则使用该模式实现线程池的隔离,它会为每一个Hystrix命令创建一个独立的线程池,这样就算在某个Hys ...

  5. Spring Cloud & Alibaba 实战 | 第十二篇: 微服务整合Sentinel的流控、熔断降级,赋能拥有降级功能的Feign新技能熔断,实现熔断降级双剑合璧(JMeter模拟测试)

    目录 一. Sentinel概念 1. 什么是Sentinel? 2. Sentinel功能特性 3. Sentinel VS Hystrix 二. Docker部署Sentinel Dashboar ...

  6. Spring Cloud微服务如何实现熔断降级?

    熔断限流概述 在基于Spring Cloud的微服务架构体系下,按照系统功能边界的不同划分,原先大而全的系统会被拆分为多个不同的微服务,而相应的微服务会提供一组功能关联的服务接口,并向系统中的其他微服 ...

  7. 一. Go微服务--隔离设计

    1. 前言 隔离设计源于船舶行业,一般而言无论大船还是小船,都会有一些隔板,将船分为不同的空间,这样如果有船舱漏水一般只会影响这一小块空间,不至于把整个船都给搞沉了. 同样我们的软件服务也是一个道理, ...

  8. 微服务架构之spring cloud hystrix&hystrix dashboard

    在前面介绍spring cloud feign中我们已经使用过hystrix,只是没有介绍,spring cloud hystrix在spring cloud中起到保护微服务的作用,不会让发生的异常无 ...

  9. 服务容错保护断路器Hystrix之七:做到自动降级

    从<高可用服务设计之二:Rate limiting 限流与降级>中的“自动降级”中,我们这边将系统遇到“危险”时采取的整套应急方案和措施统一称为降级或服务降级.想要帮助服务做到自动降级,需 ...

随机推荐

  1. 关于Python的面试题

    Python语言特性 1 Python的函数参数传递 看两个例子: a = 1 def fun(a): a = 2 fun(a) print a # 1 a = [] def fun(a): a.ap ...

  2. 3.控制hive map reduce个数

    参考: https://blog.csdn.net/wuliusir/article/details/45010129 https://blog.csdn.net/zhong_han_jun/arti ...

  3. 配置Tomcat使用HTTP/2

    转自: https://zhuanlan.zhihu.com/p/21349186 前情提要: Tomcat高效响应的秘密(一) Sendfile与Gzip Tomcat高效响应的秘密(二) keep ...

  4. Scrum Meeting 10.27

    1.会议内容: 姓名 今日任务 明日任务 预估时间(h) 徐越 配置SQLserver 学习本地和服务器之间的通信 4 卞忠昊 找上届代码的bug 学习安卓布局(layout)的有关知识,研究上届学长 ...

  5. 05慕课网《进击Node.js基础(一)》HTTP概念进阶(同步/异步)

    HTTP模块介绍 支持http协议的更多特性 不缓存请求和响应 API比较底层处理流相关,信息解析 HTTP相关概念 回调 将函数作为参数传到执行函数中,参数函数在执行函数中嵌套执行 function ...

  6. Reaction to 构造之法 of Software Engineering From The First Chapter toThe Fifth Chapter

    几个星期前,我阅读过一篇文章,一位老师教导自己的学生要积极地去阅读文学文献,其中,我很欣赏他的一句话:“Just think of liturature as if you're reading a ...

  7. svmtrain和svmpredict简介

    转自:http://blog.sina.com.cn/s/blog_4d7c97a00101bwz1.html 本文主要介绍了SVM工具箱中svmtrain和svmpredict两个主要函数: (1) ...

  8. MSSQL中IN是否用索引.....[转]

    作者:no_mIss 用MSSQL时间快一年了,数据量有时会相对比较多,所以经常要优化,也看过很多资料,但大都有一句:IN.NOT IN不用索引,今天发此贴希望能有人参与讨论,到底IN用不用索引,如果 ...

  9. Java 使用 DBCP mysql 连接池 做数据库操作

    需要的jar包有 commons-dbutils , commons-dbcp , commons-pool , mysql-connector-java 本地database.propertties ...

  10. Python多线程获取返回值

    在使用多线程的时候难免想要获取其操作完的返回值进行其他操作,下面的方法以作参考: 一,首先重写threading类,使其满足调用特定的方法获取其返回值 import threading class M ...