springcloud學習(三)之Hystrix

前言

雪崩效應

在微服務架構中,⼀個應⽤可能會有多個微服務組成,微服務之間的數據交互經過遠程過程調⽤完成。java

這就帶來⼀個問題,假設微服務A調⽤微服務B和微服務C,微服務B和微服務C⼜調⽤其它的微服務,這就是所謂的「扇出」。若是扇出的鏈路上某個微服務的調⽤響應時間過⻓或者不可⽤,對微服務A的調⽤就會佔⽤愈來愈多的系統資源,進⽽引發系統崩潰,所謂的「雪崩效應」。nginx

如圖中所示,最下游簡歷微服務響應時間過⻓,⼤量請求阻塞,⼤量線程不會釋放,會致使服務器資源耗盡,最終致使上游服務甚⾄整個系統癱瘓。git

扇⼊:表明着該微服務被調⽤的次數,扇⼊⼤,說明該模塊復⽤性好web

扇出:該微服務調⽤其餘微服務的個數,扇出⼤,說明業務邏輯複雜spring

扇⼊⼤是⼀個好事,扇出⼤不⼀定是好事數據庫

雪崩效應的三種解決方案

從系統的可靠性與可用性出發,常見的解決雪崩效應的三種技術方案:服務熔斷、服務降級、服務限流。瀏覽器

服務熔斷

服務熔斷體如今一個「斷」字上,通俗來說,就是當檢測到某個扇出鏈路的微服務不可用或者響應時間太長時,熔斷該服務的調用,進行服務降級,快速返回錯誤的響應信息。當該服務正常以後,再恢復該服務的調用鏈路。
服務熔斷與服務降級是一般在一塊兒使用的,好比Hystrix。
服務消費者工程引入jar座標:springboot

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

服務消費者啓動類上添加@EnableCircuitBreaker開啓熔斷註解。服務器

在服務消費者的controller中調用代碼方法上添加註解@HystrixProperty,裏面配置超時時間屬性,設置爲2秒。多線程

@GetMapping("/checkStateTimeout/{userId}")
    @HystrixCommand(
            commandProperties = {
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",
                    value = "2000")
            }

    )
    public Integer findResumeOpenState(@PathVariable Long userId) {
        String url="http://lagou-service-resume/resume/openstate/"+userId;
        Integer forObject =restTemplate.getForObject(url,Integer.class);
        System.out.println("======>>>從eureka server獲取服務提供者實例:"+url);
        return forObject;
    }

爲了測試,咱們在8080端口的服務提供者工程裏面的方法中添加上線程休眠語句,模擬請求超時:

@GetMapping("/openstate/{userId}")
    public Integer findDefaultResumeState(@PathVariable Long userId) {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return port;
    }

效果測試:

上面咱們說的是服務熔斷,當服務提供者出現異常或者請求超時時,返回給調用方的是異常信息。咱們不想顯示異常頁面,這樣對用戶太不友好,因此咱們返回一個預設的默認值(兜底數據)給服務的調用者,這就是服務降級。

服務降級

服務降級,通俗講就是總體資源不夠⽤了,先將⼀些不關緊的服務停掉(調⽤個人時候,給你返回⼀個預留的值,也叫作兜底數據),待渡過難關⾼峯過去,再把那些服務打開。服務降級⼀般是從總體考慮,就是當某個服務熔斷以後,服務器將再也不被調⽤,此刻客戶端能夠⾃⼰準備⼀個本地的fallback回調,返回⼀個缺省值,這樣作,雖然服務⽔平降低,但好⽍可⽤,⽐直接掛掉
要強。

在上一步服務熔斷的代碼基礎上,咱們添加服務降級的代碼。

咱們定義一個預設方法,與原方法的入參與返回結構類型保持一致,在@HystrixProperty註解裏配置fallbackMethod參數爲預設的方法名便可。

以下,咱們在預設方法中返回默認值-1:

@GetMapping("/checkStateTimeoutFallBack/{userId}")
    @HystrixCommand(
            commandProperties = {
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",
                            value = "2000")
            },
            fallbackMethod = "myFallback"
    )
    public Integer findResumeOpenStateFallBack(@PathVariable Long userId) {
        String url="http://lagou-service-resume/resume/openstate/"+userId;
        Integer forObject =restTemplate.getForObject(url,Integer.class);
        System.out.println("======>>>從eureka server獲取服務提供者實例:"+url);
        return forObject;
    }

    //預設方法
    public Integer myFallback(Long userId){
        return -1; //返回預設值(兜底數據)
    }

效果以下:

服務限流

服務降級是當服務出問題或者影響到核⼼流程的性能時,暫時將服務屏蔽掉,待⾼峯或者問題解決後再打開;可是有些場景並不能⽤服務降級來解決,⽐如秒殺業務這樣的核⼼功能,這個時候能夠結合服務限流來限制這些場景的併發/請求量
限流措施也不少,⽐如:

  • 限制總併發數(⽐如數據庫鏈接池、線程池)
  • 限制瞬時併發數(如nginx限制瞬時併發鏈接數)
  • 限制時間窗⼝內的平均速率(如Guava的RateLimiter、 nginx的limit_req模塊,限制每秒的平均速率)
  • 限制遠程接⼝調⽤速率、限制MQ的消費速率等

Hystrix 艙壁模式(線程池隔離策略)

Hystrix默認是有一個線程池,來統一維護須要熔斷及降級的方法,全部添加了@HystrixCommand註解的方法共用一個線程池。

若是不進⾏任何設置,全部熔斷⽅法使⽤⼀個Hystrix線程池(默認10個線程),那麼這樣的話會致使問題,這個問題並非扇出鏈路微服務不可⽤致使的,⽽是咱們的線程機制致使的,若是⽅法A的請求把10個線程都⽤了,⽅法2請求處理的時候壓根都無法去訪問B,由於沒有線程可⽤,並非B服務不可⽤。

爲了不問題服務請求過多致使正常服務⽆法訪問, Hystrix 不是採⽤增長線程數,⽽是單獨的爲每⼀個控制⽅法建立⼀個線程池的⽅式,這種模式叫作「艙壁模式",也是線程隔離的⼿段。

艙壁模式在現實生活中的例子:

好比一艘船,裏面混亂的放着牛奶、麪包、藥品等各類物品,若是這艘船某一個地方漏水,那麼這艘船裏的全部物品都會受到影響。(相似於Hystrix默認的全局一個線程池管理全部方法)
怎麼解決這個問題呢?
給船艙裏面加上隔板,好比把麪包和藥品完全隔離開,這樣當藥品這邊漏水了以後,也不會影響到麪包。(這就是Hystrix的艙壁模式)

咱們能夠先看一下Hystrix的默認的全部方法共用一個線程池的效果:

在PostMan裏面批量請求加了@HystrixCommand的兩個方法,循環請求二十次,模擬多線程:

在Windows命令行中經過jps及jstack命令查看線程信息:(Linux系統中將findstr命令改成grep命令便可)

能夠看到一共有10個線程。

接下來咱們運用艙壁模式配置線程池參數,部分代碼以下(完整代碼見文末源碼):

@GetMapping("/checkStateTimeout/{userId}")
    @HystrixCommand(
            //線程池 的標識,要保持惟一,否則與其它方法相同名的線程池就共用
            threadPoolKey = "checkStateTimeout",
            //線程數細節配置
            threadPoolProperties = {
                    @HystrixProperty(name="coreSize",value = "1"),  //線程數(同時運行的線程數)
                    @HystrixProperty(name="maxQueueSize",value = "20") //等待隊列長度
            },
            commandProperties = {
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",
                    value = "2000")
            }
    )
    public Integer checkStateTimeout(@PathVariable Long userId) {
        String url="http://lagou-service-resume/resume/openstate/"+userId;
        Integer forObject =restTemplate.getForObject(url,Integer.class);
        System.out.println("======>>>從eureka server獲取服務提供者實例:"+url);
        return forObject;
    }


    @GetMapping("/checkStateTimeoutFallBack/{userId}")
    @HystrixCommand(
            //線程池 的標識,要保持惟一,否則與其它方法相同名的線程池就共用
            threadPoolKey = "findResumeOpenStateFallBack",
            //線程數細節配置
            threadPoolProperties = {
                    @HystrixProperty(name="coreSize",value = "2"),  //線程數(同時運行的線程數)
                    @HystrixProperty(name="maxQueueSize",value = "20") //等待隊列長度
            },

            commandProperties = {
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",
                            value = "2000")
            },
            fallbackMethod = "myFallback"
    )
    public Integer findResumeOpenStateFallBack(@PathVariable Long userId) {
        String url="http://lagou-service-resume/resume/openstate/"+userId;
        Integer forObject =restTemplate.getForObject(url,Integer.class);
        System.out.println("======>>>從eureka server獲取服務提供者實例:"+url);
        return forObject;
    }
    
    //預設方法
    public Integer myFallback(Long userId){
        return -1; //返回預設值(兜底數據)
    }

