分佈式服務熔斷降級限流利器至Hystrix

原文地址1java

原文地址2git

全文概覽github

[TOC]面試

爲何須要hystrix

hystrix官網地址githubspring

  • Hystrix一樣是netfix公司在分佈式系統中的貢獻。一樣的也進入的不維護階段。不維護不表明被淘汰。只能說明推陳出新技術在不斷迭代。曾今的輝煌曾經的設計仍是值得咱們去學習的。
  • 在分佈式環境中,服務調度是特點也是頭疼的一塊。在服務治理章節咱們介紹了服務治理的功能。前一課咱們也介紹了ribbon、feign進行服務調用。如今天然的到了服務監控管理了。hystrix就是對服務進行隔離保護。以實現服務不會出現連帶故障。致使整個系統不可用

image-20210414145650113

  • 如上圖所示,當多個客戶端進行服務調用Aservice時,而在分佈式系統中Aservice存在三臺服務,其中Aservice某些邏輯須要Bservice處理。Bservice在分佈式系統中部署了兩臺服務。這個時候由於網絡問題致使Aservice中有一臺和Bservice的通訊異常。若是Bservice是作日誌處理的。在整個系統看來日誌丟了和系統宕機比起來應該無所謂了。可是這個時候由於網絡通訊問題致使Aservice整個服務不可用了。有點得不嘗試。

image-20210414153046542

  • 在看上圖 。 A-->B-->C-->D 。此時D服務宕機了。C由於D宕機出現處理異常。可是C的線程卻還在爲B響應。這樣隨着併發請求進來時,C服務線程池出現爆滿致使CPU上漲。在這個時候C服務的其餘業務也會受到CPU上漲的影響致使響應變慢。

特點功能

Hystrix是一個低延遲和容錯的第三方組件庫。旨在隔離遠程系統、服務和第三方庫的訪問點。官網上已經中止維護並推薦使用resilience4j。可是國內的話咱們有springcloud alibaba。express

Hystrix 經過隔離服務之間的訪問來實現分佈式系統中延遲及容錯機制來解決服務雪崩場景而且基於hystrix能夠提供備選方案(fallback)。緩存

  • 對網絡延遲及故障進行容錯
  • 阻斷分佈式系統雪崩
  • 快速失敗並平緩恢復
  • 服務降級
  • 實時監控、警報

$$ 99.99^{30} = 99.7\% \quad uptime \\ 0.3\% \quad of \quad 1 \quad billion \quad requests \quad = \quad 3,000,000 \quad failures \\ 2+ \quad hours \quad downtime/month \quad even \quad if \quad all \quad dependencies \quad have \quad excellent \quad uptime. $$tomcat

  • 上面試官網給出的一個統計。在30臺服務中每臺出現異常的概覽是0.01%。一億個請求就會有300000失敗。這樣換算下每月至少有2小時停機。這對於互聯網系統來講是致命的。

image-20210414164135471

  • 上圖是官網給出的兩種狀況。和咱們上章節的相似。都是介紹服務雪崩的場景。

項目準備

  • 在openfeign專題中咱們就探討了基於feign實現的服務熔斷當時說了內部就是基於hystrix。當時咱們也看了pom內部的結構在eureka中內置ribbon的同時也內置了hystrix模塊。

image-20210414165134167

  • 雖然包裏面包含了hystrix 。咱們仍是引入對應的start開啓相關配置吧。這裏其實就是在openfeign專題中的列子。在那個專題咱們提供了PaymentServiceFallbackImpl、PaymentServiceFallbackFactoryImpl兩個類做爲備選方案。不過當時咱們只需指出openfeign支持設置兩種方式的備選方案。今天咱們網絡

    <!--hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

演示下傳統企業沒有備選方案的狀況會發生什麼災難。併發

image-20210414171119461

image-20210414171529143

接口測試

  • 首先咱們對payment#createByOrder接口進行測試。查看下響應狀況

    image-20210415103254641

  • 在測試payment#getTimeout/id方法。

    image-20210415103254641

    • 如今咱們用jemeter來壓測payment#getTimeOut/id這個接口。一位須要4S等待會照成資源消耗殆盡問題。這個時候咱們的payment#createByOrder也會被阻塞。

    image-20210415103254641

    • spring中默認的tomcat的最大線程數是200.爲了保護咱們辛苦的筆記本。這裏咱們將線程數設置小點。這樣咱們更容易復現線程被打滿的狀況。線程滿了就會影響到payment#createByOrder接口。

    image-20210415103018785

  • 上面咱們壓測的是payment的原生接口。若是壓測的是order模塊。若是沒有在openfeign中配置fallback。那麼order服務就會由於payment#getTimeOut/id接口併發致使線程滿了從而致使order模塊響應緩慢。這就是雪崩效應。下面咱們從兩個方面來解決雪崩的發生。

