Hystrix 详细说明
在 Hystrix 入门中,使用 Hystrix 时创建命令并给予执行,实际上 Hystrix 有一套较为复杂的执行逻辑,简单来说明以下运作流程:
- 在命令开始执行时,会做一些准备工作,例如为命令创建响应的线程池等
- 判断是否打开了缓存,打开了缓存就直接查找缓存并返回结果。
- 判断断路器是否打开,如果打开了,就表示服务链路不可用,直接执行回退方法。
- 判断线程池、信号量(计数器)等条件,例如,线程池超负荷,则执行回退方法
- 执行命令,计算是否要对断路器进行处理,执行完成后,如果满足一定条件,则需要开启断路器。如果执行成功则返回结果,执行失败则执行回退
Hystrix 官方的执行流程图如下:
命令执行
Hystrix 命令执行可以使用以下方法来执行命令:
- toObservable:返回一个最原始的 Observable 实例,Observable 是 RxJava 的类,使用该对象可以观察命令的执行过程,并且将执行信息传递给订阅者,该方法是异步执行
- observe:调用 toObservable 方法,获取一个原始的 Observable 实例,使用 ReplaySubject 作为原始 Observable 的订阅者,该方法是异步执行
- queue:通过 toObservable 方法获取原始的 Observable 实例,在调用 Observable 的 toBlocking 方法得到一个 BlockingObservable 实例,最后调用 BlockingObservable 的 toFuture 方法返回 Future 实例,最后调用 Future 实例的 get 方法得到执行结果,该方法是异步执行
- execute:该方法调用 queue 的 get 方法返回命令的执行结果,该方法时同步执行
除了 execute 方法外,其他的方法都是异步执行,observe 与 toObservable 方法的区别是,toObservable 调用后,不会立即执行,只有当返回的 Observable 实例被订阅后,才会真正的执行命令,并且只能一次订阅,而 observe 方法返回的 Observable 可以支持多次订阅;每个命令实例对象,只能执行一次。四种命令的执行示例如下:
package org.lixue.hystrixclient;
import rx.Observable;
import rx.Observer;
import rx.functions.Action0;
import rx.functions.Action1;
import java.util.concurrent.Future;
public class HystrixClient{
publicstaticvoidmain(String[]args){
SpeakSleepCommand cmd=null;
try{
//调用execute方法
cmd=new SpeakSleepCommand(0);
String result=cmd.execute();
System.out.println("execute请求结果="+result);
//调用queue方法
cmd=new SpeakSleepCommand(0);
Future<String>future=cmd.queue();
result=future.get();
System.out.println("queue 请求结果="+result);
//调用observe方法
cmd=new SpeakSleepCommand(0);
Observable<String> observable=cmd.observe();
observable.subscribe(new Observer<String>(){
public void onCompleted(){
System.out.println("subscribe onCompleted 请求完成");
}
public void onError(Throwable throwable){
System.out.println("subscribe onError 请求结果错误="+throwable);
}
public void onNext(String s){
System.out.println("subscribe onNext 请求结果="+s);
}
});
observable.subscribe(new Action1<String>(){
public void call(Strings){
System.out.println("subscribe onNext call 请求结果="+s);
}
});
//调用toObservable方法
cmd=new SpeakSleepCommand(0);
Observable<String>toObservable=cmd.toObservable();
toObservable.subscribe(newObserver<String>(){
public void onCompleted(){
System.out.println("toObservable subscribe onCompleted 请求完成");
}
public void onError(Throwablet hrowable){
System.out.println("toObservable subscribe onError 请求失败");
}
public void onNext(String s){
System.out.println("toObservable subscribe onNext 请求结果="+s);
}
});
//因为是异步的,因此必须线程等待,否则会导致退出,无法获取toObservable的订阅结果
Thread.sleep(100);
}catch(Exceptionex){
ex.printStackTrace();
}
}
}
回退
根据 Hystrix 的执行流程图可以发现,有三种情况下会触发回退(fallback)操作:
- 断路器被打开
- 线程池、队列、信号量满载
- 实际执行命令失败
Hystrix 的回退机制比较灵活,可以在 A 命令的回退方法中执行 B 命令,如果 B 命令执行失败则会触发 B 命令的回退,这样就会形成一种链式的命令执行。
还有一种情况,在 A 命令中调用 B 命令和C 命令,如果 B 命令或者 C 命令调用失败,并且没有提供回退方法,则会调用 A 命令的回退方法,如果 B 命令或 C 明天提供了回退方法,则不会调用 A 命令的回退。多命令调用回退如下图所示:
断路器开启/关闭
断路器一旦开启,就会直接调用回退方法,不在执行命令,而且也不会更新链路的健康状况,断路器的开启要满足两个条件:
- 整个链路达到一定的阈值,默认情况下,10秒内产生超过20次请求,则符合第一个条件
- 满足第一个条件的情况下,如果请求的错误百分比大于阈值,则会打开断路器,默认为 50%
断路器开启相关配置:
- hystrix.command.default.metrics.rollingStats.timeInMilliseconds:默认值 10000,滚动窗口的时间范围
- hystrix.command.default.circuitBreaker.requestVolumeThreshold:默认值 20,滚动窗口期间的最小请求数
- hystrix.command.default.circuitBreaker.errorThresholdPercentage:默认值 50,滚动窗口期间的调用失败比例,高出该比例开启断路器
断路器打开后,在一段时间内,命令不会再执行,这段时间称为休眠期,休眠期默认是5秒,休眠期结束后,Hystrix 会尝试性的执行一次命令,此时断路器的状态不是开启,也不是关闭,而是一个半开的状态,如果这一次命令执行成功,则会关闭断路器并清空链路的健康信息;如果执行失败,断路器会继续保持打开的状态。
断路器关闭相关配置:
- hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds:默认值 5000,休眠期的持续时间
隔离机制
命令的真正执行,除了断路器要关闭外,还需要看执行命令的线程池或者信号量是否满载,如果满载,命令就不会执行,而是直接触发回退,这样的机制,在控制命令的执行上,实现了错误的隔离,Hystrix 提供了两种隔离策略:
- THREAD:默认值,由线程池来决定命令的执行,如果线程池满载,则不会执行命令。线程池默认大小为 10
- SEMAPHORE:由信号量来决定命令执行,当请求的并发数高于阈值时,就不再执行命令,相对与线程池,信号量的开销更小,但是该策略不支持超时以及异步。
合并请求
默认情况下,会为命令分配线程池来执行命令实例,线程池会消耗一定的性能,对于一些同类型的请求(URL相同,参数不同),Hystrix 提供了合并请求的功能,在一次请求的过程中,可以将一个时间段内的相同请求(参数不同),收集到一个命令中执行,这样节省了线程的开销,减少网络连接,从而提升了执行的性能,实现合并请求的功能,至少包含以下三个条件:
- 需要一个执行请求的命令,将全部参数进行整理,然后调用外部服务
- 需要一个合并处理器,用于收集请求,以及结果处理
- 外部接口提供支持,需要能支持批量处理的功能
合并请求只执行了一个命令,只启动了一个线程,只进行了一次网络请求,但是在收集请求、合并请求、处理结果的过程中仍然会耗费一定的时间,一般情况下,合并请求进行批量处理,比发送多个请求快。
合并请求示例:
在Hystrix 入门的示例中进行修改,增加如下代码:
- 创建执行请求的命令
主要用于处理合并后的具体处理命令,组合多个请求的参数,向批量处理接口提交。
package org.lixue.hystrixclient;
import com.alibaba.fastjson.JSONObject;
import com.netflix.hystrix.*;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class SpeakCommand extends HystrixCommand<Map<String,String>>{
//CollapsedRequest泛型的第一个参数为返回对象类型,第二个参数为请求对象类型
Collection<HystrixCollapser.CollapsedRequest<String,String>> collapsedRequests;
private CloseableHttpClient httpClient;
private Strin gurl;
public SpeakCommand(Collection<HystrixCollapser.CollapsedRequest<String,String>> collapsedRequests){
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("BatchSpeak")));
this.collapsedRequests=collapsedRequests;
this.httpClient=HttpClients.createDefault();
this.url="http://localhost:8080/speaks?names=";
}
protected Map<String,String> run() throws Exception{
StringBuilde rstringBuilder=new StringBuilder();
for(HystrixCollapser.CollapsedRequest<String,String> entry:collapsedRequests){
if(stringBuilder.length()>0){
stringBuilder.append(",");
}
stringBuilder.append(entry.getArgument());
}
try{
HttpGet request=new HttpGet(this.url+stringBuilder.toString());
HttpResponse response=httpClient.execute(request);
String responseJson=EntityUtils.toString(response.getEntity());
Map<String,String> result=(Map<String,String>)JSONObject.parse(responseJson);
return result;
}catch(Exceptionex){
ex.printStackTrace();
returnnull;
}
}
}
- 创建合并处理器
合并处理器,用于处理请求的合并和结果的映射,而具体的请求是调用请求命令来完成的。
package org.lixue.hystrixclient;
import com.netflix.hystrix.HystrixCollapser;
import com.netflix.hystrix.HystrixCollapserKey;
import com.netflix.hystrix.HystrixCollapserProperties;
import com.netflix.hystrix.HystrixCommand;
import java.util.Collection;
import java.util.Map;
//HystrixCollapser类型泛型参数,Map<String,String>命令返回类型;String响应返回对象类型;String请求参数对象类型
public class SpeakCollapserCommand extends HystrixCollapser<Map<String,String>,String,String>{
String paramName;
public SpeakCollapserCommand(StringparamName){
super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("SpeakCollapser"))
//设置合并请求的时间,在1秒内的请求进行合并
.andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(1000)));
this.paramName=paramName;
}
/**
* 返回请求参数
*
*/
public String getRequestArgument(){
return this.paramName;
}
/**
* 创建具体请求命令
*
*@param collapsedRequests
*@return
*/
protected HystrixCommand<Map<String,String>> createCommand(Collection<CollapsedRequest<String,String>> collapsedRequests){
return new SpeakCommand(collapsedRequests);
}
/**
* 处理返回结果映射到请求
*
*@param batchResponse
*@paramc ollapsedRequests
*/
protected void mapResponseToRequests(Map<String,String>batchResponse,Collection<CollapsedRequest<String,String>>collapsedRequests){
for(CollapsedRequest<String,String>entry:collapsedRequests){
String result=batchResponse.get(entry.getArgument());
entry.setResponse(result);
}
}
}
- 创建服务代码
在 server-provider(该项目提供 REST 服务) 增加批量处理 REST 服务,代码如下:
package org.lixue.webservices.services;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@RestController
public class HelloWorldController{
@Value("${server.port}")
private int port;
@RequestMapping(method=RequestMethod.GET,name="speak",path="/speaks",
produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
public Map<String,String> speaks(@RequestParam(value="names")String names) throws InterruptedException {
Map<String,String> map=new HashMap<>();
if(names==null || "".equals(names)){
return map;
}
String[]splitName=names.split(",");
for(String name:splitName){
map.put(name,"HelloWorld"+name+"Port="+port);
}
return map;
}
}
- 创建启动类
需要在初始化的时候,开启 HystrixRequestContext 否则无法支持合并请求
package org.lixue.hystrixclient;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import rx.Observable;
import rx.Observer;
import rx.functions.Action1;
import java.util.concurrent.Future;
public class HystrixClient{
public static void main(String[]args){
//需要开启HystrixRequest上下文,合并请求和缓存必须开启
HystrixRequestContext context= HystrixRequestContext.initializeContext();
try{
Future<String> collapser01=new SpeakCollapserCommand("001").queue();
Future<String> collapser02=new SpeakCollapserCommand("002").queue();
Future<String> collapser03=new SpeakCollapserCommand("003").queue();
Future<String> collapser04=new SpeakCollapserCommand("004").queue();
System.out.println(collapser01.get());
System.out.println(collapser02.get());
System.out.println(collapser03.get());
System.out.println(collapser04.get());
}catch(Exceptionex){
ex.printStackTrace();
}
finally{
context.shutdown();
}
}
}
- 测试验证
首先启动 server-provider 项目,然后启动该项目,由于我们设置的是 1 秒内的请求进行合并,因此可以看到会延迟一秒然后返回结果,可以在服务器端增加日志,看到只接收到了一次请求。
请求缓存
Hystrix 支持缓存功能,如果在一次请求的过程中,多个地方调用同一个接口,可以考虑使用缓存,缓存打开后,下一次命令不会执行,直接重缓存中获取响应并返回,开启缓存比较简单,在命令中重写父类的 getCacheKey 方法即可。
缓存示例:
- 创建命令
重写了 getCacheKey 方法,如果返回 null 表示不启用缓存,我们使用请求参数来做为缓存key
package org.lixue.hystrixclient;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class SpeakCacheCommand extends HystrixCommand<String>{
Stringname;
CloseableHttpClienthttpClient;
Stringurl;
public SpeakCacheCommand(String name){
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("speakCache")));
this.name=name;
this.httpClient=HttpClients.createDefault();
this.url="http://localhost:8080/speaks?names="+name;
}
@Override
protected String getCacheKey(){
return this.name;
}
protected String run() throws Exception{
try{
HttpGet request=new HttpGet(this.url);
HttpResponse response=httpClient.execute(request);
return EntityUtils.toString(response.getEntity());
}catch(Exceptionex){
ex.printStackTrace();
return null;
}
}
}
- 修改启动类
需要在初始化的时候,开启 HystrixRequestContext 否则无法支持缓存
package org.lixue.hystrixclient;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
public class HystrixClient{
public static void main(String[]args){
//需要开启HystrixRequest上下文,合并请求和缓存必须开启
HystrixRequestContextcontext=HystrixRequestContext.initializeContext();
try{
SpeakCacheCommand speakCacheCommand=new SpeakCacheCommand("lixue");
System.out.println("SpeakCacheCommand execute="+speakCacheCommand.execute()
+"is cache"+speakCacheCommand.isResponseFromCache());
SpeakCacheCommand speakCacheCommand1=new SpeakCacheCommand("lixue");
System.out.println("SpeakCacheCommand execute="+speakCacheCommand1.execute()
+"is cache"+speakCacheCommand1.isResponseFromCache());
context.shutdown();
}catch(Exceptionex){
ex.printStackTrace();
}finally{
context.shutdown();
}
}
}
- 测试验证
首先启动 server-provider 项目,然后启动该项目,第一次请求没有重缓存读取,而第二次请求由于时相同的请求参数,因此从缓存读取,输出如下:
SpeakCacheCommand execute={"lixue":"Hello World lixue Port=8080"} is cache false
SpeakCacheCommand execute={"lixue":"Hello World lixue Port=8080"} is cache true
Hystrix 详细说明的更多相关文章
- Hystrix介绍以及服务的降级限流熔断
(dubbo熔断,Hystrix问的少) 无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源.作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部 hang (挂起)在这个资源上, ...
- 使用 Spring Cloud 和 Docker 构建微服务架构
如何使用Spring Boot.Spring Cloud.Docker和Netflix的一些开源工具来构建一个微服务架构. 本文通过使用Spring Boot.Spring Cloud和Docker构 ...
- Spring Cloud 5分钟搭建教程(附上一个分布式日志系统项目作为参考) - 推荐
http://blog.csdn.net/lc0817/article/details/53266212/ https://github.com/leoChaoGlut/log-sys 上面是我基于S ...
- 记一次feign的问题排查(短路、线程池、队列)
https://www.jianshu.com/p/f7fb59f43485 昨天开了一百个线程采用feign去请求第三方项目,结果报错,出现了短路,大概是下面这样的.(feign整合了hystrix ...
- Hystrix框架4--circuit
circuit 在Hystrix调用服务时,难免会遇到异常,如对方服务不可用,在这种情况下如果仍然不停地调用就是不必要的,在Hystrix中可以配置使用circuit,当达到一定程度错误,就会自动调用 ...
- springcloud(五):熔断监控Hystrix Dashboard和Turbine
Hystrix-dashboard是一款针对Hystrix进行实时监控的工具,通过Hystrix Dashboard我们可以在直观地看到各Hystrix Command的请求响应时间, 请求成功率等数 ...
- 微服务(二)hystrix
特性 1.延迟和失败容忍 防止级联错误,错误回退,优雅降级.快速失败和恢复 线程和信号量隔离 2.实时监控和配置更改 3.并发 并行执行,请求缓存,自动批处理失败请求 总运行流程 当你发出请求后,hy ...
- SpringCloud学习系列之三----- 断路器(Hystrix)和断路器监控(Dashboard)
前言 本篇主要介绍的是SpringCloud中的断路器(Hystrix)和断路器指标看板(Dashboard)的相关使用知识. SpringCloud Hystrix Hystrix 介绍 Netfl ...
- 改造断路器集群监控Hystrix Turbine实现自动注册消费者、实时监控多个服务
在上一篇文章中,我们搭建了Hystrix Dashoard,对指定接口进行监控.但是只能对一个接口进行监听,功能比较局限: Turbine:汇总系统内多个服务的数据并显示到 Hystrix Dashb ...
随机推荐
- laravel 添加验证码
1. 安装依赖 composer require gregwar/captcha 2.使用 use Gregwar\Captcha\CaptchaBuilder; use DB; use Requ ...
- HDU 6038 17多校1 Function(找循环节/环)
Problem Description You are given a permutation a from 0 to n−1 and a permutation b from 0 to m−1. D ...
- 【Python】xpath-1
1.coverage包实现代码覆盖率 (1)pip install coverage (2)coverage run XX.py(测试脚本文件) (3)coverage report -m 在控制台打 ...
- Spring Boot 揭秘与实战(九) 应用监控篇 - HTTP 健康监控
文章目录 1. 内置 HealthIndicator 监控检测 2. 自定义 HealthIndicator 监控检测 3. 源代码 Health 信息是从 ApplicationContext 中所 ...
- 20165228 2017-2018-2 《Java程序设计》第2周学习总结
20165228 2017-2018-2 <Java程序设计>第2周学习总结 教材学习内容总结 标识符:名字 组成:字母/下划线/美元符号或数字 注意:标识符的第一个字符不能是数字字符,且 ...
- WEBBASE篇: 第三篇, CSS知识1
第三篇, CSS知识1 一,CSS 介绍 CSS: Cascading Style Sheets ---样式表 HTML: 搭建网页结构: CSS: 在网页结构基础上进行网页的美化: 二,CSS的使用 ...
- Ubuntu16.04安装tensorflow+安装opencv+安装openslide+安装搜狗输入法
Ubuntu16.04在cuda以及cudnn安装好之后,安装tensorflow,tensorflow以及opencv可以到网上下载对应的安装包并且直接在安装包所在的路径下直接通过pip与conda ...
- Node.js内置的文件系统模块(fs)
异步读取文件 按照js的标准,异步读取一个文本文件的格式如下: 'use strict' const fs = require('fs') fs.readFile('test.txt', 'utf-8 ...
- linux通过安装包安装nginx和jdk
1.安装prce(重定向支持)和openssl(https支持,如果不需要https可以不安装.) yum -y install pcre* yum -y install openssl* 2.下载n ...
- python3.x 正则表达式的应用
正则表达式是我认为比较难的一个东西,今天忽然又学到了这个,想到写下来,以后作为参考手册使用. python如果想使用python需要引用re方法,在文件开始进行引用. import re 接下来说一下 ...