1. 程式人生 > >Spring Cloud Hystrix(服務容錯保護)(3)

Spring Cloud Hystrix(服務容錯保護)(3)

1.請求合併

在微服務的架構中的依賴通常通過遠端呼叫來實現,而遠端呼叫最常出現的問題是通訊消耗與連線數佔用。Hystrix提供了HystrixCollapser來實現請求合併,以減少通訊消耗和執行緒數的佔用。

HystrixCollapser實現了在HystrixCommand之前放置一個合併處理器,將處於一個很短的時間窗內對同一依賴服務的多個請求進行整合並以批量方式發起請求的功能(服務提供方也需要提供相應的批量實現介面)。

這裡可以使用以繼承HystrixCollapser和註解的方式實現,但是吧用程式碼實現實在是太麻煩了在專案中應該很少會這麼用,所以這裡我採用註解的形式實現。

@Service
public class HelloService {

    private final Logger logger=Logger.getLogger(getClass());

    @Autowired
    RestTemplate restTemplate;

    @HystrixCollapser(batchMethod = "findAll",collapserProperties = {@HystrixProperty(name="timerDelayInMilliseconds",value = "100")})
    public Future<String> find(String id){
        return null;
    }

    @HystrixCommand
    public List<String> findAll(List<String> id){
//        String s= restTemplate.getForObject("http://hello-service/getid/{1}", String.class,StringUtils.join(id,","));
        String[] s= restTemplate.getForObject("http://hello-service/getid?ids={1}", String[].class,StringUtils.join(id,","));

        return Arrays.asList(s);

    }
}

這裡通過@HystrixCollapser註解為其建立了合併請求器,通過batchMethod屬性指定批量請求的實現方法為findAll方法,同時通過collapserProperties屬性為合併請求器設定相關屬性,這裡使用@HystrixProperty(name="timerDelayInMilliseconds",value = "100")將合併時間窗設定為100毫秒。

請求合併的額外開銷

雖然通過請求合併可以減少請求的數量以緩解依賴服務執行緒池的資源,但是在使用的時候也需要注意它所帶來的額外開銷:用於請求合併的延遲時間窗會使得依賴服務的請求延遲增高。比如:某個請求在不通過請求合併器訪問的平均耗時為5ms,請求合併的延遲時間窗為10ms(預設值),那麼當該請求的設定了請求合併器之後,最壞情況下(在延遲時間窗結束時才發起請求)該請求需要15ms才能完成。

由於請求合併器的延遲時間窗會帶來額外開銷,所以我們是否使用請求合併器需要根據依賴服務呼叫的實際情況來選擇,主要考慮下面兩個方面:

  • 請求命令本身的延遲。如果依賴服務的請求命令本身是一個高延遲的命令,那麼可以使用請求合併器,因為延遲時間窗的時間消耗就顯得莫不足道了。
  • 延遲時間窗內的併發量。如果一個時間窗內只有1-2個請求,那麼這樣的依賴服務不適合使用請求合併器,這種情況下不但不能提升系統性能,反而會成為系統瓶頸,因為每個請求都需要多消耗一個時間窗才響應。相反,如果一個時間窗內具有很高的併發量,並且服務提供方也實現了批量處理介面,那麼使用請求合併器可以有效的減少網路連線數量並極大地提升系統吞吐量,此時延遲時間窗所增加的消耗就可以忽略不計了。

 

下面貼上完整的程式碼:首先修改服務提供者:

@RestController
public class HelloController {

    private final Logger logger=Logger.getLogger(getClass());

    @Autowired
    private DiscoveryClient client;

    @RequestMapping("/hello")
    public String index(@RequestParam(defaultValue = "八戒") String id) throws InterruptedException {
//        int sleepTime=new Random().nextInt(3000);
//        logger.info("sleep:"+sleepTime);
//        Thread.sleep(sleepTime);
        logger.info(new Date());
        return "Hello"+new Date()+"---"+new Random().nextInt()+"id:"+id;
    }