業務隔離

  • 上面的場景發生是由於payment#createByOrder 和payment#getTimeOut/id同屬於payment服務。一個payment服務實際上就是一個Tomcat服務。同一個tomcat服務是有一個線程池的。 每次請求落到該tomcat 服務裏就會去線程池中申請線程。獲取到線程了才能由線程來處理請求的業務。就是由於tomcat內共享線程池。因此當payment#getTimeOut/id併發上來後就會搶空線程池。致使別的藉口甚至是絕不相關的接口都沒有資源能夠申請。只能乾巴巴的等待資源的釋放。
  • 這就比如上班高峯期乘坐電梯由於某一個公司集中上班致使一段時間電梯所有被使用了。這時候國家領導過來也沒辦法上電梯。
  • 咱們也知道這種狀況很好解決。每一個園區都會有專用電梯供特殊使用。
  • 咱們解決上述問題也是一樣的思路。進行隔離。不一樣的接口有不一樣的線程池。這樣就不會形成雪崩。

線程隔離

image-20210415112638886

  • 還記得咱們上面爲了演示併發將order模塊的最大線程數設置爲10.這裏咱們經過測試工具調用下order/getpayment/1這個接口看看日誌打印狀況

    image-20210415142629374

  • 咱們接口調用的地方將當前線程打印出來。咱們能夠看到一隻都是那10個線程在來回的使用。這也是上面爲何會形成雪崩現象。
@HystrixCommand(
            groupKey = "order-service-getPaymentInfo",
            commandKey = "getPaymentInfo",
            threadPoolKey = "orderServicePaymentInfo",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000")
            },
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize" ,value = "6"),
                    @HystrixProperty(name = "maxQueueSize",value = "100"),
                    @HystrixProperty(name = "keepAliveTimeMinutes",value = "2"),
                    @HystrixProperty(name = "queueSizeRejectionThreshold",value = "100")

            },
            fallbackMethod = "getPaymentInfoFallback"
    )
    @RequestMapping(value = "/getpayment/{id}",method = RequestMethod.GET)
    public ResultInfo getPaymentInfo(@PathVariable("id") Long id) {
        log.info(Thread.currentThread().getName());
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, ResultInfo.class);
    }
    public ResultInfo getPaymentInfoFallback(@PathVariable("id") Long id) {
        log.info("已經進入備選方案了,下面交由自由線程執行"+Thread.currentThread().getName());
        return new ResultInfo();
    }
  @HystrixCommand(
            groupKey = "order-service-getpaymentTimeout",
            commandKey = "getpaymentTimeout",
            threadPoolKey = "orderServicegetpaymentTimeout",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "10000")
            },
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize" ,value = "3"),
                    @HystrixProperty(name = "maxQueueSize",value = "100"),
                    @HystrixProperty(name = "keepAliveTimeMinutes",value = "2"),
                    @HystrixProperty(name = "queueSizeRejectionThreshold",value = "100")

            }
    )
    @RequestMapping(value = "/getpaymentTimeout/{id}",method = RequestMethod.GET)
    public ResultInfo getpaymentTimeout(@PathVariable("id") Long id) {
        log.info(Thread.currentThread().getName());
        return orderPaymentService.getTimeOut(id);
    }
  • 這裏演示效果很差展現,我就直接展現數據吧。
