目录
Hystrix介绍
spring cloud 用的是 hystrix,是一个容错组件。
Hystrix实现了 超时机制和断路器模式。
主要实现 降级,限流于隔离,熔断三个功能。
Hystrix是Netflix开源的一个类库,用于隔离远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。主要有以下几点功能:
-
为系统提供保护机制。在依赖的服务出现高延迟或失败时,为系统提供保护和控制。
-
防止雪崩。
-
包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中运行。
-
跳闸机制:当某服务失败率达到一定的阈值时,Hystrix可以自动跳闸,停止请求该服务一段时间。
-
资源隔离:Hystrix为每个请求都的依赖都维护了一个小型线程池,如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。防止级联失败。
-
快速失败:Fail Fast。同时能快速恢复。侧重点是:(不去真正的请求服务,发生异常再返回),而是直接失败。
-
监控:Hystrix可以实时监控运行指标和配置的变化,提供近实时的监控、报警、运维控制。
-
回退机制:fallback,当请求失败、超时、被拒绝,或当断路器被打开时,执行回退逻辑。回退逻辑我们自定义,提供优雅的服务降级。
-
自我修复:断路器打开一段时间后,会自动进入“半开”状态,可以进行打开,关闭,半开状态的转换。前面有介绍。
自己理解的Hystrix
防止雪崩
为什么会出现雪崩呢?
系统A 调用 系统B,系统B调用系统C。。。。,一连串的远程调用
如果所有的请求都在一个线程池里处理,比如说A调用B,B调用C,在系统C中,所有的请求都在一个线程池中进行处理,
比如说系统C中有一个开户的服务c1,当高并发的时候,大量调用c1 服务,导致所以的线程都在处理调用c1的请求,
当其他的请求过来,比如B又调用C的c2的服务接口,此时线程池中已经没有多余的线程了,c2服务就无法处理,那么B系统就只能等待,A系统也只能等待,
这样的就会引起连锁反应,所有的系统都会收到影响,进而造成雪崩。
以上都有什么问题呢
1. 一个系统会影响其他的系统。保护系统
2. 一个接口会影响其他的接口。保护每个接口对立运行,互不影响
3. 用户体验,不能让用户一直等待,然后timeOut吧
推出Hystrix
Hystrix的解决方案
- 降级
在高并发的情况下,如果网络状况不好,用户等待时间过长,用户体验不好,或者服务异常等,就会降级操作,返回一个比较好的备用方案。降级的作用是返回一个凑合能用的结果。
- 熔断
在高并发的情况下,如果请求数量达到一定极限后,为了保护服务,避免引起其他系统的一连串问题,就会进行熔断,直接拒绝访问,让后通过降级,返回一个备用方案。
和保险丝一样,当电流过载的时候,为了避免导致一连串更大的问题,就会熔断保险丝。
- 限流和隔离
服务隔离,是对于接口级的保护,保护接口和接口之间互不影响,独立运行。
因为在默认情况下,一个线程池会维护一个系统所以的服务接口,为了防止高并发情况下对一个服务接口的大量访问,占用大量的资源,导致其他的接口无法调用问题,需要对服务隔离,每个服务接口都有自己的线程池,相互之间互不影响,解决雪崩。逼仓模式
服务限流。近地铁的时候不是经常采取限流么。就是当大流量的请求过来时,一定要采取限流的措施。每次保证一定数量的请求通过。
Hystrix的运用
Hystrix 自己实现的猜想
/**
*
* try{
* 服务降级
* 1. 发起请求到服务方,
* 1.1 判断是否超时,如果超时了,进入1.2
* 1.2 判断其他的方法器是否超时,如果也超时了,进入catch,备用方案
* 1.3 调用服务出现异常
*
* 服务隔离
*
* 某个具体的服务有大量的访问时,每个请求都会开启一个线程,
* map(请求uri,线程数量),在线程池中进行操作。所以相同的请求,限定一个阈值(阀值),将每个具体的服务接口隔离,每个不同的请求都已自己的线程池。
*
* if(阈值){
* throw new Excetpion()
* }
*
* 服务熔断
* 每次请求失败,都会记一个失败数,count++,当失败次数达到一定的值是,直接熔断
* 以后发过来的请求都会直接走throw
* if(count++>10){
* throw new Exception()
* }
*
* 服务熔断,请求的状态,开/关/半开
* 开,请求
* 关,不请求,
* 半开,隔一段时间发一次试一试,如果发现请求成功看,那么就开请求。同时重置一下count。
*
* 如果有服务消费者有多个,相互之间不需要通知,一个调不通,并不代表另一个也调不通。
* 总结:
* Hystrix并不是用Try。catch实现的。通过代理的方式实现。
*
* }catch(Exception e){
*
* 备用方案:
* 1. 返回一个友好的提示,页面等
* 2. 返回一个其他的备用方案。
* }
*
*
*
*
*/
1. Hystrix 整合RestTemplate
- Hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 启动类注解
@EnableHystrix,该注解是必须的,在使用RestTemplate调用远程服务的时候。
如果用Feign则可以不需要该注解。因为Feign已经默认集成 了Hystrix
这两个是没有冲突的。在用Feign的时候也是可以配置该注解的。
- 代码
这里进行了业务代码的分层,
控制层
@RestController
public class AController {
@Autowired
UserService userService;
@RequestMapping("user")
public String getUser(){
String user = userService.getUser();
return user;
}
}
服务层,在服务层进行远程调用。
利用@Hystrixcommand注解,如果调用失败,fallbackMethod 指向备用的方法。back
package com.example.consumer;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Component
public class UserService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "back")
public String getUser(){
String url = "http://provider/getUser";
String forObject = restTemplate.getForObject(url, String.class);
return forObject;
}
public String back(){
return "RestTemplate降级处理";
}
}
2. Hystrix 整合 Feign
- 依赖包同上
- Feign开启hystrix配置
虽然使用Feign整合Hystrix的时候不需要在启动类上配置@EnaleHystrix注解,但是启动hystrix。
Feign集成了Hystrix,但是默认是关闭的。需要开启
#Feign开启hystrix
feign.hystrix.enabled=true
- 代码1
由于Feign是直接配置在接口上的,所以不能向整合TestTemplate一样,只作用在一个接口url上,指定一个备份的方法。
而Feign则是指定一个实现类。
这里指定了一个实现类。fallback=FallbackService.class.
package com.example.consumer;
import com.example.service.FallbackService;
import com.example.userapi.UserApi;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
@FeignClient(name = "provider",fallback = FallbackService.class)
public interface AppInter extends UserApi {
@RequestMapping("/helloOne")
public String getHelloOne();
@RequestMapping(value="/helloTwo")
public String getHelloTwo(@RequestParam String name);
@RequestMapping(value="/helloThree")
public String getHelloThree(@RequestBody String name);
@RequestMapping("/user")
public String getUserbyId(@RequestParam String id);
}
创建FallbackService类实现AppInter接口。实现接口中的方法,作为调用远程服务的备份方法。
package com.example.consumer;
import com.example.consumer.AppInter;
import org.springframework.stereotype.Component;
@Component
public class FallbackService implements AppInter {
@Override
public String getHelloOne() {
return "我是helloOne调用的降级";
}
@Override
public String getHelloTwo(String name) {
return null;
}
@Override
public String getHelloThree(String name) {
return null;
}
@Override
public String getUserbyId(String id) {
return null;
}
@Override
public String alive() {
return null;
}
}
在启动的时候会报一个错
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'com.example.consumer.AppInter' method
com.sun.proxy.$Proxy114#getHelloOne()
to { /user/helloOne}: There is already 'fallbackService' bean method
com.example.consumer.FallbackService#getHelloOne() mapped.报错的原因是什么的呢?
是因为引用的API里在类上面配置了@RequestMapping的问题,如下。这个问题目前是Hystrix框架的一个bug。目前还没解决。
现在咱们可以先去掉类上配置的@RequestMapping。
去掉之后,重新打包一下,就可以了。
这中引入API的这种方法,其实不推荐使用。维护起来也并不方便。
package com.example.userapi; import org.springframework.web.bind.annotation.RequestMapping; @RequestMapping("user") public interface UserApi { @RequestMapping("alive") public String alive(); }
总结:
以上方法就实现的Feign的Hystrix。
但是,这个降级操作是当调用服务的时候发生异常就会进行。
我们就不清楚报错的信息到底是什么,我们怎么获取报错信息呢。
- 代码2.
除了@FeignClient(name = "provider",fallback = FallbackService.class),还有一个@FeignClient(name = "provider",fallbackFactory = FallbackService.class)。
除了 fallback 还可以使用 fallbackFactory
package com.example.consumer;
import com.example.userapi.UserApi;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
//@FeignClient(name = "provider",fallback = FallbackService.class)
@FeignClient(name = "provider",fallbackFactory = FallbackFactoryService.class)
public interface AppInter extends UserApi {
@RequestMapping("/helloOne")
public String getHelloOne();
@RequestMapping(value="/helloTwo")
public String getHelloTwo(@RequestParam String name);
@RequestMapping(value="/helloThree")
public String getHelloThree(@RequestBody String name);
@RequestMapping("/user")
public String getUserbyId(@RequestParam String id);
}
创建FallbackFactoryService类,实现的FallbackFactory接口。
这里我们可以打一下异常的栈信息,这种方式可以根据报错信息进行具体的分析,做最好的降级处理。
package com.example.consumer;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpServerErrorException;
@Service
public class FallbackFactoryService implements FallbackFactory<AppInter> {
@Override
public AppInter create(Throwable throwable) {
return new AppInter() {
@Override
public String getHelloOne() {
throwable.printStackTrace();
if(throwable instanceof HttpServerErrorException.InternalServerError) {
System.out.println("InternalServerError");
return "远程服务报错";
}else if(throwable instanceof RuntimeException) {
return "请求时异常:" + throwable;
}else {
return "这是helloOne接口的降级,FallbackFactory方式";
}
}
@Override
public String getHelloTwo(String name) {
return null;
}
@Override
public String getHelloThree(String name) {
return null;
}
@Override
public String getUserbyId(String id) {
return null;
}
@Override
public String alive() {
return null;
}
};
}
}
- 降级,fallback
这里的并不是说仅仅返回一个友好的错误提示之类的东西。
这里的fallback是需要进行更合理的处理的。最好是返回一个能用的数据。
例如一个调用列表查询的接口,如果发生异常,需要降级。并不能仅仅返回一个提示之类的的东西。
需要返回的时确实能有的一个列表数据,当然这些数据可以不是最新的。但必须有这么一些数据,还需要组装成页面需要的结构,否则页面就不能够正常的展示了。这个很重要。
信号量隔离与线程池隔离
Hystrix隔离的好处
概念中的舱壁模式。想一下货船上,每个货仓中间的隔离。两个好处:
-
服务提供者高延迟或异常,不会影响到整个系统的失败。
-
能够控制每个调用者的并发度。因为有独立的线程池。
Hystrix的隔离策略,线程隔离和信号量隔离,即“THREAD”和“SEMAPHORE” semaphore
- THREAD(线程隔离):
使用该方式,HystrixCommand将会在单独的线程上执行,并发请求受线程池中线程数量的限制。不同服务通过使用不同线程池,彼此间将不受影响,达到隔离效果。
此种隔离方式:将调用服务线程与服务访问的执行线程分割开来,调用线程能够空出来去做其他工作,而不至于因为服务调用的执行,阻塞过长时间。
hystrix将使用独立的线程池对应每一个服务提供者,用于隔离和限制这些服务。于是某个服务提供者的高延迟或者资源受限只会发生在该服务提供者对应的线程池中。
- SEMAPHORE(信号量隔离):
其实就是个计数器,使用该方式,HystrixCommand将会在调用线程上执行,通过信号量限制单个服务提供者的并发量,开销相对较小(因为不用那么多线程池),并发请求受到信号量个数的限制。 线程隔离会带来线程开销,有些场景(比如无网络请求场景)可能会因为用开销换隔离得不偿失,为此hystrix提供了信号量隔离,当服务的并发数大于信号量阈值时将进入fallback
- 默认隔离
Hystrix中默认并且推荐使用线程隔离(THREAD), 一般来说,只有当调用负载异常高时(例如每个实例每秒调用数百次)才需要信号量隔离,因为这种场景下使用THREAD开销会比较高。信号量隔离一般仅适用于非网络调用的隔离。
正常情况下,默认为线程隔离, 保持默认即可。
- 取舍:
线程池和信号量都支持熔断和限流。相比线程池,信号量不需要线程切换,因此避免了不必要的开销。但是信号量不支持异步,也不支持超时,也就是说当所请求的服务不可用时,信号量会控制超过限制的请求立即返回,但是已经持有信号量的线程只能等待服务响应或从超时中返回,即可能出现长时间等待。线程池模式下,当超过指定时间未响应的服务,Hystrix会通过响应中断的方式通知线程立即结束并返回。
所有的配置都是在客户端的,提供者不主动,不拒绝,不负责。这就是微服务。啥也不干,就躺着,你自己来。
Hystrix的隔离策略默认是Thread线程池隔离的。
那我们应该如果监控呢,这里需要引入Hystrixt Dashboard监控
Hystrix Dashboard
Hystrix提供了对于微服务调用状态的监控信息,但是需要结合spring-boot-actuator模块一起使用。
Hystrix Dashboard主要用来实时监控Hystrix的各项指标信息。通过Hystrix Dashboard反馈的实时信息,可以帮助我们快速发现系统中存在的问题。
- 依赖包,需要和actuator一起使用进行监控。
<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
- 启动项目
健康上报,监控,当然需要将actuator的端点打开,
#actuator开启所有端点 management.endpoints.web.exposure.include=*
浏览器中输入:http://localhost:90/actuator/hystrix.stream,可以进行监控,
如果访问结果为ping: ......
那就发一笔调用,就能看到
data: {"type":"HystrixCommand","name":"AppInter#getHelloOne()","group":"provider","currentTime":1615956693116,"isCircuitBreakerOpen":false,"errorPercentage":0,"errorCount":0,"requestCount":0,"rollingCountBadRequests":0,"rollingCountCollapsedRequests":0,"rollingCountEmit":0,"rollingCountExceptionsThrown":0,"rollingCountFailure":0,"rollingCountFallbackEmit":0,"rollingCountFallbackFailure":0,"rollingCountFallbackMissing":0,"rollingCountFallbackRejection":0,"rollingCountFallbackSuccess":0,"rollingCountResponsesFromCache":0,"rollingCountSemaphoreRejected":0,"rollingCountShortCircuited":0,"rollingCountSuccess":0,"rollingCountThreadPoolRejected":0,"rollingCountTimeout":0,"currentConcurrentExecutionCount":1,"rollingMaxConcurrentExecutionCount":0,"latencyExecute_mean":0,"latencyExecute":{"0":0,"25":0,"50":0,"75":0,"90":0,"95":0,"99":0,"99.5":0,"100":0},"latencyTotal_mean":0,"latencyTotal":{"0":0,"25":0,"50":0,"75":0,"90":0,"95":0,"99":0,"99.5":0,"100":0},"propertyValue_circuitBreakerRequestVolumeThreshold":20,"propertyValue_circuitBreakerSleepWindowInMilliseconds":5000,"propertyValue_circuitBreakerErrorThresholdPercentage":50,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_circuitBreakerForceClosed":false,"propertyValue_circuitBreakerEnabled":true,"propertyValue_executionIsolationStrategy":"THREAD","propertyValue_executionIsolationThreadTimeoutInMilliseconds":1000,"propertyValue_executionTimeoutInMilliseconds":1000,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"propertyValue_executionIsolationThreadPoolKeyOverride":null,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":10,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":10,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,"propertyValue_requestCacheEnabled":true,"propertyValue_requestLogEnabled":true,"reportingHosts":1,"threadPool":"provider"}
如果看的不是很清楚,可以使用图形化界面
浏览器输入:http://localhost:90/hystrix,这里的ip和端口输入自己的,这hystrix的界面,并将http://localhost:90/actuator/hystrix.stream输入到输入栏中,点击monitor Stream
出现一下页面,Thread Pools 就是线程池监控
线程池隔离
默认是Hystrix的线程池隔离,以上就是线程池隔离的配置
信号量隔离 semaphore
1. 依赖包,启动类同上
2. 修改hystrix的隔离策略,配置里配置给命令
hystrix.command.default.execution.isolation.strategy=SEMAPHORE
3. 监控
4. 如果使用dashboard 监控,发现图形话界面一个是loading状态。
确保配置无误的话,那就是spring cloud版本的问题
将spring Cloud版本调低一点。Hoxton.SR3是可以使用的,我原来用的是Hoxton.SR10,就不行。
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
对比
隔离方式 | 是否支持超时 | 是否支持熔断 | 隔离原理 | 是否是异步调用 | 资源消耗 |
线程池隔离 | 支持,可直接返回 | 支持,当线程池到达maxSize后,再请求会触发fallback接口进行熔断 | 每个服务单独用线程池 | 可以是异步,也可以是同步。看调用的方法 | 大,大量线程的上下文切换,容易造成机器负载高 |
信号量隔离 | 不支持,如果阻塞,只能通过调用协议(如:socket超时才能返回) | 支持,当信号量达到maxConcurrentRequests后。再请求会触发fallback | 通过信号量的计数器 | 同步调用,不支持异步 | 小,只是个计数器 |