    @RequestMapping("/getid")
    public List<String> test(String ids) {
        System.out.println("ids>>>>>>>>>>>>>>>>>>>>>" + ids);
        ArrayList<String> books = new ArrayList<>();
        books.add("AAAAAAAAAAAAAAAAAAA");
        books.add("bbbbbbbbbbbbbbbbbb");
        books.add("cccccccccccccccccc");
        books.add(ids);

        return books;
    }

    @RequestMapping("/getid/{id}")
    public String test2(@PathVariable String id) {
       System.out.println(id);
        return id;
    }

}

然後下面是我的服務呼叫者程式碼:

@Service
public class HelloService {

    private final Logger logger=Logger.getLogger(getClass());

    @Autowired
    RestTemplate restTemplate;

    @HystrixCollapser(batchMethod = "findAll",collapserProperties = {@HystrixProperty(name="timerDelayInMilliseconds",value = "100")})
    public Future<String> find(String id){
        return null;
    }

    @HystrixCommand
    public List<String> findAll(List<String> id){
//        String s= restTemplate.getForObject("http://hello-service/getid/{1}", String.class,StringUtils.join(id,","));
        String[] s= restTemplate.getForObject("http://hello-service/getid?ids={1}", String[].class,StringUtils.join(id,","));

        return Arrays.asList(s);

    }
}

我的控制器程式碼(用於測試):


    @RequestMapping("/finds")
    public String finds(String id) throws ExecutionException, InterruptedException {
        Future<String>s=helloService.find(id);
        Future<String>ss=helloService.find(id);
        Future<String>sss=helloService.find(id);
        String str=s.get();
        String str2=ss.get();
        String str3=sss.get();
        System.out.println(str+"-----"+str2+"-----"+str3);
        Thread.sleep(3000);
        Future<String>sssss=helloService.find(id);
        System.out.println(sssss.get());
        return str;
    }

http://localhost:8099/finds?id=10

Hystrix有4個不同優先順序的配置:

1.全域性預設值:如果沒有設定下面三個屬性,那麼這個屬性就是預設值。由於該屬性通過程式碼定義,所以對於這個級別主要關注它在程式碼中定義的預設值即可。

2.全域性配置屬性:通過在配置檔案中定義全域性屬性,在應用啟動時或在與Spring Cloud Config和Spring Cloud Bus實現動態重新整理配置功能配合下可以實現對“全域性預設值”的覆蓋以及在執行期對“全域性預設值”的動態調整。

3.例項預設值:通過程式碼為例項定義的預設值。通過程式碼的方式為例項設定屬性值來覆蓋預設的全域性配置。

4.例項配置屬性:通過配置檔案來為指定的例項進行屬性配置,以覆蓋前面的是哪個預設值。他也可以用Spring Cloud Config 和Spring Cloud Bus實現動態重新整理配置功能實現對具體例項配置的動態調整。

2.Command屬性

command屬性主要控制HystrixCommand命令的行為。