併發量在getpaymentTimeout getpaymentTimeout/{id} /getpayment/{id}
20 三個線程打滿後一段時間開始報錯 能夠正常響應;也會慢,cpu線程切換須要時間
30 同上 同上
50 同上 也會超時,由於order調用payment服務壓力會受影響
  • 若是咱們將hystrix加載payment原生服務就不會出現上面第三條狀況。爲何我會放在order上就是想讓你們看看雪崩的場景。在併發50的時候由於payment設置的最大線程也是10,他自己也是有吞吐量的。在order#getpyament/id接口雖然在order模塊由於hystrix線程隔離有本身的線程運行,可是由於原生服務不給力致使本身調用超時從而影響運行的效果。這樣演示也是爲了後續引出fallback解決雪崩的一次場景模擬吧。
  • 咱們能夠在payment服務中經過hystrix設置fallback。保證payment服務低延遲從而保證order模塊不會由於payment本身緩慢致使order#getpayment這種正常接口異常。
  • 還有一點雖然經過hystrix進行線程隔離了。可是咱們在運行其餘接口時響應時間也會稍長點。由於CPU在進行線程切換的時候是有開銷的。這一點也是痛點。咱們並不能爲所欲爲的進行線程隔離的。這就引出咱們的信號量隔離了。

信號量隔離

  • 關於信號量隔離這裏也就不演示了。演示的意義不是很大
@HystrixCommand(
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000"),
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value = "SEMAPHORE"),
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value = "6")
            },
            fallbackMethod = "getPaymentInfoFallback"
    )
  • 咱們如上配置表示信號量最大爲6 。 表示併發6以後就會進行等待。等待超時時間未1s。
措施 優勢 缺點 超時 熔斷 異步
線程隔離 一個調用一個線程池;互相不干擾;保證高可用 cpu線程切換開銷
信號量隔離 避免CPU切換。高效 在高併發場景下須要存儲信號量變大 × ×
  • 除了線程隔離、信號量隔離等隔離手段咱們能夠經過請求合併、接口數據緩存等手段增強穩定性。

服務降級

觸發條件

  • 程序發生除HystrixBadRequestException異常。
  • 服務調用超時
  • 服務熔斷
  • 線程池、信號量不夠

image-20210415180423428

  • 在上面咱們的timeout接口。不論是線程隔離仍是信號量隔離在條件知足的時候就會直接拒絕後續請求。這樣太粗暴了。上面咱們也提到了fallback。
  • 還記的上面咱們order50個併發的timeout的時候會致使getpayment接口異常,當時定位了是由於原生payment服務壓力撐不住致使的。若是咱們在payment上加入fallback就能保證在資源不足的時候也能快速響應。這樣至少能保證order#getpayment方法的可用性。

    image-20210415180423428

    • 可是這種配置屬於實驗性配置。在真實生產中咱們不可能在每一個方法上配置fallback的。這樣愚蠢至極。
    • hystrix除了在方法上特殊定製的fallback之外,還有一個全局的fallback。只須要在類上經過@DefaultProperties(defaultFallback = "globalFallback")來實現全局的備選方案。一個方法知足觸發降級的條件時若是該請求對應的HystrixCommand註解中沒有配置fallback則使用所在類的全局fallback。若是全局也沒有則拋出異常。

      不足

      • 雖然DefaultProperties 能夠避免每一個接口都配置fallback。可是這種的全局好像還不是全局的fallback。咱們仍是須要每一個類上配置fallback。筆者查閱了資料好像也沒有
      • 可是在openfeign專題裏咱們說了openfeign結合hystrix實現的服務降級功能。還記的裏面提到了一個FallbackFactory這個類嗎。這個類能夠理解成spring的BeanFactory。這個類是用來產生咱們所須要的FallBack的。咱們在這個工廠裏能夠生成一個通用類型的fallback的代理對象。代理對象能夠根據代理方法的方法簽名進行入參和出參。
      • 這樣咱們能夠在全部的openfeign地方配置這個工廠類。這樣的話就避免的生成不少個fallback。 美中不足的仍是須要每一個地方都指定一下。關於FallBackFactory感興趣的能夠下載源碼查看或者進主頁查看openfeign專題。

服務熔斷

