本文为《Spring Cloud微服务实战》一书的摘要总结
服务容错保护:Spring Cloud Hystrix
在分布式架构中,断路器模式 的作用是:当某个服务单元发生故障之后,通过断路器的故障监控,向调用方法返回一个错误响应,而不是长时间的等待。Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护功能。
使用
-
添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
在工程的主内上使用
@EnableCiruitBreaker
注解,开启 断路器 功能这里也可以使用
@SpringCloudApplication
注解来修饰应用主内,@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker public @interface SpringCloudApplication { }
可以看到,
@SpringCloudApplication
这个注解包含了@SpringBootApplication
,@EnableDiscoveryClient
和@EnableCircuitBreaker
这三个主要的注解。 -
在使用
RestTemplate
请求服务的方法上使用@HystrixCommond
注解来指定回调方法:@HystrixCommand(fallbackMethod = "testFailed") public String test() { return restTemplate.getForObject("http://SERVER-HELLO/home",String.class); } public String testFailed(){ return "failed"; }
上面的例子中,当请求服务失败时,就会调用指定的回调方法:
testFailed()
。
原理分析
-
创建
HystrixCommand
或HystrixObservableCommand
对象创建创建
HystrixCommand
或HystrixObservableCommand
对象,用来表示对依赖服务的操作请求,使用 命令模式 来实现对服务调用操作的封装。HystrixCommand
:用在服务返回单个操作结果的时候;HystrixObservableCommand
:用在服务返回多个操作结果的时候。
-
命令执行
HystrixCommand
实现了execute()
和queue()
-
结果是否被缓存
若 当前命令 的请求缓存功能是启用的,并且该命令缓存命中,那么缓存的结果会立即以
Observable
对象的形式返回 -
断路器是否打开
若命令结果没有缓存命中,Hystrix在命令执行前要检查断路器是否为打开状态:
- 若断路器是打开状态,Hystrix不执行命令,转到fallback处理逻辑(第8步)
- 若断路器是关闭状态,Hystrix检查是否有可用资源来执行命令(第5步)
-
线程池/请求队列/信号量是否占满
判断是否有足够的资源来执行命令,若没有,则调到第8步
-
HystrixObservableCommand.construct()或HystrixCommond.run()
Hystrix根据我们编写的方法来决定采用什么样的方式去请求依赖服务。
如果
run
或construct
方法执行失败,会转到第8步;如果方法超时,会抛出TimeoutException
异常,同时转到第8步。如果命令没有抛出异常且返回了结果,那么Hystrix在记录一些日志并采集监控报告之后将结果返回。
-
计算断路器的健康度
Hystrix会将成功、失败、拒绝、超时等信息报告给断路器,而断路器会维护 一组计数器 来统计这些数据。断路器会根据这些数据来决定是否将断路器打开,来对某个依赖服务的请求进行“熔断/短路”
-
fallback处理
当命令执行失败,会进入fallback,尝试回退处理,通常也称该操作为 服务降级。
HystrixCommand
使用getFallback()
实现服务降级,该方法返回HystrixObservableCommand
使用resumeWithFallback()
实现服务降级
引起服务降级的情况:
- 第4步:当前命令处于“熔断/短路”状态,断路器是打开状态;
- 第5步:线程池,请求队列,信号量被占满时;
- 第6步:执行命令抛出异常时
-
返回成功的响应
断路器原理
HystrixCiruitBreaker
:
public interface HystrixCircuitBreaker {
//每个Hystrix命令的请求都通过该方法判断是否被执行
boolean allowRequest();
//返回当前断路器是否打开
boolean isOpen();
//用来闭合断路器
void markSuccess();
//什么都不做的断路器实现,允许所有请求,断路器永远闭合
public static class NoOpCircuitBreaker implements HystrixCircuitBreaker {
public NoOpCircuitBreaker() {
}
public boolean allowRequest() {
return true;
}
public boolean isOpen() {
return false;
}
public void markSuccess() {
}
}
//看下面的详细说明
public static class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker {
...
}
public static class Factory {
//维护着一个Hystrix命令与HystrixCircuitBreaker的关系集合
private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap();
...
}
}
HystrixCircuitBreakerImpl
:
//HystrixCircuitBreaker的一个具体实现
public static class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker {
//断路器对应HystrixCommand实例的属性对象
private final HystrixCommandProperties properties;
//用来让HystrixCommand记录各类度量指标的对象
private final HystrixCommandMetrics metrics;
//断路器是否打开的标志
private AtomicBoolean circuitOpen = new AtomicBoolean(false);
//断路器上次打开或者上次测试的时间戳
private AtomicLong circuitOpenedOrLastTestedTime = new AtomicLong();
public boolean isOpen() {
if (this.circuitOpen.get()) {
//断路器打开标志位True,直接返回
return true;
} else {
//获取度量指标对象中的HealthCounts对象,
//该对象记录了一个滚动时间窗内的请求信息快照,默认为10秒
HealthCounts health = this.metrics.getHealthCounts();
if (health.getTotalRequests() < (long)(Integer)this.properties.circuitBreakerRequestVolumeThreshold().get()) {
//请求总数在阈值内(默认20)
return false;
} else if (health.getErrorPercentage() < (Integer)this.properties.circuitBreakerErrorThresholdPercentage().get()) {
//错误百分比在阈值内(默认为50)
return false;
} else if (this.circuitOpen.compareAndSet(false, true)) {
//上面两个两个条件都不满足,就将断路器设为打开状态
//记录打开时间
//返回true
this.circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
return true;
} else {
//上一个if中是一个原子操作,如果为false,则断路器状态已经时true了,直接返回
return true;
}
}
}
public boolean allowRequest() {
//根据配置对象判断强制打开是否被设置
if ((Boolean)this.properties.circuitBreakerForceOpen().get()) {
//设置了强制打开为true,拒绝任何请求
return false;
} else if ((Boolean)this.properties.circuitBreakerForceClosed().get()) {
//依据条件打开断路器
this.isOpen();
//设置了强制打开为false,允许所有请求
return true;
} else {
//没有设置强制打开属性,一般都会来带这个判断分支
//1.当断路器没有打开,返回true
//2.否则,当前时间已经超过断路器上次打开后的休眠时间,
//此时虽然断路器打开标志位为true,但是可以认为此时断路器处于“半开路状态”,
//允许请求尝试访问,若访问成功,则关闭断路器,访问失败,进入下一个休眠期
return !this.isOpen() || this.allowSingleTest();
}
}
public boolean allowSingleTest() {
long timeCircuitOpenedOrWasLastTested = this.circuitOpenedOrLastTestedTime.get();
//1.断路器为开启状态
//2.满足第一个条件的前提下,当前时间 > 上次开启或测试时间+配置中的circuitBreakerSleepWindowInMilliseconds(断路器打开之后的休眠时间,默认为5秒)
//3.满足前两个条件的前提下,成功将上次次开启或测试时间设置为当前时间
// 满足上面三个条件则返回true,否则返回false
return this.circuitOpen.get() && System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested + (long)(Integer)this.properties.circuitBreakerSleepWindowInMilliseconds().get() && this.circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis());
}
//该方法在断路器在半开路状态下服务请求成功后被调用
public void markSuccess() {
//关闭断路器
if (this.circuitOpen.get() && this.circuitOpen.compareAndSet(true, false)) {
//重置度量指标对象
this.metrics.resetStream();
}
}
}
断路器的详细执行逻辑图:
从图中的描述可以知道:断路器会保存最近10个时间窗口的请求信息快照,当超过十个快照时,会丢弃最久的那个快照。快照中包括一个窗口期内,服务请求成功次数,失败次数,超时次数,和拒绝次数。
依赖隔离
Hystrix使用“舱壁模式”实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现了问题,也不会影响到其它的依赖服务。
通过实现对依赖服务的线程池隔离,带来了一下的优势:
- 应用自身得到完全保护,不会受不可控的依赖服务的影响。
- 可以有效降低接入新服务的风险。
- 当依赖的服务失效回复正常后,它的线程池会被清理并且能够马上回复健康的服务;相比之下,容器级别的清理恢复速度要慢的多。
- 当依赖的服务出现配置错误的时候,线程池会快速反应出此问题。
- 当依赖的服务因实现机制调整等原因造成其性能出现很大变化的时候,线程池的监控指标信息能够反应出这些变化。
Hystrix除了可以使用线程池外,还可以使用 信号量 来控制单个依赖服务的并发度;只需要我们将隔离策略参数execution.isolation.stratege
设置为"SEMAAPHORE"即可。
总之,以来合理可以让我们的应用更加的健壮,不会因为个别的服务出现问题而影响其他的服务。
使用详解
创建Hystrix命令
public class MsgCommand extends HystrixCommand<String> {
private RestTemplate restTemplate;
public MsgCommand(RestTemplate restTemplate) {
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MSGGroup"));
this.restTemplate = restTemplate;
}
//具体的依赖服务调用逻辑
@Override
protected String run() throws Exception {
log.info("run:开始");
String msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
log.info("run:结束");
return msg;
}
}
求情服务
//同步请求
String msg = new MsgCommand(restTemplate).execute();
//异步请求
Future<String> result = new MsgCommand(restTemplate).queue();
msg = result.get();
基于注解的使用
//同步求情
@GetMapping("syn")
@HystrixCommand(fallbackMethod = "testFailed")
public String syn() throws InterruptedException {
String msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
return msg;
}
//异步请求
@GetMapping("asyn")
@HystrixCommand(fallbackMethod = "testFailed")
public String asyn() throws InterruptedException, ExecutionException {
Future<String> result = new AsyncResult<String>() {
@Override
public String invoke() {
log.info("invoke开始");
String msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
log.info("invoke结束");
return msg;
}
@Override
public String get(){
return invoke();
}
};
return result.get();
}
返回多次请求的结果
这里使用的是观察者模式
public class MsgObservableCommand extends HystrixObservableCommand {
private RestTemplate restTemplate;
public MsgObservableCommand(RestTemplate restTemplate) {
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MSGGroup"));
this.restTemplate = restTemplate;
}
//具体的依赖服务调用逻辑
@Override
protected Observable construct() {
return Observable.unsafeCreate(new Observable.OnSubscribe<String>(){
@Override
public void call(Subscriber<? super String> subscriber) {
try{
if (!subscriber.isUnsubscribed()){
String msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
subscriber.onNext(msg + 1);
msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
subscriber.onNext(msg + 2);
subscriber.onCompleted();
}
}catch (Exception e){
subscriber.onError(e);
}
}
});
}
}
定义观察者:
public class MsgSubscriber extends Subscriber {
private String msg;
private boolean completed = false;
public boolean getCompleted(){
return completed;
}
@Override
public void onCompleted() {
this.completed = true;
}
@Override
public void onError(Throwable throwable) {
log.info("onError:");
throwable.printStackTrace();
}
@Override
public void onNext(Object o) {
//做一些关于请求结果o的操作
...
log.info(o);
}
public String getMsg(){
return msg;
}
}
使用:
@GetMapping("syn")
public String syn() throws InterruptedException {
//调用observer()方法,会立即执行请求逻辑,并得到一个Hot Observable,每个订阅者都可以得到全部的请求结果
Observable<String> ho = new MsgObservableCommand(restTemplate).observe();
MsgSubscriber subscriber1 = new MsgSubscriber();
ho.subscribe(subscriber1);
String msg = "";
if (subscriber1.getCompleted()){
msg = subscriber1.getMsg();
log.info("subscriber1:" + msg);
}
MsgSubscriber subscriber2 = new MsgSubscriber();
ho.subscribe(subscriber2);
if (subscriber2.getCompleted()){
msg = subscriber2.getMsg();
log.info("subscriber2:" + msg);
}
return msg;
}
@GetMapping("asyn")
public String asyn() throws InterruptedException, ExecutionException {
//调用toObservable()方法, 不会立即执行请求逻辑,得到一个Cold Observable,只有当被订阅时,才执行请求逻辑
Observable<String> co = new MsgObservableCommand(restTemplate).toObservable();
MsgSubscriber subscriber = new MsgSubscriber();
//订阅,可观察者开始执行请求逻辑
co.subscribe(subscriber);
String msg = "";
if (subscriber1.getCompleted()){
msg = subscriber1.getMsg();
log.info("subscriber:" + msg);
}
return msg;
}
注解方式:
@HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER, fallbackMethod = "testFailed")
public Observable<String> getMsgEager(){
return Observable.create(subscriber -> {
try{
if (!subscriber.isUnsubscribed()){
log.info("getObject1:start");
String msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
log.info("getObject1:finish");
subscriber.onNext(msg + 1);
Thread.sleep(100);
log.info("getObject2:start");
msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
log.info("getObject2:finish");
subscriber.onNext(msg + 2);
subscriber.onCompleted();
}
}catch (Exception e){
subscriber.onError(e);
}
});
}
public class MsgController{
@Autowire
private MsgService service;
@GetMapping("getMsg")
public String getMsg() throws InterruptedException {
Observable<String> ho = service.getMsgEager();
MsgSubscriber subscriber1 = new MsgSubscriber();
ho.subscribe(subscriber1);
String msg = "";
if (subscriber1.getCompleted()){
msg = subscriber1.getMsg();
log.info("subscriber1:" + msg);
}
return msg;
}
}
observableExecutionMode = ObservableExecutionMode.EAGER:对应observer()方法
observableExecutionMode = ObservableExecutionMode.LAZY:对应toObservable();方法
定义服务降级
在HystrixCommand
中,通过重载getFallback
方法来实现服务降级逻辑。在HystrixObservableCommand
中,通过重载resumeWithFallback
方法来实现服务降级逻辑。
public class MsgCommand extends HystrixCommand<String> {
private RestTemplate restTemplate;
public MsgCommand(RestTemplate restTemplate) {
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MSGGroup"));
this.restTemplate = restTemplate;
}
@Override
protected String run() throws Exception {
log.info("run:开始");
String msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
log.info("run:结束");
return msg;
}
//run方法抛出异常时,调用该方法进行服务降级
@Override
protected String getFallback() {
return "failed";
}
}
public class MsgObservableCommand extends HystrixObservableCommand {
private RestTemplate restTemplate;
public MsgObservableCommand(RestTemplate restTemplate) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MSGGroup")));
this.restTemplate = restTemplate;
}
@Override
protected Observable construct() {
return Observable.unsafeCreate(new Observable.OnSubscribe<String>(){
@Override
public void call(Subscriber<? super String> subscriber) {
try{
if (!subscriber.isUnsubscribed()){
log.info("getObject1:start");
String msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
log.info("getObject1:finish");
subscriber.onNext(msg + 1);
// log.info("getObject:start");
Thread.sleep(100);
log.info("getObject2:start");
msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
log.info("getObject2:finish");
subscriber.onNext(msg + 2);
subscriber.onCompleted();
}
}catch (Exception e){
subscriber.onError(e);
}
}
});
}
//当命令执行失败时,Hystrix会将调用该方法返回失败时的被观察者,该被观察者会将自身的信息通着给所有的观察者
@Override
protected Observable resumeWithFallback() {
return Observable.unsafeCreate(new Observable.OnSubscribe<String>(){
@Override
public void call(Subscriber<? super String> subscriber) {
try{
if (!subscriber.isUnsubscribed()){
subscriber.onNext("failed");
subscriber.onCompleted();
}
}catch (Exception e){
subscriber.onError(e);
}
}
});
}
}
使用注解:
@HystrixCommand(fallbackMethod = "failedMethod")
public Observable<String> getMsgEager(){
return Observable.create(subscriber -> {
try{
if (!subscriber.isUnsubscribed()){
log.info("getObject1:start");
String msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
log.info("getObject1:finish");
subscriber.onNext(msg + 1);
Thread.sleep(100);
log.info("getObject2:start");
msg = restTemplate.getForObject("http://SERVER-HELLO/home",String.class);
log.info("getObject2:finish");
subscriber.onNext(msg + 2);
subscriber.onCompleted();
}
}catch (Exception e){
subscriber.onError(e);
}
});
}
public String failedMethod(){
return "faild";
}
只需要指定fallbackMethod
参数即可,它的值为服务降级逻辑方法的方法名,该方法应该与具体命令定义在一个类中,且方法的入参,返回值与请求逻辑方法要一样。
异常处理
在HystrixCommand
中实现的run
方法中抛出异常时,除了HystrixBadRequestException
之外,其他的任何异常均会被认为执行失败并处罚服务降级的处理逻辑。在使用注解配置实现Hystrix命令是,还支持忽略指定异常类型的功能:
@HystrixCommand(fallbackMethod = "failedMethod",ingnoreExceptions={BadRequestException.class})
public Observable<String> getMsgEager(){
...
}
上面的例子中,当getMsgEager抛出BadRequestException类型的异常时,Hystrix会将它封装在HystrixBadRequestException
中抛出,这样就不会触发服务降级了。
在服务降级逻辑中,我们可能需要根据异常的类型做不同的处理。在使用继承方式的Hystrix时,我们可以在getExecutionException()
方法来获取异常。
@Override
protected String getFallback() {
Throwable throwable = getExecutionException();
return throwable.getMessage();
}
//或者是:
@Override
protected Observable resumeWithFallback() {
Throwable throwable = getExecutionException();
return Observable.unsafeCreate(new Observable.OnSubscribe<String>(){
@Override
public void call(Subscriber<? super String> subscriber) {
try{
if (!subscriber.isUnsubscribed()){
subscriber.onNext(throwable.getMessage());
subscriber.onCompleted();
}
}catch (Exception e){
subscriber.onError(e);
}
}
});
}
命令名称、分组以及线程池
我们采用继承的方式实现HystrixCommand
时,在在构造函数中通过Setter
静态类设置了命令的名称:
public MsgCommand(RestTemplate restTemplate) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MSGGroup")));
this.restTemplate = restTemplate;
}
上面的例子只为命令设置了组名,并没有设置命令名称,此时命令的名称为类名,Hystrix会按照组名为该命令分配线程池(相同组名的命令使用一个线程池)。
public MsgCommand(RestTemplate restTemplate) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MSGGroup")).andCommandKey(HystrixCommandKey.Factory.asKey("msgCommand")));
this.restTemplate = restTemplate;
}
此时虽然为命令设置了名称,但是Hystrix依然使用组名为其分配线程池。
public MsgCommand(RestTemplate restTemplate) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MSGGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("msgCommand"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("threadPoolKey")));
this.restTemplate = restTemplate;
}
上面的例子中我们为命令指定了线程池名,此时Hystrix将使用ThreadPoolKey为其分配线程池。
使用注解方式:
@HystrixCommand(fallbackMethod = "failedMethod",ingnoreExceptions={BadRequestException.class}, groupKey="MsgGroup",commandKey="getMsgEager",threadPoolKey="getMsgEagerThread")
public Observable<String> getMsgEager(){
...
}
分别使用groupKey
,commandKey
和threadPoolKey
来指定。
请求缓存
实现HystrixCommand
和HystrixObservableCommand
时,重载getCacheKey()
方法,我们就可以开启请求缓存了。
public class UserCommand extends HystrixCommand<String> {
private int id;
private RestTemplate restTemplate;
public UserCommand(int id, RestTemplate restTemplate) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("USER")));
this.id = id;
this.restTemplate = restTemplate;
}
@Override
protected String run() throws Exception {
return restTemplate.getForObject("http://SERVER-USER/"+id,String.class);
}
@Override
protected String getFallback() {
return getExecutionException().getMessage();
}
@Override
protected String getCacheKey() {
return String.valueOf(id);
}
}
public String getUserById(Integer id){
//初始化HystrixRequestContext
//如果只在一个方法中初始化的话,那么请求缓存的作用域只能是这一个请求,我们应该实现Request级别的上下文
HystrixRequestContext hystrixRequestContext = HystrixRequestContext.initializeContext();
String user = new UserCommand(id,restTemplate).execute();
//第二次将直接从缓存获取结果
String user2 = new UserCommand(id,restTemplate).execute();
hystrixRequestContext.close();
return user;
}
通过开启请求缓存,可以让我们实现的Hystrix命令具备下面几个好处:
- 减少重复的请求数,减低依赖服务的并发度;
- 在同一用户请求的上下文中,相同依赖服务返回的数据始终保持一致;
- 请求缓存在
run
,construct
方法执行之前生效,可以减少不必要的线程开销。
清理失效缓存
如果请求缓存是,只是读操作,不比关系缓存内容是否正确的问题;但是如果请求命令中有更新数据的操作,那么缓存中的数据就需要我们在进行些操作时对缓存数据进行清理,
以防读命令读去到失效的内容。
public class UserCommand extends HystrixCommand<String> {
private int id;
private RestTemplate restTemplate;
private static final HystrixCommandKey GETTER_KEY = HystrixCommandKey.Factory.asKey("CommandKey");
public UserCommand(int id, RestTemplate restTemplate) {
//添加CommandKey
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("USER").andCommandKey(GETTER_KEY)).andCommandKey(GETTER_KEY));
this.id = id;
this.restTemplate = restTemplate;
}
...
public static void flushCache(int id){
log.info("flushCache");
//通过CommandKey获取缓存实例
HystrixRequestCache.getInstance(GETTER_KEY, HystrixConcurrencyStrategyDefault.getInstance()).clear(String.valueOf(id));
}
}
通过注解实现请求缓存
@CacheResult //必须与@HystrixCommand绑定使用,缓存请求结果
@HystrixCommand(groupKey="USER",commandKey="getUser")
public User getUserById(@CacheKey("id") int id){//指定缓存的Key值
return restTemplate.getForObject("http://USER_SERVICE/user/{1}",User.class,id);
}
@CacheRemove(commandKey="getUser") //清除缓存,需要指定commandKey
@HystrixCommand(groupKey="USER",commandKey="getUser")
public void updateUser(@CacheKey("id") User user){//指定缓存Id为user对象的id属性
restTemplate.postForObject("http://USER_SERVICE/user",user);
}
在指定缓存Id时,我们还可以使用cacheKeyMethod
注解属性:
@Autowired
private RestTemplate restTemplate;
@CacheResult(cacheKeyMethod = "getUserByIdCacheKey") //它会将getUserById的入参传给getUserByIdCacheKey
@HystrixCommand(commandKey = "getUserById")
public User getUserById(int id){
return restTemplate.getForObject("http://SERVER-HELLO/"+id,User.class);
}
@CacheRemove(commandKey = "getUserById", cacheKeyMethod = "cleanCacheCacheKey")
@HystrixCommand
public void cleanCache(User user){
log.info("clean cache");
}
//获取CacheId的方法必须返回String类型
private String getUserByIdCacheKey(int id){
log.info("cacheKey:" + id);
return String.valueOf(id);
}
private String cleanCacheCacheKey(User user){
return String.valueOf(user.getEmp_no());
}
请求合并
HystrixCollapser
实现了在HystrixCommand
之前放置一个 合并处理器 ,将处于一个很短时间窗(默认10毫秒)内对 同一依赖服务 的多个请求进行整合并以批量的方式发情请求的功能(要求服务方也要提供批量操作的服务)。
public abstract class HystrixCollapser<BatchReturnType, ResponseType, RequestArgumentType> implements HystrixExecutable<ResponseType>, HystrixObservable<ResponseType> {
...
public abstract RequestArgumentType getRequestArgument();
protected abstract HystrixCommand<BatchReturnType> createCommand(Collection<HystrixCollapser.CollapsedRequest<ResponseType, RequestArgumentType>> var1);
protected abstract void mapResponseToRequests(BatchReturnType var1, Collection<HystrixCollapser.CollapsedRequest<ResponseType, RequestArgumentType>> var2);
}
HystrixCollapser
指定了三个类型参数:
BatchReturnType
:合并后,批量请求的返回类型ResponseType
: 单个请求的返回类型RequestArgumentType
:请求参数的类型
三个抽象方法:
getRequestArgument
:定义获取请求参数的方法createCommand
:定义合并请求,产生批量命令的方法mapResponseToRequests
:定义对批量命令返回结果的处理————实现对批量结果进行拆分,并传递给合并前的各个原子请求命令的逻辑
注解形式
@Service
public UserService {
@HystrixCollapser(batchMethod = "findAll",
collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "100")}//设置合并的时间窗为100毫秒
,scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL //一次请求所有线程的所有服务请求进行合并
)
@Override
public Future<Map<String,Object>> find(String id) {
return null;
}
@HystrixCommand(fallbackMethod = "failed")
@Override
public List<Map<String,Object>> findAll(List<String> ids) {
log.info("合并"+ ids.toString());
return restTemplate.getForObject("http://SERVER-HELLO/ids/"+StringUtils.join(ids,","),List.class);
}
@HystrixCollapser(batchMethod = "findAll",
collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds",value = "100")}
,scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL
)
@Override
public Map<String, Object> find2(String id) {
return null;
}
public List<Map<String,Object>> failed(List<String> ids){
log.info("出错了!!!");
return null;
}
}
public void getUser(){
String[] idArray = {"10001","10002","10003","10004"};
Future[] result = new Future[idArray.length];
result[0] = userService.find(idArray[0]);
result[1] = userService.find(idArray[1]);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
result[2] = userService.find(idArray[2]);
result[3] = userService.find(idArray[3]);
}
});
Thread.sleep(200);//超过了时间窗口,子线程中的请求将不会与前面两个请求合并
thread.start();
Map<String, Object> user = null;
for (Future<Map<String,Object>> u : result){
user = u.get();
log.info(user.toString());
}
}
public void getUser2(){
HystrixRequestContext requestContext = HystrixRequestContext.initializeContext();
String[] idArray = {"10001","10002","10003","10004"};
Map<String, Object> user = null;
for (String id : idArray){
user = userService.find2(id); //单个请求service返回的类型不是Future,而是原对象,那么便是同步请求,即使在时间窗内,请求也不会合并
}
requestContext.close();
return user;
}
Hystrix 属性
Hystrix 仪表盘
创建Hystrix dashboard
- 新建SpringBoot项目
- 在项目中加入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 在应用主类上加上
@EnableHystrixDashboard
- 启动项目
如果dashboard包的版本是2.2.3的话,需要修改spring-cloud-netflix-hystrix-dashboard-2.2.3.RELEASE.jar\templates\hystrix\monitor.ftlh:
$(window).load(function()
改为:
$(window).on('load',function()
监控单个应用
- 被监控应用需要加入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 被监控应用需要暴露hystrix stram:
management:
endpoints:
web:
exposure:
include:
- info
- health
- hystrix.stream
-
调用一次被监控应用的服务
-
访问仪表板:http://localhost:9090/hystrix
-
在输入框输入型如:https://hystrix-app:port/actuator/hystrix.stream 的url,然后开始监控应用
Turbine集群监控
- 创建一个Turbine应用,加入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
-
在应用的主类上加上
@EnableDiscoveryClient
和@EnableTurbine
注解 -
在配置文件中配置:
turbine:
app-config: CONSUMER #想要监控的应用名
cluster-name-expression: new String("default") # 指定集群名称为default,若有多个Turbine服务,则可以通过该属性来区分
combine-host-port: true # 让同一主机上的服务通过主机名与端口号来区分,默认使用host来区分