Spring Cloud Hystrix:服務容錯保護
- 2019 年 10 月 5 日
- 筆記
Spring Cloud Hystrix 是Spring Cloud Netflix 子項目的核心組件之一,具有服務容錯及執行緒隔離等一系列服務保護功能,本文將對其用法進行詳細介紹。
Hystrix 簡介
在微服務架構中,服務與服務之間通過遠程調用的方式進行通訊,一旦某個被調用的服務發生了故障,其依賴服務也會發生故障,此時就會發生故障的蔓延,最終導致系統癱瘓。Hystrix實現了斷路器模式,當某個服務發生故障時,通過斷路器的監控,給調用方返回一個錯誤響應,而不是長時間的等待,這樣就不會使得調用方由於長時間得不到響應而佔用執行緒,從而防止故障的蔓延。Hystrix具備服務降級、服務熔斷、執行緒隔離、請求快取、請求合併及服務監控等強大功能。
創建一個hystrix-service模組
這裡我們創建一個hystrix-service模組來演示hystrix的常用功能。
在pom.xml中添加相關依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
在application.yml進行配置
主要是配置了埠、註冊中心地址及user-service的調用路徑。
server: port: 8401 spring: application: name: hystrix-service eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8001/eureka/ service-url: user-service: http://user-service
在啟動類上添加@EnableCircuitBreaker來開啟Hystrix的斷路器功能
@EnableCircuitBreaker @EnableDiscoveryClient @SpringBootApplication public class HystrixServiceApplication { public static void main(String[] args) { SpringApplication.run(HystrixServiceApplication.class, args); } 創建UserHystrixController介面用於調用user-service服務
服務降級演示
- 在UserHystrixController中添加用於測試服務降級的介面:
@GetMapping("/testFallback/{id}") public CommonResult testFallback(@PathVariable Long id) { return userService.getUser(id); } - 在UserService中添加調用方法與服務降級方法,方法上需要添加@HystrixCommand註解:
@HystrixCommand(fallbackMethod = "getDefaultUser") public CommonResult getUser(Long id) { return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id); } public CommonResult getDefaultUser(@PathVariable Long id) { User defaultUser = new User(-1L, "defaultUser", "123456"); return new CommonResult<>(defaultUser); } - 啟動eureka-server、user-service、hystrix-service服務;

- 調用介面進行測試:http://localhost:8401/user/testFallback/1

- 關閉user-service服務重新測試該介面,發現已經發生了服務降級:

@HystrixCommand詳解
@HystrixCommand中的常用參數
- fallbackMethod:指定服務降級處理方法;
- ignoreExceptions:忽略某些異常,不發生服務降級;
- commandKey:命令名稱,用於區分不同的命令;
- groupKey:分組名稱,Hystrix會根據不同的分組來統計命令的告警及儀錶盤資訊;
- threadPoolKey:執行緒池名稱,用於劃分執行緒池。
設置命令、分組及執行緒池名稱
- 在UserHystrixController中添加測試介面:
@GetMapping("/testCommand/{id}") public CommonResult testCommand(@PathVariable Long id) { return userService.getUserCommand(id); } - 在UserService中添加方式實現功能:
@HystrixCommand(fallbackMethod = "getDefaultUser", commandKey = "getUserCommand", groupKey = "getUserGroup", threadPoolKey = "getUserThreadPool") public CommonResult getUserCommand(@PathVariable Long id) { return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id); } 使用ignoreExceptions忽略某些異常降級
- 在UserHystrixController中添加測試介面:
@GetMapping("/testException/{id}") public CommonResult testException(@PathVariable Long id) { return userService.getUserException(id); } - 在UserService中添加實現方法,這裡忽略了NullPointerException,當id為1時拋出IndexOutOfBoundsException,id為2時拋出NullPointerException:
@HystrixCommand(fallbackMethod = "getDefaultUser2", ignoreExceptions = {NullPointerException.class}) public CommonResult getUserException(Long id) { if (id == 1) { throw new IndexOutOfBoundsException(); } else if (id == 2) { throw new NullPointerException(); } return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id); } public CommonResult getDefaultUser2(@PathVariable Long id, Throwable e) { LOGGER.error("getDefaultUser2 id:{},throwable class:{}", id, e.getClass()); User defaultUser = new User(-2L, "defaultUser2", "123456"); return new CommonResult<>(defaultUser); } - 調用介面進行測試:http://localhost:8401/user/tesException/1