@HystrixCommand(
            commandProperties = {
                    @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),  //是否開啓斷路器
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),   //請求次數
                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),  //時間範圍
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失敗率達到多少後跳閘
            },
            fallbackMethod = "getInfoFallback"
    )
    @RequestMapping(value = "/get", method = RequestMethod.GET)
    public ResultInfo get(@RequestParam Long id) {
        if (id < 0) {
            int i = 1 / 0;
        }
        log.info(Thread.currentThread().getName());
        return orderPaymentService.get(id);
    }
    public ResultInfo getInfoFallback(@RequestParam Long id) {

        return new ResultInfo();
    }
  • 首先咱們經過circuitBreaker.enabled=true開啓熔斷器
  • circuitBreaker.requestVolumeThreshold設置統計請求次數
  • circuitBreaker.sleepWindowInMilliseconds 設置時間滑動單位 , 在觸發熔斷後多久進行嘗試開放,及俗稱的半開狀態
  • circuitBreaker.errorThresholdPercentage 設置觸發熔斷開關的臨界條件
  • 上面的配置若是最近的10次請求錯誤率達到60% ,則觸發熔斷降級 , 在10S內都處於熔斷狀態服務進行降級。10S後半開嘗試獲取服務最新狀態
  • 下面咱們經過jmeter進行接口http://localhost/order/get?id=-1進行20次測試。雖然這20次無一例額外都會報錯。可是咱們會發現一開始報錯是由於咱們代碼裏的錯誤。後面的錯誤就是hystrix熔斷的錯誤了。一開始試by zero 錯誤、後面就是short-circuited and fallback failed 熔斷錯誤了

  • 正常咱們在hystrix中會配置fallback , 關於fallback兩種方式咱們上面降級章節已經實現了。這裏是爲了方便看到錯誤的不一樣特地放開了。

image-20210421163842061

  • 在HystrixCommand中配置的參數基本都是在HystrixPropertiesManager對象中。咱們能夠看到關於熔斷器的配置有6個參數。基本就是咱們上面的四個配置

服務限流

  • 服務降級咱們上面提到的兩種隔離就是實現限流的策略。

請求合併

  • 除了熔斷、降級、限流意外hystrix還爲咱們提供了請求合併。顧名思義就是將多個請求合併成一個請求已達到下降併發的問題。
  • 好比說咱們order有個接個是查詢當個訂單信息order/getId?id=1 忽然有一萬個請求過來。爲了緩解壓力咱們集中一下請求每100個請求調用一次order/getIds?ids=xxxxx 。這樣咱們最終到payment模塊則是10000/100=100個請求。下面咱們經過代碼配置實現下請求合併。

HystrixCollapser

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HystrixCollapser {
    String collapserKey() default "";

    String batchMethod();

    Scope scope() default Scope.REQUEST;

    HystrixProperty[] collapserProperties() default {};
}
屬性 含義
collapserKey 惟一標識
batchMethod 請求合併處理方法。即合併後須要調用的方法
scope 做用域;兩種方式[REQUEST, GLOBAL] ;
REQUEST : 在同一個用戶請求中達到條件將會合並
GLOBAL : 任何線程的請求都會加入到這個全局統計中
HystrixProperty[] 配置相關參數

image-20210422094851902

  • 在Hystrix中全部的properties配置都會在HystrixPropertiesManager.java中。咱們在裏面能夠找到Collapser只有兩個相關的配置。分別表示最大請求數和統計時間單元。
@HystrixCollapser(
            scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
            batchMethod = "getIds",
            collapserProperties = {
                    @HystrixProperty(name = HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH , value = "3"),
                    @HystrixProperty(name = HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS, value = "10")
            }
    )
    @RequestMapping(value = "/getId", method = RequestMethod.GET)
    public ResultInfo getId(@RequestParam Long id) {
        if (id < 0) {
            int i = 1 / 0;
        }
        log.info(Thread.currentThread().getName());
        return null;
    }
    @HystrixCommand
    public List<ResultInfo> getIds(List<Long> ids) {
        System.out.println(ids.size()+"@@@@@@@@@");
        return orderPaymentService.getIds(ids);
    }
  • 上面咱們配置了getId會走getIds請求,最可能是10S三個請求會合並在一塊兒。而後getIds有payment服務在分別去查詢最終返回多個ResultInfo。

  • 咱們經過jemeter進行getId接口壓測,日誌中ids的長度最大是3 。 驗證了咱們上面getId接口的配置。這樣就能保證在出現高併發的時候會進行接口合併下降TPS。
  • 上面咱們是經過請求方法註解進行接口合併處理。實際上內部hystrix是經過HystrixCommand

工做流程

image-20210421171613835

  • 官網給出的流程圖示,並配備流程說明一共是9部。下面咱們就翻譯下。
  • ①、建立HystrixCommand或者HystrixObservableCommand對象

    • HystrixCommand : 用在依賴單個服務上
    • HystrixObservableCommand : 用在依賴多個服務上
  • ②、命令執行,hystrrixCommand 執行execute、queue ; hystrixObservableCommand執行observe、toObservable