它主要有5種不同型別的屬性配置。

 

 

  • execution配置

     

     

     

     

     

    • execution.isolation.strategy :該屬性用來設定HystrixCommand.run()執行的隔離策略,有如下二個選項:

      • THREAD:通過執行緒池隔離的策略,在獨立執行緒上執行,並且他的併發限制受執行緒池中執行緒數量的限制(預設)
      • SEMAPHONE:通過訊號量隔離的策略,在呼叫執行緒上執行,並且他的併發限制受訊號量計數的限制。
    • execution.isolation.thread.timeoutInMilliseconds:該屬性用來配置 HystrixCommand 執行的超時時間,單位為毫秒,預設值 1000 ,超出此時間配置,Hystrix 會將該執行命令為 TIMEOUT 並進入服務降級處理邏輯
    • execution.timeout.enabled:該屬性用來配置 HystrixCommand 執行是否啟動超時時間,預設值 true,如果設定為 false,則 execution.isolation.thread.timeoutInMilliseconds 屬性的配置將不起作用
    • execution.isolation.thread.interruptOnTimeout:該屬性用來配置當 HystrixCommand.run()執行超時的時候,是否需要將他中斷,預設值 true
    • execution.isolation.semaphore.maxConcurrentRequests:當隔離策略使用訊號量時,該屬性用來配置訊號量的大小(併發請求數),當最大併發請求數達到該設定值,後續的請求將會被拒絕
    •  execution.isolation.thread.interruptOnCancel:該屬性用來配置當HystrixCommand.run()執行被取消的時候是否要將它中斷。
  • fallback配置

    下面這些屬性用來控制HystrixCommand.getFallback()的執行。這些屬性同時適用於執行緒池的訊號量的隔離策略。

     

    • fallback.enabled:該屬性用來設定服務降級策略是否啟用,預設值 true ,如果設定為false,當請求失敗或拒絕發生時,將不會呼叫 HystrixCommand.getFallback() 來執行服務降級邏輯
    • fallback.isolation.semaphore.maxConcurrentRequests:該屬性用來設定從呼叫執行緒中允許HystrixCommand.getFallback()方法執行的最大併發請求數
  • circuitBreaker 配置

    下面是這些斷路器的屬性配置,用來控制HystrixCircuitBreaker的行為。

     

     

     

     

    • circuitBreaker.enabled:該屬性用來確定當服務請求命令失敗時,是否使用斷路器來跟蹤其健康指標和熔斷請求,預設值 true
    • circuitBreaker.requestVolumeThreshold:該屬性用來設定在滾動時間窗中,斷路器的最小請求數。例如:預設值 20 的情況下,如果滾動時間窗(預設值 10秒)內僅收到19個請求,即使這19個請求都失敗了,斷路器也不會開啟。
    • circuitBreaker.sleepWindowInMilliseconds:該屬性用來設定當斷路器開啟之後的休眠時間窗。預設值 5000 毫秒,休眠時間窗結束之後,會將斷路器設定為"半開"狀態,嘗試熔斷的請求命令,如果依然失敗就將斷路器繼續設定為"開啟"狀態,如果成功就設定為"關閉"狀態。
    • circuitBreaker.errorThresholdPercentage:該屬性用來設定斷路器開啟的錯誤百分比條件。例如,預設值為 50 的情況下,表示在滾動時間窗中,在請求數量超過 circuitBreaker.requestVolumeThreshold 閾值的請求下,如果錯誤請求數的百分比超過50,就把斷路器設定為"開啟"狀態,否則就設定為"關閉"狀態。
    • circuitBreaker.forceOpen:該屬性如果設為true斷路器j將強制進入"開啟"狀態,會拒絕所有請求,該屬性優先於 circuitBreaker.forceClosed
    • circuitBreaker.forceClosed:該屬性如果設為true,斷路器強制進入"關閉"狀態,會接收所有請求。如果circuitBreaker.forceOpens屬性為true,該屬性不會生效。
  • metrics 配置

    該配置屬性與HystrixCommand 和 HystrixObservableCommand 執行中捕獲指標資訊有關,配置字首為 hystrix.command.default

     

     

     

    • metrics.rollingStats.timeInMilliseconds:該屬性用於設定滾動時間窗的長度,單位毫秒,該時間用於斷路器判斷健康度時需要收集資訊的持續時間,預設值 10000 。斷路器值啊收集指標資訊時候會根據設定的時間窗長度拆分成多個"桶"來累計各度量值,每個"桶"記錄了一段時間內的採集指標。例如當採用預設值10000毫秒時,斷路器預設將其拆分成10個桶(桶的數量可以通過metrics.rollingStats.numBuckets引數設定),每個桶記錄1000毫秒內的指標資訊。
    • metrics.rollingStats.numBuckets:該屬性用來設定滾動時間窗統計指標資訊時,劃分"桶"的數量,預設值 10 。 metrics.rollingStats.timeInMilliseconds 引數的設定必須能被該引數整除,否則將丟擲異常。

    • metrics.rollingPercentile.timeInMilliseconds:該屬性用來設定百分位統計的滾動視窗的持續時間,單位:毫秒,預設值 60000
    • metrics.rollingPercentile.enabled:該屬性用來設定對命令執行的延遲是否使用百分位數來跟蹤和計算,預設值 true ,如果設定為 false 那麼所有概要統計都將返回 -1
    • metrics.rollingPercentile.numBuckets:該屬性用來設定百分位統計視窗中使用"桶"的數量,預設值 6
    • metrics.rollingPercentile.timeInMilliseconds:該屬性用來設定百分位的滾動視窗的持續時間,單位是毫秒。
    • metrics.rollingPercentile.bucketSize:該屬性用來設定在執行過程中每個"桶"中保留的最大執行次數,如果在滾動時間窗內發生超該設定值的執行次數,就從最初的位置開始重寫,例如:設定為 100,滾動視窗為 10 秒,若在10秒內一個"桶"中發生了500次執行,那麼該"桶"中只保留最後的100次執行的統計。另外,增加該值的大小會增加記憶體量的消耗,並增加排序 百分位數所需的計算時間。預設值 100
    • metrics.healthSnapshot.intervalInMilliseconds:該屬性用來設定採集影響斷路器狀態的健康快照(請求的成功、錯誤百分比)的間隔等待時間,預設值 500
  • requestContext 配置

    下面這些屬性涉及HystrixCommand使用HystrixRequestContext的設定。

     

     

    • requestCache.enabled:該屬性用來配置是否開啟請求快取
    • requestLog.enabledg:該屬性用來設定 HystrixCommand 的執行和事件是否列印日誌到 HystrixRequestLog 中,預設值 true
  • collapser 配置

    該屬性除了在程式碼中用set和配置檔案之外,也可以用註解進行配置。可使用@HystrixCollapser中的collapserProperties屬性來設定,如:

    @HystrixCollapser(batchMethod="batch",collapserProperties={
        @HystrixProperty(name="timerDelayInMilliseconds",value="20")
    })

     下面這些屬性用來控制命令合併相關的行為。

     

     

    • maxRequestsInBatch:該屬性用來設定一次請求合併批處理允許的最大請求數量,預設值 Integer.MAX_VALUE
    • timerDelayInMilliseconds:該屬性用來設定批處理過程中每個命令延遲的時間,單位毫秒,預設值 10
    • requestCache.enabled:該屬性用來設定批處理過程中是否開啟請求快取,預設值 true

     

    threadPool 配置

    該屬性除了在程式碼中用set和配置檔案之外,也可以用註解進行配置。可使用@HystrixCollapser中的threadPoolProperties屬性來設定,如:

    @HystrixCommand(fallbackMethod="helloFallback",commandKey="helloKey",
        threadPoolProperties={
            @HystrixProperty(name="coreSize",value="20")
        }
    )

     

     

     

     

    • coreSize:該屬性用來設定執行命令執行緒池的核心執行緒數,該值也就是命令執行的最大併發量,預設值 10
    • maxQueueSize:該屬性用來設定執行緒池的最大佇列大小,當設定為 -1 時,執行緒池將使用 SynchronousQueue 實現的佇列,否則使用 LinkedBlockingQueue 實現的佇列。(該屬性只有在初始化時有用,無法通過動態重新整理調整)
    • queueSizeRejectionThreshold :該屬性用來為佇列設定拒絕閾值,即使佇列沒有到達最大值也能拒絕請求,該屬性主要對 LinkedBlockingQueue 佇列的補充,因為LinkedBlockingQueue佇列不能動態修改它的物件大小。預設值 5,當 maxQueueSize 屬性為 -1 時候,該屬性無效
    • metrics.rollingPercentile.timeInMilliseconds:該屬性用來設定執行緒池統計的滾動視窗的持續時間,單位:毫秒,預設值 10000。該滾動時間窗的長度用於執行緒池的指標度量它會被分成多個桶來統計指標。
    • metrics.rollingPercentile.numBuckets:該屬性用來設定執行緒池統計視窗中使用"桶"的數量,預設值 10

 

參考《Spring Cloud微服務實戰》