咱們在代碼中給checkStateTimeout方法配置線程數爲1,給checkStateTimeoutFallBack方法配置線程數爲2,而後在postman中批量運行線程,
結束以後查看線程信息以下:

能夠看到名爲checkStateTimeout的線程只有1個,名爲checkStateTimeoutFallBack的線程有2個,與咱們程序中設置一致。

Hystrix工做流程

在微服務調用鏈路中當某個服務某一次出現問題時,Hystrix並非直接一刀切的切斷有問題的鏈路,今後再也不啓用,而是經過一段時間間隔的閾值來進行檢測判斷,具體的流程以下圖:

工做流程:

  1. 當調⽤出現問題時,開啓⼀個時間窗(10s)

  2. 在這個時間窗內,統計調⽤次數是否達到最⼩請求數(本身設定)
    <1>若是沒有達到,則重置統計信息,回到第1步
    <2>若是達到了,則統計失敗的請求數佔全部請求數的百分⽐,是否達到閾值?
    ①若是達到,則跳閘(再也不請求對應服務)
    ②若是沒有達到,則重置統計信息,回到第1步

  3. 若是跳閘,則會開啓⼀個活動窗⼝(默認5s),每隔5s, Hystrix會讓⼀個請求經過,到達那個問題服務,看是否調⽤成功,若是成功,重置斷路器回到第1步,若是失敗,回到第3步

springboot中暴露健康檢查等斷點接⼝,在服務消費者一方的application.yml中配置以下

management:
  endpoints:
    web:
      exposure:
        include: "*"
# 暴露健康接⼝的細節
  endpoint:
    health:
      show-details: always

查看健康狀態:

接下來咱們配置一些參數,
實現「8秒鐘內,請求次數達到2個,而且失敗率在50%以上,就跳閘,跳閘後活動窗⼝設置爲3s」,經常使用的參數以下:

//8秒鐘內,請求次數達到2個,而且失敗率在50%以上,就跳閘
                   //跳閘後活動窗⼝設置爲3s
                   @HystrixProperty(name =
                           "metrics.rollingStats.timeInMilliseconds",value = "8000"),
                   @HystrixProperty(name =
                           "circuitBreaker.requestVolumeThreshold",value = "2"),
                   @HystrixProperty(name =
                           "circuitBreaker.errorThresholdPercentage",value = "50"),
                   @HystrixProperty(name =
                           "circuitBreaker.sleepWindowInMilliseconds",value = "3000")

在postman中批量執行調用checkStateTimeoutFallBack方法,在健康狀態監測中能夠看到hystrix的狀態變化,由熔斷到恢復:
連續調用幾回後熔斷:

後來變爲正常:

Hystrix Dashboard斷路監控儀表盤

前面經過/actuator/health接口查看到的信息是一個hystrix的總體的運行狀態,要想看具體的細節,好比每一個加了@HystrixCommand註解的方法的hystrix的運行信息的話,咱們應該尋找另外的方式。Hystrix Dashboard斷路監控儀表盤就是幹這個的。

/actuator/hystrix.stream接⼝能夠獲取到監控的⽂字信息,可是不直觀,Hystrix官⽅還提供了基於圖形化的DashBoard(儀表板)監控平臺,咱們能夠經過新建單獨的DashBoard服務經過圖形化的頁面來直觀查看hystrix的詳細信息。