- 調用介面進行測試:http://localhost:8401/user/tesException/1

Hystrix的請求快取
當系統並發量越來越大時,我們需要使用快取來優化系統,達到減輕並發請求執行緒數,提供響應速度的效果。
相關註解
- @CacheResult:開啟快取,默認所有參數作為快取的key,cacheKeyMethod可以通過返回String類型的方法指定key;
- @CacheKey:指定快取的key,可以指定參數或指定參數中的屬性值為快取key,cacheKeyMethod還可以通過返回String類型的方法指定;
- @CacheRemove:移除快取,需要指定commandKey。
測試使用快取
- 在UserHystrixController中添加使用快取的測試介面,直接調用三次getUserCache方法:
@GetMapping("/testCache/{id}") public CommonResult testCache(@PathVariable Long id) { userService.getUserCache(id); userService.getUserCache(id); userService.getUserCache(id); return new CommonResult("操作成功", 200); } - 在UserService中添加具有快取功能的getUserCache方法:
@CacheResult(cacheKeyMethod = "getCacheKey") @HystrixCommand(fallbackMethod = "getDefaultUser", commandKey = "getUserCache") public CommonResult getUserCache(Long id) { LOGGER.info("getUserCache id:{}", id); return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id); } /** * 為快取生成key的方法 */ public String getCacheKey(Long id) { return String.valueOf(id); } - 調用介面測試http://localhost:8401/user/testCache/1,這個介面中調用了三次getUserCache方法,但是只列印了一次日誌,說明有兩次走的是快取:

測試移除快取
- 在UserHystrixController中添加移除快取的測試介面,調用一次removeCache方法:
@GetMapping("/testRemoveCache/{id}") public CommonResult testRemoveCache(@PathVariable Long id) { userService.getUserCache(id); userService.removeCache(id); userService.getUserCache(id); return new CommonResult("操作成功", 200); } - 在UserService中添加具有移除快取功能的removeCache方法:
@CacheRemove(commandKey = "getUserCache", cacheKeyMethod = "getCacheKey") @HystrixCommand public CommonResult removeCache(Long id) { LOGGER.info("removeCache id:{}", id); return restTemplate.postForObject(userServiceUrl + "/user/delete/{1}", null, CommonResult.class, id); } - 調用介面測試http://localhost:8401/user/testRemoveCache/1,可以發現有兩次查詢都走的是介面:

快取使用過程中的問題
- 在快取使用過程中,我們需要在每次使用快取的請求前後對HystrixRequestContext進行初始化和關閉,否則會出現如下異常:
java.lang.IllegalStateException: Request caching is not available. Maybe you need to initialize the HystrixRequestContext? at com.netflix.hystrix.HystrixRequestCache.get(HystrixRequestCache.java:104) ~[hystrix-core-1.5.18.jar:1.5.18] at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:478) ~[hystrix-core-1.5.18.jar:1.5.18] at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:454) ~[hystrix-core-1.5.18.jar:1.5.18]
- 這裡我們通過使用過濾器,在每個請求前後初始化和關閉HystrixRequestContext來解決該問題:
/** * Created by macro on 2019/9/4. */ @Component @WebFilter(urlPatterns = "/*",asyncSupported = true) public class HystrixRequestContextFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HystrixRequestContext context = HystrixRequestContext.initializeContext(); try { filterChain.doFilter(servletRequest, servletResponse); } finally { context.close(); } } } 請求合併
微服務系統中的服務間通訊,需要通過遠程調用來實現,隨著調用次數越來越多,佔用執行緒資源也會越來越多。Hystrix中提供了@HystrixCollapser用於合併請求,從而達到減少通訊消耗及執行緒數量的效果。
@HystrixCollapser的常用屬性
- batchMethod:用於設置請求合併的方法;
- collapserProperties:請求合併屬性,用於控制實例屬性,有很多;
- timerDelayInMilliseconds:collapserProperties中的屬性,用於控制每隔多少時間合併一次請求;
功能演示
- 在UserHystrixController中添加testCollapser方法,這裡我們先進行兩次服務調用,再間隔200ms以後進行第三次服務調用:
@GetMapping("/testCollapser") public CommonResult testCollapser() throws ExecutionException, InterruptedException { Future<User> future1 = userService.getUserFuture(1L); Future<User> future2 = userService.getUserFuture(2L); future1.get(); future2.get(); ThreadUtil.safeSleep(200); Future<User> future3 = userService.getUserFuture(3L); future3.get(); return new CommonResult("操作成功", 200); } - 使用@HystrixCollapser實現請求合併,所有對getUserFuture的的多次調用都會轉化為對getUserByIds的單次調用:
@HystrixCollapser(batchMethod = "getUserByIds",collapserProperties = { @HystrixProperty(name = "timerDelayInMilliseconds", value = "100") }) public Future<User> getUserFuture(Long id) { return new AsyncResult<User>(){ @Override public User invoke() { CommonResult commonResult = restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id); Map data = (Map) commonResult.getData(); User user = BeanUtil.mapToBean(data,User.class,true); LOGGER.info("getUserById username:{}", user.getUsername()); return user; } }; } @HystrixCommand public List<User> getUserByIds(List<Long> ids) { LOGGER.info("getUserByIds:{}", ids); CommonResult commonResult = restTemplate.getForObject(userServiceUrl + "/user/getUserByIds?ids={1}", CommonResult.class, CollUtil.join(ids,",")); return (List<User>) commonResult.getData(); } - 訪問介面測試http://localhost:8401/user/testCollapser,由於我們設置了100毫秒進行一次請求合併,前兩次被合併,最後一次自己單獨合併了。