方法 做用
execute 同步執行;返回結果對象或者異常拋出
queue 異步執行;返回Future對象
observe 返回Observable對象
toObservable 返回Observable對象
  • ③、查看緩存是否開啓及是否命中緩存,命中則返回緩存響應
  • ④、是否熔斷, 若是已經熔斷則fallback降級;若是熔斷器是關閉的則放行
  • ⑤、線程池、信號量是否有資源供使用。若是沒有足夠資源則fallback 。 有則放行
  • ⑥、執行run或者construct方法。這兩個是hystrix原生的方法,java實現hystrix會實現兩個方法的邏輯,springcloud已經幫咱們封裝了。這裏就不看這兩個方法了。若是執行錯誤或者超時則fallback。在此期間會將日誌採集到監控中心。
  • ⑦、計算熔斷器數據,判斷是否須要嘗試放行;這裏統計的數據會在hystrix.stream的dashboard中查看到。方便咱們定位接口健康狀態
  • ⑧、在流程圖中咱們也能看到④、⑤、⑥都會指向fallback。 也是咱們俗稱的服務降級。可見服務降級是hystrix熱門業務啊。
  • ⑨、返回響應

HystrixDashboard

  • hystrix 除了服務熔斷、降級、限流之外,還有一個重要的特性是實時監控。並造成報表統計接口請求信息。
  • 關於hystrix的安裝也很簡單,只須要在項目中配置actutor和hystrix-dashboard兩個模塊就好了
<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 就引入了dashboard了。 咱們不須要進行任何開發。這個和eureka同樣主須要簡單的引包就能夠了。

image-20210422161743942

  • 就這樣dashboard搭建完成了。dashboard主要是用來監控hystrix的請求處理的。因此咱們還須要在hystrix請求出將端點暴露出來。
  • 在使用了hystrix命令的模塊加入以下配置便可,我就在order模塊加入
@Component
public class HystrixConfig {
    @Bean
    public ServletRegistrationBean getServlet(){
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        //注意這裏配置的/hystrix.stream  最終訪問地址就是 localhost:port/hystrix.stream ; 若是在配置文件中配置在新版本中是須要
        //加上actuator  即 localhost:port/actuator
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}
  • 而後咱們訪問order模塊localhost/hystrix.stream 就會出現ping的界面。表示咱們order模塊安裝監控成功。固然order也須要actuator模塊
  • 下面咱們經過jmeter來壓測咱們的熔斷、降級、限流接口在經過dashboard來看看各個狀態吧。

  • 上面的動畫看起來咱們的服務仍是很忙的。想一想若是是電商當你看着每一個接口的折線圖像不像就是你的心跳。過高的你就擔憂的。過低了就沒有成就高。下面咱們看看dashboard的指標詳情

image-20210422163536850

  • 咱們看看咱們服務運行期間各個接口的現狀。

image-20210422161504688

聚合監控

  • 上面咱們經過新建的模塊hystrix-dashboard 來對咱們的order模塊進行監控。可是實際應用中咱們不可能只在order中配置hystrix的。
  • 咱們只是在上面爲了演示因此在order配置的。如今咱們在payment中也對hystrix中配置。那麼咱們就須要在dashboard中來回切換order、payment的監控數據了。
  • 因此咱們的聚合監控就來了。在進行聚合監控以前咱們先將payment也引入hystrix。注意上面咱們是經過bean方式注入hystrix.stream 的 。 訪問前綴不須要actuator

新建hystrix-turbine

pom

<!--新增hystrix dashboard-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
        </dependency>
  • 主要就是新增turbine座標,其餘的就是hystrix , dashboard等模塊,具體查看結尾處源碼

yml

spring:
  application:
    name: cloud-hystrix-turbine

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    prefer-ip-address: true

# 聚合監控

turbine:
  app-config: cloud-order-service,cloud-payment-service
  cluster-name-expression: "'default'"
  # 該處配置和url同樣。若是/actuator/hystrix.stream 的則須要配置actuator
  instanceUrlSuffix: hystrix.stream

啓動類

啓動類上添加EnableTurbine註解

image-20210423093456668




源碼

上述源碼

image-20210414153359481

相關文章
相關標籤/搜索