在父工程下新建一個監控工程,導入依賴:

<!--hystrix-->
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
   </dependency>
   <!--hystrix 儀表盤-->
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
   </dependency>

啓動類上加上@EnableHystrixDashboard註解。
在被監測的微服務中註冊監控servlet,咱們在服務啓動類中注入bean:

@Bean
   public ServletRegistrationBean getServlet(){
       HystrixMetricsStreamServlet streamServlet = new
               HystrixMetricsStreamServlet();
       ServletRegistrationBean registrationBean = new
               ServletRegistrationBean(streamServlet);
       registrationBean.setLoadOnStartup(1);
       registrationBean.addUrlMappings("/actuator/hystrix.stream");
       registrationBean.setName("HystrixMetricsStreamServlet");
       return registrationBean;
   }

在被監控微服務發佈以後,能夠直接訪問監控servlet:

能夠看到數據,可是並不直觀。

發佈監控微服務,訪問http://localhost:9000/hystrix以下:

進入監控詳情頁面後以下:

咱們能夠實時的監控方法的狀態。

Hystrix Turbine聚合監控

前面咱們是針對一個微服務實例的Hystrix進行數據查詢分析, 在微服務架構下,⼀個微服務的實例每每是多個(集羣化)

⽐如⾃動投遞微服務咱們能夠部署三臺:

實例1(hystrix) ip1:port1/actuator/hystrix.stream

實例2(hystrix) ip2:port2/actuator/hystrix.stream

實例3(hystrix) ip3:port3/actuator/hystrix.stream

按照已有的⽅法,咱們就能夠結合dashboard儀表盤每次輸⼊⼀個監控數據流url,進去查看。可是這樣比較麻煩,咱們可否有一個聚合的工具把同一個服務的全部實例的Hystrix狀態信息聚合起來呢?
答案是:Hystrix Turbine聚合(聚合各個實例上的hystrix監控數據)

Turbine的原理圖以下:

創建一個hystrix-turbine微服務,引入依賴:

<dependencies>
    <!--hystrix turbine聚合監控-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
    </dependency>
    <!--
    引⼊eureka客戶端的兩個緣由
    一、微服務架構下的服務都儘可能註冊到服務中⼼去,便於統⼀管理
    二、後續在當前turbine項⽬中咱們須要配置turbine聚合的服務,⽐如,咱們但願聚合
    lagou-service-autodeliver這個服務的各個實例的hystrix數據流,那隨後
    咱們就須要在application.yml⽂件中配置這個服務名,那麼turbine獲取服務下具
    體實例的數據流的
    時候須要ip和端⼝等實例信息,那麼怎麼根據服務名稱獲取到這些信息呢?
    固然能夠從eureka服務註冊中⼼獲取
    -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    </dependencies>

application.yml中進行turbine的相關配置:

#turbine配置
turbine:
#appCofing配置須要聚合的服務名稱,⽐如這⾥聚合⾃動投遞微服務的hystrix監控數據
#若是要聚合多個微服務的監控數據,那麼可使⽤英⽂逗號拼接,⽐如 a,b,c
  appConfig: lagou-service-autodeliver
  clusterNameExpression: "'default'" # 集羣默認名稱

在啓動類上添加@EnableTurbine開啓儀表盤以及Turbine聚合。

依次啓動eureka註冊中心、服務提供者、服務消費者、HystrixDashboard服務、HystrixTuibin服務。
完畢後在瀏覽器輸入http://localhost:9001/turbine.stream,在postman中請求8090和8091兩個服務消費者實例,能夠看到turbine的監控信息:

訪問http://localhost:9000/hystrix,將tuibine.stream地址輸入:

postman中批量訪問兩個服務消費者實例:

能夠觀察多臺主機裏的方法聚合的狀態信息:

源碼地址

hystrix-demo源碼地址: hystrix-demo源碼地址

附錄:

idea刪除模塊後建立同名模塊遇到的問題及解決方案

idea複製項目module出現的問題

springboot啓動報沒法加載主類錯誤

歡迎訪問個人博客:https://www.liuyj.top

相關文章
相關標籤/搜索