目录
服务雪崩
Hystrix简介
Hystrix能干吗
服务熔断
服务降级
舱壁模式
信号量、线程池
信号量模式
线程池模式
Dashboard简介
开启Dashboard后的架构
HystrixCommand基本配置、同步和异步执行
示例代码
HystrixCommand、Group、ThreadPool 配置
HystrixCommand和分组、线程池三者的关系
HystrixCommand如何执行?同步?异步?
request cache的使用
fallback机制
单个fallback
多级fallback
主次多HystrixCommand fallback
接入现有业务
HystrixCommand执行原理
PREPARE 初始化
Observable 大串烧
command 状态位
executeCommandWithSpecifiedIsolation
getUserExecutionObservable
getFallbackOrThrowException
注意:本文参考
docs/system-design/framework/springcloud/springcloud-intro.md · SnailClimb/JavaGuide - Gitee.com
20000 字的 Spring Cloud 总结,从此任何问题也难不住你
外行人都能看懂的SpringCloud,错过了血亏!
https://segmentfault.com/u/yedge/articles
https://segmentfault.com/a/1190000012549823
https://segmentfault.com/a/1190000013912669
Hystrix系列之信号量、线程池 - 简书
Hystrix 隔离策略:线程池、信号量_先熬半个月的博客-CSDN博客_hystrix信号量
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
1. 为系统提供保护机制。在依赖的服务出现高延迟或失败时,为系统提供保护和控制。
2. 防止雪崩。
3. 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中运行。
4. 跳闸机制:当某服务失败率达到一定的阈值时,Hystrix可以自动跳闸,停止请求该服务一段时间。
5. 资源隔离:Hystrix为每个请求都的依赖都维护了一个小型线程池,如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。防止级联失败。
6. 快速失败:Fail Fast。同时能快速恢复。侧重点是:(不去真正的请求服务,发生异常再返回),而是直接失败。
7. 监控:Hystrix可以实时监控运行指标和配置的变化,提供近实时的监控、报警、运维控制。
8. 回退机制:fallback,当请求失败、超时、被拒绝,或当断路器被打开时,执行回退逻辑。回退逻辑我们自定义,提供优雅的服务降级。
9. 自我修复:断路器打开一段时间后,会自动进入“半开”状态,可以进行打开,关闭,半开状态的转换。前面有介绍。
服务降级(快速失败并即时恢复,合理的fallback和优雅降级)
服务熔断(阻断传递失败,防止雪崩)
服务限流
提供近实时的监控、告警和操作控制
对外依赖包括第三方类库的依赖提供延迟和失败保护
熔断机制是应对雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回"错误"的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。
其实这里所讲的 熔断 就是指的 Hystrix
中的 断路器模式 ,你可以使用简单的 @HystrixCommand
注解来标注某个方法,这样 Hystrix
就会使用 断路器 来“包装”这个方法,每当调用时间超过指定时间时(默认为 1000ms),断路器将会中断对这个方法的调用。
Hystrix提供几个熔断关键参数:滑动窗口大小(20)、 熔断器开关间隔(5s)、错误率(50%)
每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。
直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。
当然你可以对这个注解的很多属性进行设置,比如设置超时时间,像这样。
@HystrixCommand(
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1200")}
)
public List getXxxx() {
// ...省略代码逻辑
}
整体资源快不够了,忍痛将某些服务先关掉,待渡过难关,再开启回来。
服务降级处理是在客户端实现完成的,与服务端没有关系
但是,我查阅了一些博客,发现他们都将 熔断 和 降级 的概念混淆了,以我的理解,降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。这也就对应着 Hystrix
的 后备处理 模式。你可以通过设置 fallbackMethod
来给一个方法设置备用的代码逻辑。比如这个时候有一个热点新闻出现了,我们会推荐给用户查看详情,然后用户会通过 id 去查询新闻的详情,但是因为这条新闻太火了(比如最近什么*易对吧),大量用户同时访问可能会导致系统崩溃,那么我们就进行 服务降级 ,一些请求会做一些降级处理比如当前人数太多请稍后查看等等。
// 指定了后备方法调用
@HystrixCommand(fallbackMethod = "getHystrixNews")
@GetMapping("/get/news")
public News getNews(@PathVariable("id") int id) {
// 调用新闻系统的获取新闻api 代码逻辑省略
}
//
public News getHystrixNews(@PathVariable("id") int id) {
// 做服务降级
// 返回当前人数太多,请稍后查看
}
package com.atguigu.springcloud.service;
import java.util.List;
import org.springframework.stereotype.Component;
import com.atguigu.springcloud.entities.Dept;
import feign.hystrix.FallbackFactory;
@Component//不要忘记添加,不要忘记添加
public class DeptClientServiceFallbackFactory implements FallbackFactory
{
@Override
public DeptClientService create(Throwable throwable)
{
return new DeptClientService() {
@Override
public Dept get(long id)
{
return new Dept().setDeptno(id)
.setDname("该ID:"+id+"没有没有对应的信息,Consumer客户端提供的降级信息,此刻服务Provider已经关闭")
.setDb_source("no this database in MySQL");
}
@Override
public List list()
{
return null;
}
@Override
public boolean add(Dept dept)
{
return false;
}
};
}
}
package com.atguigu.springcloud.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.atguigu.springcloud.entities.Dept;
@FeignClient(value = "MICROSERVICECLOUD-DEPT",fallbackFactory=DeptClientServiceFallbackFactory.class)
public interface DeptClientService
{
@RequestMapping(value = "/dept/get/{id}",method = RequestMethod.GET)
public Dept get(@PathVariable("id") long id);
@RequestMapping(value = "/dept/list",method = RequestMethod.GET)
public List list();
@RequestMapping(value = "/dept/add",method = RequestMethod.POST)
public boolean add(Dept dept);
}
资源/依赖隔离(线程池隔离):它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响, 而不会拖慢其他的依赖服务。
在不使用舱壁模式的情况下,服务 A 调用服务 B,这种调用默认的是 使用同一批线程来执行 的,而在一个服务出现性能问题的时候,就会出现所有线程被刷爆并等待处理工作,同时阻塞新请求,最终导致程序崩溃。而舱壁模式会将远程资源调用隔离在他们自己的线程池中,以便可以控制单个表现不佳的服务,而不会使该程序崩溃。
Hystrix内部提供了两种模式执行逻辑:信号量、线程池。
默认情况下,Hystrix使用线程池模式。
不过两者有什么区别,在实际场景中如何选择?
如果要使用信号量模式,需要配置参数execution.isolation.strategy = ExecutionIsolationStrategy.SEMAPHORE
.
信号量隔离,适应非网络请求,因为是同步的请求,无法支持超时,只能依靠协议本身
线程池隔离,即:每个实例都增加个线程池进行隔离
它在调用线程上执行,并发请求受到信号量的限制。
注意:理论上上所有的hystrixcommand共用一个信号量!
每次调用线程,当前请求通过计算信号量进行限制,当信号量大于了最大请求数(maxConcurrentRequests)时,进行限制,调用fallback接口快速返回。
最重要的是,信号量的调用是同步的
,也就是说,每次调用都会阻塞调用方的线程,直到结果返回
。这样就导致了无法对访问做超时(只能依靠调用协议超时,无法主动释放)
通常,只有在调用量如此之大(每个实例每秒数百个)以至于各个线程的开销过高时,才需要对 HystrixCommands 使用信号隔离。 这通常仅适用于非网络呼叫。(来自官网)
另外,为了限制对下游依赖的并发调用量,可以配置Hystrix的execution.isolation.semaphore.maxConcurrentRequests
,当并发请求数达到阈值时,请求线程可以快速失败,执行降级。
实现也很简单,一个简单的计数器,当请求进入熔断器时,执行tryAcquire()
,计数器加1,结果大于阈值的话,就返回false,发生信号量拒绝事件,执行降级逻辑。当请求离开熔断器时,执行release()
,计数器减1。
它在单独的线程上执行,并发请求受线程池中线程数的限制
通过每次都开启一个单独线程运行。它的隔离是通过线程池,即每个隔离粒度都是个线程池,互相不干扰
。线程池隔离方式,等于多了一层的保护措施,可以通过hytrix直接设置超时,超时后直接返回。
在信号量模式提到的问题,对所依赖的多个下游服务,通过线程池的异步执行,可以有效的提高接口性能。
优势
减少所依赖服务发生故障时的影响面,比如ServiceA服务发生异常,导致请求大量超时,对应的线程池被打满,这时并不影响ServiceB、ServiceC的调用。
如果接口性能有变动,可以方便的动态调整线程池的参数或者是超时时间,前提是Hystrix参数实现了动态调整。
缺点
请求在线程池中执行,肯定会带来任务调度、排队和上下文切换带来的开销。
因为涉及到跨线程,那么就存在ThreadLocal数据的传递问题,比如在主线程初始化的ThreadLocal变量,在线程池线程中无法获取
注意
因为Hystrix默认使用了线程池模式,所以对于每个Command,在初始化的时候,会创建一个对应的线程池,如果项目中需要进行降级的接口非常多,比如有上百个的话,不太了解Hystrix内部机制的同学,按照默认配置直接使用,可能就会造成线程资源的大量浪费。
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
我们现在的服务是这样的:
除了可以开启单个实例的监控页面之外,还有一个监控端点 /turbine.stream
是对集群使用的。从端点的命名中,可以引入Turbine, 通过它来汇集监控信息,并将聚合后的信息提供给 HystrixDashboard 来集中展示和监控。
举个例子:
3y和女朋友决定去万达玩,去到万达的停车场发现在负一层已经大大写上“负一层已停满,请下负二层,负二层空余停车位还有100个!”
这时,3y就跟女朋友说:“万达停车场是做得挺好的,如果它没有直接告知我负一层已满,可能我就去负一层找位置了,要是一堆人跑去负一层但都找不到车位的话,恐怕就塞死了”。3y接着说:“看停车位的状态也做得不错,在停车位上头有一个感应(监控),如果是红色就代表已被停了,如果是绿色就说明停车位是空的”。
3y女朋友不屑的说:“你话是真的多”
helloWorld初窥Hystrix
先贴代码
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
public class CommandHelloWorld extends HystrixCommand {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
return "Hello " + name + "!";
}
}
代码很简单,声明一个类CommandHelloWorld
,集成HystrixCommand
, HystrixCommand携带泛型,泛型的类型就是我们的执行方法run()
返回的结果的类型。逻辑执行体就是run方法的实现。
构造方法至少要传递一个分组相关的配置给父类才能实现实例化,具体用来干什么的后面会描述。
下面测试一下
public class CommandHelloWorldTest {
@Test
public void test_01(){
String result = new CommandHelloWorld("world").execute();
Assert.assertEquals("Hello world!",result);
}
}
就这样第一个hellworld就跑起来,so easy
Hystrix把执行都包装成一个HystrixCommand,并启用线程池实现多个依赖执行的隔离。
上面的代码集成了HystrixCommand并实现了类似分组key的构造方法,那么分组是用来做什么呢?还有没有其他类似的东西?怎么没有看到线程配置呢?
Hystrix每个command都有对应的commandKey可以认为是command的名字,默认是当前类的名字getClass().getSimpleName()
,每个command也都一个归属的分组,这两个东西主要方便Hystrix进行监控、报警等。
HystrixCommand使用的线程池也有线程池key,以及对应线程相关的配置
下面是代码的实现方式
自定义command key
HystrixCommandKey.Factory.asKey("HelloWorld")
自定义command group
HystrixCommandGroupKey.Factory.asKey("ExampleGroup")
那么线程池呢?
HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")
Hystrix command配置有熔断阀值,熔断百分比等配置,ThreadPoll有线程池大小,队列大小等配置,如何设置?
Hystrix的配置可以通过Setter进行构造
public CommandHelloWorld(){
super(Setter
//分组key
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("helloWorldGroup"))
//commandKey
.andCommandKey(HystrixCommandKey.Factory.asKey("commandHelloWorld"))
//command属性配置
.andCommandPropertiesDefaults(HystrixPropertiesCommandDefault.Setter().withCircuitBreakerEnabled(true).withCircuitBreakerForceOpen(true))
//线程池key
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("helloWorld_Poll"))
//线程池属性配置
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(20).withMaxQueueSize(25))
);
}
其它的详细配置可参考https://github.com/Netflix/Hy... 后续也会整理对应的配置介绍文章。
commandKey分组内唯一,HystrixCommand和分组、线程池是多对1的关系。分组和线程池没关系。
同步
从helloWorld的例子可以看到,我们实例化了我们的HelloWorldCommand,调用了execute方法,从而执行了command的Run()。这种是同步的执行方式。
异步执行
在实际业务中,有时候我们会同时触发多个业务依赖的调用,而这些业务又相互不依赖这时候很适合并行执行,我们可以使用Future方式,调用command的queue()方法。
我们可以再写一个helloWorld2
public class CommandHelloWorld2 extends HystrixCommand {
private final String name;
public CommandHelloWorld2(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
return "Hello " + name + "!";
}
}
具体异步调用
@Test
public void test_02() throws ExecutionException, InterruptedException {
Future future1 = new CommandHelloWorld("world").queue();
Future future2 = new CommandHelloWorld2("world").queue();
Assert.assertEquals("Hello world!",future1.get());
Assert.assertEquals("Hello world!",future2.get());
}
public class CachedCommand extends HystrixCommand {
private String key;
private static final HystrixCommandKey COMMANDKEY = HystrixCommandKey.Factory.asKey("CachedCommand_cmd");
protected CachedCommand(String key){
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CachedCommand"))
.andCommandKey(COMMANDKEY));
this.key = key;
}
@Override
protected String getCacheKey() {
return this.key;
}
public static void flushCache(String key) {
HystrixRequestCache.getInstance(COMMANDKEY,
HystrixConcurrencyStrategyDefault.getInstance()).clear(key);
}
@Override
protected String run() throws Exception {
return "hello "+ key +" !";
}
}
Hystrix的cache,个人的理解就是在上下文中,多次请求同一个command,返回值不会发生改变的时候可以使用。cache如果要生效,必须声明上下文
HystrixRequestContext context = HystrixRequestContext.initializeContext();
....... command 的调用 ........
context.shutdown();
清缓存,就是先获得到command然后把对应的key删除
HystrixRequestCache.getInstance(COMMANDKEY,
HystrixConcurrencyStrategyDefault.getInstance()).clear(key);
接下来看下完整的调用
@Test
public void test_no_cache(){
HystrixRequestContext context = HystrixRequestContext.initializeContext();
String hahahah = "hahahah";
CachedCommand cachedCommand = new CachedCommand(hahahah);
Assert.assertEquals("hello hahahah !", cachedCommand.execute());
Assert.assertFalse(cachedCommand.isResponseFromCache());
CachedCommand cachedCommand2 = new CachedCommand(hahahah);
Assert.assertEquals("hello hahahah !", cachedCommand2.execute());
Assert.assertTrue(cachedCommand2.isResponseFromCache());
//清除缓存
CachedCommand.flushCache(hahahah);
CachedCommand cachedCommand3 = new CachedCommand(hahahah);
Assert.assertEquals("hello hahahah !", cachedCommand3.execute());
Assert.assertFalse(cachedCommand3.isResponseFromCache());
context.shutdown();
}
fallback就是当HystrixCommand 执行失败的时候走的后备逻辑,只要实现HystrixCommand 的fallback方法即可
public class CommandWithFallBack extends HystrixCommand {
private final boolean throwException;
public CommandWithFallBack(boolean throwException) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.throwException = throwException;
}
@Override
protected String run() {
if (throwException) {
throw new RuntimeException("failure from CommandThatFailsFast");
} else {
return "success";
}
}
@Override
protected String getFallback() {
return "I'm fallback";
}
}
测试结果
@Test
public void testSuccess() {
assertEquals("success", new CommandWithFallBack(false).execute());
}
@Test
public void testFailure() {
try {
assertEquals("I'm fallback", new CommandWithFallBack(true).execute());
} catch (HystrixRuntimeException e) {
Assert.fail();
}
}
当我们执行业务的时候,有时候会有备用方案1、备用方案2,当备用方案1失败的时候启用备用方案2,所以可以使用多级fallback。
多级fallback没有名字那么神秘,说到底其实就是HystrixCommand1执行fallback1, fallback1的执行嵌入HystrixCommand2,当HystrixCommand2执行失败的时候,触发HystrixCommand2的fallback2,以此循环下去实现多级fallback,暂未上限,只要你的方法栈撑的起。
command1
public class CommandWithMultiFallBack1 extends HystrixCommand {
private final boolean throwException;
public CommandWithMultiFallBack1(boolean throwException) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.throwException = throwException;
}
@Override
protected String run() {
if (throwException) {
throw new RuntimeException("failure from CommandThatFailsFast");
} else {
return "success";
}
}
@Override
protected String getFallback() {
return new CommandWithMultiFallBack2(true).execute();
}
}
command2
public class CommandWithMultiFallBack2 extends HystrixCommand {
private final boolean throwException;
public CommandWithMultiFallBack2(boolean throwException) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.throwException = throwException;
}
@Override
protected String run() {
if (throwException) {
throw new RuntimeException("failure from CommandThatFailsFast");
} else {
return "I'm fallback1";
}
}
@Override
protected String getFallback() {
return "I'm fallback2";
}
调用测试
@Test
public void testMultiFailure(){
try {
assertEquals("I'm fallback2", new CommandWithMultiFallBack1(true).execute());
} catch (HystrixRuntimeException e) {
Assert.fail();
}
}
这里探讨的是 主Command里串行执行 多个Command时的fallback执行逻辑
这里就不贴代码了,fallback的跳转也比较好理解,次command,不管任何一个执行失败都认为主command的run执行失败,进而进入主command的fallback
上面的章节主要在解释如何使用HystrixCommand,但是我们在开发中都已经分好了各种业务的servie,如何套入这个Hystrix?
模拟业务场景
假设我们要加载商品详情页,需要加载商品信息、用户信息、店铺信息
接入Hystrix前的代码(代码有点天真,只是为了表述下意思)
//主功能类
public class GoodsService {
private UserService userService = new UserService();
private ShopService shopService = new ShopService();
/**
* 获取商品详情
* @return
*/
public GoodsDetailFrontModel getGoodsFrontDetail(){
GoodsDetailFrontModel goodsDetailFrontModel = new GoodsDetailFrontModel();
goodsDetailFrontModel.setTitle("这是一个测试商品");
goodsDetailFrontModel.setPrice(10000L);
UserModel userInfo = userService.getUserInfo(1000001L);
ShopModel shopInfo = shopService.getShopInfo(2001L);
goodsDetailFrontModel.setShopModel(shopInfo);
goodsDetailFrontModel.setUserModel(userInfo);
return goodsDetailFrontModel;
}
}
//依赖的用户类
public class UserService {
/**
* 获取用户信息
* @param userId
* @return
*/
public UserModel getUserInfo(Long userId){
return new UserModel();
}
}
下面我们对用户服务套入Hystrix,为了不侵入我们依赖的服务,我们新建一个门户类,包装Hystrix相关的代码
public class UserServiceFacade extends HystrixCommand {
//原业务service
private UserService userService = new UserService();
private Long userId;
protected UserServiceFacade() {
super(HystrixCommandGroupKey.Factory.asKey("UserServiceFacade"));
}
@Override
protected UserModel run() throws Exception {
return userService.getUserInfo(userId);
}
/**
*如果执行失败则返回游客身份
**/
@Override
protected UserModel getFallback() {
UserModel userModel = new UserModel();
userModel.setName("游客");
return userModel;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
然后我们再看下主执行类
GoodsService
/**
* 获取商品详情
* @return
*/
public GoodsDetailFrontModel getGoodsFrontDetail(){
GoodsDetailFrontModel goodsDetailFrontModel = new GoodsDetailFrontModel();
goodsDetailFrontModel.setTitle("这是一个测试商品");
goodsDetailFrontModel.setPrice(10000L);
//原写法
//UserModel userInfo = userService.getUserInfo(1000001L);
//这里替换成了调用用户门面类
UserServiceFacade userServiceFacade = new UserServiceFacade();
userServiceFacade.setUserId(1000001L);
UserModel userInfo = userServiceFacade.execute();
ShopModel shopInfo = shopService.getShopInfo(2001L);
goodsDetailFrontModel.setShopModel(shopInfo);
goodsDetailFrontModel.setUserModel(userInfo);
return goodsDetailFrontModel;
}
上面的代码提供一个套入的思路,官方原生的Hystrix就是这样接入的,这里注意一点,HystrixCommand每次执行都需要new一个,不能使用单例,一个command实例只能执行一次
,上面的代码也就是我们的userServiceFacade,每次执行都需要new一个新的对象。
Hystrix的常规使用姿势
@Test
public void test_run(){
String s = new CommandHelloWorld("Bob").execute();
System.out.println(s);
}
我们的command在new的时候发生了什么?execute()是如何执行的?execute执行失败或者超时如何fallback?
当我们new XXCommand()的时候,大部分的工作都是在 AbstractCommand
完成
protected AbstractCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool,
HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults,
HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore,
HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) {
this.commandGroup = initGroupKey(group);
this.commandKey = initCommandKey(key, getClass());
this.properties = initCommandProperties(this.commandKey, propertiesStrategy, commandPropertiesDefaults);
this.threadPoolKey = initThreadPoolKey(threadPoolKey, this.commandGroup, this.properties.executionIsolationThreadPoolKeyOverride().get());
this.metrics = initMetrics(metrics, this.commandGroup, this.threadPoolKey, this.commandKey, this.properties);
this.circuitBreaker = initCircuitBreaker(this.properties.circuitBreakerEnabled().get(), circuitBreaker, this.commandGroup, this.commandKey, this.properties, this.metrics);
this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults);
//Strategies from plugins
this.eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(this.commandKey, this.commandGroup, this.metrics, this.circuitBreaker, this.properties);
this.executionHook = initExecutionHook(executionHook);
this.requestCache = HystrixRequestCache.getInstance(this.commandKey, this.concurrencyStrategy);
this.currentRequestLog = initRequestLog(this.properties.requestLogEnabled().get(), this.concurrencyStrategy);
/* fallback semaphore override if applicable */
this.fallbackSemaphoreOverride = fallbackSemaphore;
/* execution semaphore override if applicable */
this.executionSemaphoreOverride = executionSemaphore;
}
可以很清晰的看到,这里面在进行command配置装载、线程池配置装载及线程池的创建、指标搜集器、熔断器的初始化等等。
//HystrixCommandMetrics
ConcurrentHashMap metrics = new ConcurrentHashMap();
//HystrixThreadPoolDefault
final static ConcurrentHashMap threadPools = new ConcurrentHashMap();
//com.netflix.hystrix.HystrixCircuitBreaker.Factory
private static ConcurrentHashMap circuitBreakersByCommand = new ConcurrentHashMap();
除HystrixCommand每次都需要重新建立,其它基本都以commandKey维护着配置,熔断器,指标的单例而线程池则以threadkey进场存储。
我们可以了了解下Hystrix的线程池如何管理
创建线程调用 HystrixThreadPool.Factory.getInstance
static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesBuilder) {
// get the key to use instead of using the object itself so that if people forget to implement equals/hashcode things will still work
String key = threadPoolKey.name();
// this should find it for all but the first time
HystrixThreadPool previouslyCached = threadPools.get(key);
if (previouslyCached != null) {
return previouslyCached;
}
// if we get here this is the first time so we need to initialize
synchronized (HystrixThreadPool.class) {
if (!threadPools.containsKey(key)) {
threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
}
}
return threadPools.get(key);
}
从缓存中以threadPoolKey获取线程池,获取不到则 调用new HystrixThreadPoolDefault
新建
public HystrixThreadPoolDefault(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesDefaults) {
this.properties = HystrixPropertiesFactory.getThreadPoolProperties(threadPoolKey, propertiesDefaults);
HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
this.queueSize = properties.maxQueueSize().get();
this.metrics = HystrixThreadPoolMetrics.getInstance(threadPoolKey,
concurrencyStrategy.getThreadPool(threadPoolKey, properties),
properties);
this.threadPool = this.metrics.getThreadPool();
this.queue = this.threadPool.getQueue();
/* strategy: HystrixMetricsPublisherThreadPool */
HystrixMetricsPublisherFactory.createOrRetrievePublisherForThreadPool(threadPoolKey, this.metrics, this.properties);
}
注意
this.metrics = HystrixThreadPoolMetrics.getInstance(threadPoolKey,concurrencyStrategy.getThreadPool(threadPoolKey, properties),properties);
其中 concurrencyStrategy.getThreadPool,HystrixConcurrencyStrategy
就是hystrix的线程创建策略者
真正的创建线程执行
HystrixConcurrencyStrategy#getThreadPool
public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
.....各种配置,此处代码省略......
if (allowMaximumSizeToDivergeFromCoreSize) {
final int dynamicMaximumSize = threadPoolProperties.maximumSize().get();
if (dynamicCoreSize > dynamicMaximumSize) {
logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " +
dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ". Maximum size will be set to " +
dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value");
return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
} else {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
}
} else {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
}
}
这里调用java JUC原生的 ThreadPoolExecutor创建线程
Hystrix的执行利用RxJava,组合了很多的Observable,形成一个Observable,和传统的调用链相比更加简洁。
1 toObservable
第一个observable,在下一个chain之前,会更改HystrixCommand状态位 OBSERVABLE_CHAIN_CREATED
2 toObservable
doOnTerminate,探测到terminate时,会将HystrixCommand更改为 TERMINAL
3 executeCommandWithSpecifiedIsolation
在开始执行的时候会更改HystrixCommand更改为 USER_CODE_EXECUTED
4 toObservable
doOnUnsubscribe,探测到terminate时,会将HystrixCommand更改为 UNSUBSCRIBED
分配执行线程,维护线程状态
private Observable executeCommandWithSpecifiedIsolation(final AbstractCommand _cmd) {
if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.THREAD) {
// mark that we are executing in a thread (even if we end up being rejected we still were a THREAD execution and not SEMAPHORE)
return Observable.defer(new Func0>() {
@Override
public Observable call() {
.....省略干扰代码.....
if (!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)) {
return Observable.error(new IllegalStateException("execution attempted while in state : " + commandState.get().name()));
}
if (isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT) {
// the command timed out in the wrapping thread so we will return immediately
// and not increment any of the counters below or other such logic
return Observable.error(new RuntimeException("timed out before executing run()"));
}
if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.STARTED)) {
try {
.....省略干扰代码.....
return getUserExecutionObservable(_cmd);
} catch (Throwable ex) {
return Observable.error(ex);
}
} else {
//command has already been unsubscribed, so return immediately
return Observable.error(new RuntimeException("unsubscribed before executing run()"));
}
}
}).doOnTerminate(new Action0() {
@Override
public void call() {
if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.TERMINAL)) {
handleThreadEnd(_cmd);
}
if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.TERMINAL)) {
//if it was never started and received terminal, then no need to clean up (I don't think this is possible)
}
//if it was unsubscribed, then other cleanup handled it
}
}).doOnUnsubscribe(new Action0() {
@Override
public void call() {
if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.UNSUBSCRIBED)) {
handleThreadEnd(_cmd);
}
if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.UNSUBSCRIBED)) {
//if it was never started and was cancelled, then no need to clean up
}
//if it was terminal, then other cleanup handled it
}
}).subscribeOn(threadPool.getScheduler(new Func0() {
@Override
public Boolean call() {
return properties.executionIsolationThreadInterruptOnTimeout().get() && _cmd.isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT;
}
}));
} else {
.....省略干扰代码.....
}
}
具体逻辑
1.判断隔离策略,如果是Semaphore 信号量则在当前线程上执行,否则进入线程分配逻辑
2.更改HystrixCommand的状态 USER_CODE_EXECUTED
3.判断HystrixCommand超时状态,如果已经超时则抛出异常
4.更改当前command的线程执行状态为 STARTED
5.调用 getUserExecutionObservable 执行具体逻辑
6.doOnTerminate
当Observale执行完毕后(HystrixCommand可能失败也可能执行成功),此时的线程状态可能有两种分别是 STARTED
和 NOT_USING_THREAD
, 然后更改线程状态为 TERMINAL
7.doOnUnsubscribe
当Observable被取消订阅,更改线程状态为 TERMINAL
8.subscribeOn
指定scheduler,这里Hystrix实现了自己的scheduler,在scheduler的worker指定线程池,在配置线程之前会重新加载线程池配置(这里是Rxjava的东西,暂时大家可以粗略的认为这里就是指定线程池,然后把要执行的任务扔到这个线程池里)
@Override
public Scheduler getScheduler(Func0 shouldInterruptThread) {
touchConfig();
return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread);
}
// allow us to change things via fast-properties by setting it each time
private void touchConfig() {
final int dynamicCoreSize = properties.coreSize().get();
final int configuredMaximumSize = properties.maximumSize().get();
int dynamicMaximumSize = properties.actualMaximumSize();
final boolean allowSizesToDiverge = properties.getAllowMaximumSizeToDivergeFromCoreSize().get();
boolean maxTooLow = false;
if (allowSizesToDiverge && configuredMaximumSize < dynamicCoreSize) {
//if user sets maximum < core (or defaults get us there), we need to maintain invariant of core <= maximum
dynamicMaximumSize = dynamicCoreSize;
maxTooLow = true;
}
// In JDK 6, setCorePoolSize and setMaximumPoolSize will execute a lock operation. Avoid them if the pool size is not changed.
if (threadPool.getCorePoolSize() != dynamicCoreSize || (allowSizesToDiverge && threadPool.getMaximumPoolSize() != dynamicMaximumSize)) {
if (maxTooLow) {
logger.error("Hystrix ThreadPool configuration for : " + metrics.getThreadPoolKey().name() + " is trying to set coreSize = " +
dynamicCoreSize + " and maximumSize = " + configuredMaximumSize + ". Maximum size will be set to " +
dynamicMaximumSize + ", the coreSize value, since it must be equal to or greater than the coreSize value");
}
threadPool.setCorePoolSize(dynamicCoreSize);
threadPool.setMaximumPoolSize(dynamicMaximumSize);
}
threadPool.setKeepAliveTime(properties.keepAliveTimeMinutes().get(), TimeUnit.MINUTES);
}
touchConfig
执行具体的线程池参数调整。
从上面的过程也能发现,该observable也是维护线程状态的地方,线程的状态变更见下图
执行具体业务逻辑
private Observable getUserExecutionObservable(final AbstractCommand _cmd) {
Observable userObservable;
try {
userObservable = getExecutionObservable();
} catch (Throwable ex) {
// the run() method is a user provided implementation so can throw instead of using Observable.onError
// so we catch it here and turn it into Observable.error
userObservable = Observable.error(ex);
}
return userObservable
.lift(new ExecutionHookApplication(_cmd))
.lift(new DeprecatedOnRunHookApplication(_cmd));
}
userObservable = getExecutionObservable();
由HystrixCommand自己实现
//HystrixCommand
final protected Observable getExecutionObservable() {
return Observable.defer(new Func0>() {
@Override
public Observable call() {
try {
return Observable.just(run());
} catch (Throwable ex) {
return Observable.error(ex);
}
}
}).doOnSubscribe(new Action0() {
@Override
public void call() {
// Save thread on which we get subscribed so that we can interrupt it later if needed
executionThread.set(Thread.currentThread());
}
});
}
这里看到 run()
应该就明白了,就是我们自己的业务代码 CommandHelloWorld
去实现的。
当executeCommandWithSpecifiedIsolation探测到异常时触发该Observable。getFallbackOrThrowException里具体fallback执行看
executeCommandAndObserve。
private Observable executeCommandAndObserve(final AbstractCommand _cmd) {
.....省略干扰代码.....
final Func1> handleFallback = new Func1>() {
.....省略干扰代码.....
};
.....省略干扰代码.....
Observable execution;
if (properties.executionTimeoutEnabled().get()) {
execution = executeCommandWithSpecifiedIsolation(_cmd)
.lift(new HystrixObservableTimeoutOperator(_cmd));
} else {
execution = executeCommandWithSpecifiedIsolation(_cmd);
}
return execution.doOnNext(markEmits)
.doOnCompleted(markOnCompleted)
.onErrorResumeNext(handleFallback)
.doOnEach(setRequestContext);
}
doErrorResumeNext 会触发下一个 handleFallback。
private Observable getFallbackOrThrowException(final AbstractCommand _cmd, final HystrixEventType eventType, final FailureType failureType, final String message, final Exception originalException) {
....省略干扰代码....
if (isUnrecoverable(originalException)) {
....省略干扰代码....
} else {
....省略干扰代码....
if (properties.fallbackEnabled().get()) {
....省略干扰代码....
Observable fallbackExecutionChain;
// acquire a permit
if (fallbackSemaphore.tryAcquire()) {
try {
if (isFallbackUserDefined()) {
executionHook.onFallbackStart(this);
fallbackExecutionChain = getFallbackObservable();
} else {
//same logic as above without the hook invocation
fallbackExecutionChain = getFallbackObservable();
}
} catch (Throwable ex) {
//If hook or user-fallback throws, then use that as the result of the fallback lookup
fallbackExecutionChain = Observable.error(ex);
}
return fallbackExecutionChain
.doOnEach(setRequestContext)
.lift(new FallbackHookApplication(_cmd))
.lift(new DeprecatedOnFallbackHookApplication(_cmd))
.doOnNext(markFallbackEmit)
.doOnCompleted(markFallbackCompleted)
.onErrorResumeNext(handleFallbackError)
.doOnTerminate(singleSemaphoreRelease)
.doOnUnsubscribe(singleSemaphoreRelease);
} else {
return handleFallbackRejectionByEmittingError();
}
} else {
return handleFallbackDisabledByEmittingError(originalException, failureType, message);
}
}
}
这里优先几个步骤
1.判断异常是否是能走fallback处理,不能则抛出HystrixRuntimeException
2.判断配置是否开启允许fallback,开启,则进入 getFallbackObservable()
,而该方法具体有HystrixCommand实现,调用的则是用户的Command的fallback方法,如果调用方没有覆盖该方法,则会执行HystrixCommand的fallback方法,抛出未定义fallback方法的异常