如需转载,请根据 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 许可,附上本文作者及链接。
本文作者: 执笔成念
作者昵称: zbcn
本文链接: https://1363653611.github.io/zbcn.github.io/2021/01/04/springcloud-04Hystrix/
springcloud Hystrix 容器容错保护
Spring Cloud Hystrix 是Spring Cloud Netflix 子项目的核心组件之一,具有服务容错及线程隔离等一系列服务保护功能,本文将对其用法进行详细介绍。
Hystrix 简介
在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。Hystrix实现了断路器模式,当某个服务发生故障时,通过断路器的监控,给调用方返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。
Hystrix具备:
- 服务降级、
- 服务熔断、
- 线程隔离、
- 请求缓存、
- 请求合并
- 服务监控
- 。。。。等强大功能。
创建 一个Hystrix-service 模块
这里我们创建一个hystrix-service模块来演示hystrix的常用功能。
在pom 中添加相关依赖
1 | <dependency> |
2 | <groupId>org.springframework.cloud</groupId> |
3 | <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> |
4 | </dependency> |
5 | <dependency> |
6 | <groupId>org.springframework.cloud</groupId> |
7 | <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> |
8 | </dependency> |
9 | <dependency> |
10 | <groupId>org.springframework.boot</groupId> |
11 | <artifactId>spring-boot-starter-web</artifactId> |
12 | </dependency> |
在 application.yml 中添加配置
主要是配置了端口、注册中心地址及user-service的调用路径。
1 | server: |
2 | port: 8401 |
3 | spring: |
4 | application: |
5 | name: hystrix-service |
6 | eureka: |
7 | client: |
8 | register-with-eureka: true |
9 | fetch-registry: true |
10 | service-url: |
11 | defaultZone: http://localhost:8001/eureka/ |
12 | service-url: |
13 | user-service: http://user-service |
在启动类上添加 @EnableCircuitBreaker来开启Hystrix的断路器功能
1 |
|
2 |
|
3 |
|
4 | public class HystrixServiceApplication { |
5 | |
6 | public static void main(String[] args) { |
7 | SpringApplication.run(HystrixServiceApplication.class, args); |
8 | } |
创建UserHystrixController接口用于调用user-service服务
服务降级演示
- 在UserHystrixController中添加用于测试服务降级的接口:
1 | "/testFallback/{id}") ( |
2 | public CommonResult testFallback(@PathVariable Long id) { |
3 | return userService.getUser(id); |
4 | } |
- 在UserService中添加调用方法与服务降级方法,方法上需要添加@HystrixCommand注解:
1 | "getDefaultUser") (fallbackMethod = |
2 | public CommonResult getUser(Long id) { |
3 | return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id); |
4 | } |
5 | |
6 | public CommonResult getDefaultUser(@PathVariable Long id) { |
7 | User defaultUser = new User(-1L, "defaultUser", "123456"); |
8 | return new CommonResult<>(defaultUser); |
9 | } |
启动eureka-server、user-service、hystrix-service服务;
关闭user-service服务重新测试该接口,发现已经发生了服务降级:
@HystrixCommand详解
@HystrixCommand 常用参数
- fallbackMethod:指定服务降级处理方法;
- ignoreExceptions:忽略某些异常,不发生服务降级;
- commandKey:命令名称,用于区分不同的命令;
- groupKey:分组名称,Hystrix会根据不同的分组来统计命令的告警及仪表盘信息;
- threadPoolKey:线程池名称,用于划分线程池。
设置命令、分组及线程池名称
- 在UserHystrixController中添加测试接口:
1 | "/testCommand/{id}") ( |
2 | public CommonResult testCommand(@PathVariable Long id) { |
3 | return userService.getUserCommand(id); |
4 | } |
使用ignoreExceptions忽略某些异常降级
- 在UserHystrixController中添加测试接口:
1 | "/testException/{id}") ( |
2 | public CommonResult testException(@PathVariable Long id) { |
3 | return userService.getUserException(id); |
4 | } |
在UserService中添加实现方法,这里忽略了NullPointerException,当id为1时抛出IndexOutOfBoundsException,id为2时抛出NullPointerException:
1
"getDefaultUser2", ignoreExceptions = {NullPointerException.class}) (fallbackMethod =
2
public ResponseResult getUserException(Long id) {
3
if (id == 1) {
4
throw new IndexOutOfBoundsException();
5
} else if (id == 2) {
6
throw new NullPointerException();
7
}
8
return restTemplate.getForObject(userServiceUrl + "/user/{1}", ResponseResult.class, id);
9
}
10
11
public ResponseResult getDefaultUser2(@PathVariable Long id, Throwable e) {
12
log.error("getDefaultUser2 id:{},throwable class:{}", id, e.getClass());
13
return commonResult();
14
}
Hystrix的请求缓存
当系统并发量越来越大时,我们需要使用缓存来优化系统,达到减轻并发请求线程数,提供响应速度的效果。
相关注解
- @CacheResult:开启缓存,默认所有参数作为缓存的key,cacheKeyMethod可以通过返回String类型的方法指定key;
- @CacheKey:指定缓存的key,可以指定参数或指定参数中的属性值为缓存key,cacheKeyMethod还可以通过返回String类型的方法指定;
- @CacheRemove:移除缓存,需要指定commandKey。
测试缓存
- 在UserHystrixController中添加使用缓存的测试接口,直接调用三次getUserCache方法:
1 | "/testCache/{id}") ( |
2 | public ResponseResult testCache(@PathVariable Long id) { |
3 | userService.getUserCache(id); |
4 | userService.getUserCache(id); |
5 | userService.getUserCache(id); |
6 | return ResponseResult.success("操作成功"); |
7 | } |
- 在UserService中添加具有缓存功能的getUserCache方法:
1 | "getCacheKey") (cacheKeyMethod = |
2 | "getDefaultUser", commandKey = "getUserCache") (fallbackMethod = |
3 | public ResponseResult getUserCache(Long id) { |
4 | log.info("getUserCache id:{}", id); |
5 | return restTemplate.getForObject(userServiceUrl + "/user/{1}", ResponseResult.class, id); |
6 | } |
7 | |
8 | /** |
9 | * 为缓存生成key的方法 |
10 | */ |
11 | public String getCacheKey(Long id) { |
12 | return String.valueOf(id); |
13 | } |
测试移除缓存
- 在UserHystrixController中添加移除缓存的测试接口,调用一次removeCache方法:
1 | "/testRemoveCache/{id}") ( |
2 | public ResponseResult testRemoveCache(@PathVariable Long id) { |
3 | userService.getUserCache(id); |
4 | userService.removeCache(id); |
5 | userService.getUserCache(id); |
6 | return ResponseResult.success("操作成功"); |
7 | } |
- 在UserService中添加具有移除缓存功能的removeCache方法:
1 | "getUserCache", cacheKeyMethod = "getCacheKey") (commandKey = |
2 |
|
3 | public ResponseResult removeCache(Long id) { |
4 | log.info("removeCache id:{}", id); |
5 | return restTemplate.postForObject(userServiceUrl + "/user/delete/{1}", null, ResponseResult.class, id); |
6 | } |
缓存使用过程中的问题
- 在缓存使用过程中,我们需要在每次使用缓存的请求前后对HystrixRequestContext进行初始化和关闭,否则会出现如下异常:
1 | java.lang.IllegalStateException: Request caching is not available. Maybe you need to initialize the HystrixRequestContext? |
2 | at com.netflix.hystrix.HystrixRequestCache.get(HystrixRequestCache.java:104) ~[hystrix-core-1.5.18.jar:1.5.18] |
3 | at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:478) ~[hystrix-core-1.5.18.jar:1.5.18] |
4 | at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:454) ~[hystrix-core-1.5.18.jar:1.5.18] |
- 这里我们通过使用过滤器,在每个请求前后初始化和关闭HystrixRequestContext来解决该问题:
1 |
|
2 | "/*",asyncSupported = true) (urlPatterns = |
3 | public class HystrixRequestContextFilter implements Filter { |
4 | |
5 | |
6 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { |
7 | HystrixRequestContext context = HystrixRequestContext.initializeContext(); |
8 | try { |
9 | filterChain.doFilter(servletRequest, servletResponse); |
10 | } finally { |
11 | context.close(); |
12 | } |
13 | } |
14 | } |
请求合并
微服务系统中的服务间通信,需要通过远程调用来实现,随着调用次数越来越多,占用线程资源也会越来越多。Hystrix中提供了@HystrixCollapser用于合并请求,从而达到减少通信消耗及线程数量的效果。
@HystrixCollapser的常用属性
- batchMethod:用于设置请求合并的方法;
- collapserProperties:请求合并属性,用于控制实例属性,有很多;
- timerDelayInMilliseconds:collapserProperties中的属性,用于控制每隔多少时间合并一次请求;
功能实现
- 在UserHystrixController中添加testCollapser方法,这里我们先进行两次服务调用,再间隔200ms以后进行第三次服务调用:
1 | "/testCollapser") ( |
2 | public CommonResult testCollapser() throws ExecutionException, InterruptedException { |
3 | Future<User> future1 = userService.getUserFuture(1L); |
4 | Future<User> future2 = userService.getUserFuture(2L); |
5 | future1.get(); |
6 | future2.get(); |
7 | ThreadUtil.safeSleep(200); |
8 | Future<User> future3 = userService.getUserFuture(3L); |
9 | future3.get(); |
10 | return new CommonResult("操作成功", 200); |
11 | } |
- 使用@HystrixCollapser实现请求合并,所有对getUserFuture的的多次调用都会转化为对getUserByIds的单次调用:
1 | "getUserByIds",collapserProperties = { (batchMethod = |
2 | "timerDelayInMilliseconds", value = "100") (name = |
3 | }) |
4 | public Future<Map> getUserFuture(Long id) { |
5 | return new AsyncResult<Map>(){ |
6 | |
7 | public Map invoke() { |
8 | ResponseResult commonResult = restTemplate.getForObject(userServiceUrl + "/user/{1}", ResponseResult.class, id); |
9 | Map data = (Map) commonResult.getData(); |
10 | log.info("getUserById username:{}", data.get("userName")); |
11 | return data; |
12 | } |
13 | }; |
14 | } |
15 | |
16 | |
17 | public List<Map> getUserByIds(List<Long> ids) { |
18 | log.info("getUserByIds:{}", ids); |
19 | ResponseResult commonResult = restTemplate.getForObject(userServiceUrl + "/user/getUserByIds?ids={1}", ResponseResult.class, StringUtils.join(ids, ",")); |
20 | return (List<Map>) commonResult.getData(); |
21 | } |
Hystrix的常用配置
全局配置
1 | hystrix: |
2 | command: #用于控制HystrixCommand的行为 |
3 | default: |
4 | execution: |
5 | isolation: |
6 | strategy: THREAD #控制HystrixCommand的隔离策略,THREAD->线程池隔离策略(默认),SEMAPHORE->信号量隔离策略 |
7 | thread: |
8 | timeoutInMilliseconds: 1000 #配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理 |
9 | interruptOnTimeout: true #配置HystrixCommand执行超时的时候是否要中断 |
10 | interruptOnCancel: true #配置HystrixCommand执行被取消的时候是否要中断 |
11 | timeout: |
12 | enabled: true #配置HystrixCommand的执行是否启用超时时间 |
13 | semaphore: |
14 | maxConcurrentRequests: 10 #当使用信号量隔离策略时,用来控制并发量的大小,超过该并发量的请求会被拒绝 |
15 | fallback: |
16 | enabled: true #用于控制是否启用服务降级 |
17 | circuitBreaker: #用于控制HystrixCircuitBreaker的行为 |
18 | enabled: true #用于控制断路器是否跟踪健康状况以及熔断请求 |
19 | requestVolumeThreshold: 20 #超过该请求数的请求会被拒绝 |
20 | forceOpen: false #强制打开断路器,拒绝所有请求 |
21 | forceClosed: false #强制关闭断路器,接收所有请求 |
22 | requestCache: |
23 | enabled: true #用于控制是否开启请求缓存 |
24 | collapser: #用于控制HystrixCollapser的执行行为 |
25 | default: |
26 | maxRequestsInBatch: 100 #控制一次合并请求合并的最大请求数 |
27 | timerDelayinMilliseconds: 10 #控制多少毫秒内的请求会被合并成一个 |
28 | requestCache: |
29 | enabled: true #控制合并请求是否开启缓存 |
30 | threadpool: #用于控制HystrixCommand执行所在线程池的行为 |
31 | default: |
32 | coreSize: 10 #线程池的核心线程数 |
33 | maximumSize: 10 #线程池的最大线程数,超过该线程数的请求会被拒绝 |
34 | maxQueueSize: -1 #用于设置线程池的最大队列大小,-1采用SynchronousQueue,其他正数采用LinkedBlockingQueue |
35 | queueSizeRejectionThreshold: 5 #用于设置线程池队列的拒绝阀值,由于LinkedBlockingQueue不能动态改版大小,使用时需要用该参数来控制线程数 |
## 实例配置
实例配置只需要将全局配置中的default换成与之对应的key即可。
1 | hystrix: |
2 | command: |
3 | HystrixComandKey: #将default换成HystrixComrnandKey |
4 | execution: |
5 | isolation: |
6 | strategy: THREAD |
7 | collapser: |
8 | HystrixCollapserKey: #将default换成HystrixCollapserKey |
9 | maxRequestsInBatch: 100 |
10 | threadpool: |
11 | HystrixThreadPoolKey: #将default换成HystrixThreadPoolKey |
12 | coreSize: 10 |
配置文件中相关key的说明
- HystrixComandKey对应@HystrixCommand中的commandKey属性;
- HystrixCollapserKey对应@HystrixCollapser注解中的collapserKey属性;
- HystrixThreadPoolKey对应@HystrixCommand中的threadPoolKey属性。
使用到的模块
1 | ZBCN-SERVER |
2 | ├── zbcn-register/eureka-server -- eureka注册中心 |
3 | ├── zbcn-business/user-service -- 提供User对象CRUD接口的服务 |
4 | └── zbcn-common/ hystrix-server -- hystrix-server服务调用测试服务 |