Hystrix的常用配置
全局配置
hystrix: command: #用於控制HystrixCommand的行為 default: execution: isolation: strategy: THREAD #控制HystrixCommand的隔離策略,THREAD->執行緒池隔離策略(默認),SEMAPHORE->訊號量隔離策略 thread: timeoutInMilliseconds: 1000 #配置HystrixCommand執行的超時時間,執行超過該時間會進行服務降級處理 interruptOnTimeout: true #配置HystrixCommand執行超時的時候是否要中斷 interruptOnCancel: true #配置HystrixCommand執行被取消的時候是否要中斷 timeout: enabled: true #配置HystrixCommand的執行是否啟用超時時間 semaphore: maxConcurrentRequests: 10 #當使用訊號量隔離策略時,用來控制並發量的大小,超過該並發量的請求會被拒絕 fallback: enabled: true #用於控制是否啟用服務降級 circuitBreaker: #用於控制HystrixCircuitBreaker的行為 enabled: true #用於控制斷路器是否跟蹤健康狀況以及熔斷請求 requestVolumeThreshold: 20 #超過該請求數的請求會被拒絕 forceOpen: false #強制打開斷路器,拒絕所有請求 forceClosed: false #強制關閉斷路器,接收所有請求 requestCache: enabled: true #用於控制是否開啟請求快取 collapser: #用於控制HystrixCollapser的執行行為 default: maxRequestsInBatch: 100 #控制一次合併請求合併的最大請求數 timerDelayinMilliseconds: 10 #控制多少毫秒內的請求會被合併成一個 requestCache: enabled: true #控制合併請求是否開啟快取 threadpool: #用於控制HystrixCommand執行所在執行緒池的行為 default: coreSize: 10 #執行緒池的核心執行緒數 maximumSize: 10 #執行緒池的最大執行緒數,超過該執行緒數的請求會被拒絕 maxQueueSize: -1 #用於設置執行緒池的最大隊列大小,-1採用SynchronousQueue,其他正數採用LinkedBlockingQueue queueSizeRejectionThreshold: 5 #用於設置執行緒池隊列的拒絕閥值,由於LinkedBlockingQueue不能動態改版大小,使用時需要用該參數來控制執行緒數
實例配置
實例配置只需要將全局配置中的default換成與之對應的key即可。
hystrix: command: HystrixComandKey: #將default換成HystrixComrnandKey execution: isolation: strategy: THREAD collapser: HystrixCollapserKey: #將default換成HystrixCollapserKey maxRequestsInBatch: 100 threadpool: HystrixThreadPoolKey: #將default換成HystrixThreadPoolKey coreSize: 10
配置文件中相關key的說明
- HystrixComandKey對應@HystrixCommand中的commandKey屬性;
- HystrixCollapserKey對應@HystrixCollapser註解中的collapserKey屬性;
- HystrixThreadPoolKey對應@HystrixCommand中的threadPoolKey屬性。
使用到的模組
springcloud-learning ├── eureka-server -- eureka註冊中心 ├── user-service -- 提供User對象CRUD介面的服務 └── hystrix-service -- hystrix服務調用測試服務
項目源碼地址
https://github.com/macrozheng/springcloud-learning