Spring Cloud Hystrix:服務容錯保護

SpringBoot實戰電商項目mall(20k+star)地址:github.com/macrozheng/…java

摘要

Spring Cloud Hystrix 是Spring Cloud Netflix 子項目的核心組件之一,具備服務容錯及線程隔離等一系列服務保護功能,本文將對其用法進行詳細介紹。git

Hystrix 簡介

在微服務架構中,服務與服務之間經過遠程調用的方式進行通訊,一旦某個被調用的服務發生了故障,其依賴服務也會發生故障,此時就會發生故障的蔓延,最終致使系統癱瘓。Hystrix實現了斷路器模式,當某個服務發生故障時,經過斷路器的監控,給調用方返回一個錯誤響應,而不是長時間的等待,這樣就不會使得調用方因爲長時間得不到響應而佔用線程,從而防止故障的蔓延。Hystrix具有服務降級、服務熔斷、線程隔離、請求緩存、請求合併及服務監控等強大功能。github

建立一個hystrix-service模塊

這裏咱們建立一個hystrix-service模塊來演示hystrix的經常使用功能。web

在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的調用路徑。spring

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服務;

  • 關閉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);
}
複製代碼

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);
}
複製代碼

測試移除緩存

  • 在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);
}
複製代碼

緩存使用過程當中的問題

  • 在緩存使用過程當中,咱們須要在每次使用緩存的請求先後對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();
}
複製代碼

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服務調用測試服務
複製代碼

項目源碼地址

github.com/macrozheng/…app

公衆號

mall項目全套學習教程連載中,關注公衆號第一時間獲取。async

公衆號圖片
相關文章
相關標籤/搜索