微服務:SpringCloud hystrix 服務熔斷、斷路器、隔離

一、雪崩效應

在微服務架構中,一個請求需要調用多個服務是非常常見的。如客戶端訪問A服務,而A服務需要調用B 服務,B服務需要調用C服務,由於網絡原因或者自身的原因,如果B服務或者C服務不能及時響應,A服 務將處於阻塞狀態,直到B服務C服務響應。此時若有大量的請求涌入,容器的線程資源會被消耗完畢, 導致服務癱瘓。服務與服務之間的依賴性,故障會傳播,造成連鎖反應,會對整個微服務系統造成災難性的嚴重後果,這就是服務故障的“雪崩效應。
 
雪崩是系統中的蝴蝶效應導致其發生的原因多種多樣,有不合理的容量設計,或者是高併發下某一個方 法響應變慢,亦或是某臺機器的資源耗盡。從源頭上我們無法完全杜絕雪崩源頭的發生,但是雪崩的根本原因來源於服務之間的強依賴,所以我們可以提前評估,做好熔斷,隔離,限流。
 
服務隔離:顧名思義,它是指將系統按照一定的原則劃分爲若干個服務模塊,各個模塊之間相對獨立,無強依賴。 當有故障發生時,能將問題和影響隔離在某個模塊內部,而不擴散風險,不波及其它模塊,不影響整體 的系統服務。
 
熔斷降級 :熔斷這一概念來源於電子工程中的斷路器(Circuit Breaker)。在互聯網系統中,當下遊服務因訪問壓 力過大而響應變慢或失敗,上游服務爲了保護系統整體的可用性,可以暫時切斷對下游服務的調用。這種犧牲局部,保全整體的措施就叫做熔斷。
 
所謂降級,就是當某個服務熔斷之後,服務器將不再被調用,此時客戶端可以自己準備一個本地的 fallback回調,返回一個缺省值。 也可以理解爲兜底。
 
服務限流:限流可以認爲服務降級的一種,限流就是限制系統的輸入和輸出流量已達到保護系統的目的。一般來說系統的吞吐量是可以被測算的,爲了保證系統的穩固運行,一旦達到的需要限制的閾值,就需要限制流 量並採取少量措施以完成限制流量的目的。比方:推遲解決,拒絕解決,或者者部分拒絕解決等等。

 

二、hrstrix組件

Hystrix是由Netflflix開源的一個延遲和容錯庫,用於隔離訪問遠程系統、服務或者第三方庫,防止級聯失敗,從而提升系統的可用性與容錯性。Hystrix主要通過以下幾點實現延遲和容錯。
  • 包裹請求:使用HystrixCommand包裹對依賴的調用邏輯,每個命令在獨立線程中執行。這使用了設計模式中的“命令模式。 
  • 跳閘機制:當某服務的錯誤率超過一定的閾值時,Hystrix可以自動或手動跳閘,停止請求該服務一段時間。
  • 資源隔離:Hystrix爲每個依賴都維護了一個小型的線程池(或者信號量)。如果該線程池已滿, 發往該依賴的請求就被立即拒絕,而不是排隊等待,從而加速失敗判定。 
  • 監控:Hystrix可以近乎實時地監控運行指標和配置的變化,例如成功、失敗、超時、以及被拒絕的請求等。
  • 回退機制:當請求失敗、超時、被拒絕,或當斷路器打開時,執行回退邏輯。回退邏輯由開發人員自行提供,例如返回一個缺省值。
  • 自我修復:斷路器打開一段時間後,會自動進入半開狀態。

三、Rest實現服務熔斷

(1)order-service

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring_cloud_hystrix_demo</artifactId>
        <groupId>cn.itcast</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order_service_rest</artifactId>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-commons</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--引入EurekaClient-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!--springcloud整合的openFeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>
</project>
server:
  port: 9004 #端口
spring:
  application:
    name: service-order #服務名稱
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
    username: root
    password: 111111
  jpa:
    database: MySQL
    show-sql: true
    open-in-view: true
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址註冊
package cn.itcast.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
//激活hystrix
@EnableCircuitBreaker
@EntityScan("cn.itcast.order.entity")
public class RestOrderApplication {

	@LoadBalanced
	@Bean
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}

	public static void main(String[] args) {
		SpringApplication.run(RestOrderApplication.class,args);
	}
}
package cn.itcast.order.controller;

import cn.itcast.order.entity.Product;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/order")
public class OrderController {

	@Autowired
	private RestTemplate restTemplate;


	/**
	 * 使用註解配置熔斷保護
	 *     fallbackmethod : 配置熔斷之後的降級方法
	 */
	@HystrixCommand(fallbackMethod = "orderFallBack")
	@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
	public Product findById(@PathVariable Long id) {
		return restTemplate.getForObject("http://service-product/product/1",Product.class);
	}


	/**
	 * 降級方法
	 *  和需要收到保護的方法的返回值一致
	 *  方法參數一致
	 */
	public Product orderFallBack(Long id) {
		Product product = new Product();
		product.setProductName("觸發降級方法");
		return product;
	}

}

(2)service-product 不變


	@RequestMapping(value = "/{id}",method = RequestMethod.GET)
	public Product findById(@PathVariable Long id) {
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Product product = productService.findById(id);
		product.setProductName("訪問的服務地址:"+ip + ":" + port);
		return product;
	}

輸入http://localhost:9004/order/buy/1

有的同學可能會問,這不是高併發爲啥,還這樣咧?降級了。
 
超時設置
 
默認訪問服務接口,如果超過一秒,就降級。這裏可以去配置文件配置。
hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: ExecutionIsolationStrategy.SEMAPHORE #信號量隔離
          strategy: # ExecutionIsolationStrategy.THREAD 線程池隔離
          thread:
            timeoutInMilliseconds: 3000 #默認的連接超時時間1秒,若1秒沒有返回數據,自動的觸發降級邏輯

統一降級接口:如果我們的服務有多個接口需要降級,該怎麼辦呢?

添加統一降級方法,和@DefaultProperties註解在類上面。這樣就不需要給某個接口特地的指定某個方法
 

package cn.itcast.order.controller;

import cn.itcast.order.entity.Product;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/order")
/**
 * @DefaultProperties : 指定此接口中公共的熔斷設置
 *      如果過在@DefaultProperties指定了公共的降級方法
 *      在@HystrixCommand不需要單獨指定了
 */
@DefaultProperties(defaultFallback = "defaultFallBack")
public class OrderController {

	@Autowired
	private RestTemplate restTemplate;


	/**
	 * 使用註解配置熔斷保護
	 *     fallbackmethod : 配置熔斷之後的降級方法
	 */
	@HystrixCommand
	@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
	public Product findById(@PathVariable Long id) {
		return restTemplate.getForObject("http://service-product/product/1",Product.class);
	}


	/**
	 * 指定統一的降級方法
	 *  * 參數 : 沒有參數
	 */
	public Product defaultFallBack() {
		Product product = new Product();
		product.setProductName("觸發統一的降級方法");
		return product;
	}



}

測試:

四、Feign實現服務熔斷

添加依賴:

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

feign中開啓配置hystrix:

feign:
  client:
    config:
      service-product:  #需要調用的服務名稱
        loggerLevel: FULL
  #開啓對hystrix的支持
  hystrix:
    enabled: true

配置一個feign的降級邏輯類:

package cn.itcast.order.feign;

import cn.itcast.order.entity.Product;
import org.springframework.stereotype.Component;

@Component
public class ProductFeignClientCallBack implements ProductFeignClient {

	/**
	 * 熔斷降級的方法
	 */
	@Override
	public Product findById(Long id) {
		Product product = new Product();
		product.setProductName("feign調用觸發熔斷降級方法");
		return product;
	}
}
package cn.itcast.order.feign;

import cn.itcast.order.entity.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 聲明需要調用的微服務名稱
 *  @FeignClient
 *      * name : 服務提供者的名稱
 *      * fallback : 配置熔斷髮生降級方法
 *                  實現類
 */
@FeignClient(name="service-product",fallback = ProductFeignClientCallBack.class)
public interface ProductFeignClient {

	/**
	 * 配置需要調用的微服務接口
	 */
	@RequestMapping(value="/product/{id}",method = RequestMethod.GET)
	public Product findById(@PathVariable("id") Long id);
}

 啓動類:

package cn.itcast.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EntityScan("cn.itcast.order.entity")
//激活Feign
@EnableFeignClients
//激活hystrix
@EnableCircuitBreaker
public class FeignOrderApplication {

	public static void main(String[] args) {
		SpringApplication.run(FeignOrderApplication.class,args);
	}
}

測試:

降級成功。

五、hystrix的監控平臺

除了實現容錯功能,Hystrix還提供了近乎實時的監控,HystrixCommand和 HystrixObservableCommand在執行時,會生成執行結果和運行指標。比如每秒的請求數量,成功數 量等。這些狀態會暴露在Actuator提供的/health端點中。只需爲項目添加 spring-boot-actuator 依 賴,重啓項目,訪問http://localhost:9001/actuator/hystrix.stream ,即可看到實時的監控數據。

這樣有助於我們去看哪些接口出現了降級

使用feign服務來驗證。

引入依賴

<!--引入hystrix的監控信息-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
   

 

配置暴露接口:

management:
  endpoints:
    web:
      exposure:
        include: '*'

就是這樣的,那麼大家是不是感覺這樣很不方便看啊

 解決辦法:

引入依賴

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

啓動類添加一個註解:

http://localhost:9003/hystrix

這三個0有一些特別的含義:

第一個代表:成功的次數,第二個代表熔斷的次數,第三個代表錯誤請求的次數。

 

六、斷路器聚合監控Turbine

在微服務架構體系中,每個服務都需要配置Hystrix DashBoard監控。如果每次只能查看單個實例的監 控數據,就需要不斷切換監控地址,這顯然很不方便。要想看這個系統的Hystrix Dashboard數據就需 要用到Hystrix TurbineTurbine是一個聚合Hystrix 監控數據的工具,他可以將所有相關微服務的 Hystrix 監控數據聚合到一起,方便使用。引入Turbine後,整個監控系統架構如下:
 
 

創建一個新的工程:

引入依賴:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring_cloud_hystrix_demo</artifactId>
        <groupId>cn.itcast</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hystrix_turbine</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
    </dependencies>
</project>

配置啓動類

package cn.itcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.turbine.EnableTurbine;

@SpringBootApplication
//trubin配置
@EnableTurbine
@EnableHystrixDashboard
public class TurbinAppliation {

	public static void main(String[] args) {
		SpringApplication.run(TurbinAppliation.class,args);
	}
}

配置文件

server:
  port: 8031
spring:
  application:
    name: hystrix-turbine
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true
turbine:
  # 要監控的微服務列表,多個用,分隔
  appConfig: service-order
  clusterNameExpression: "'default'"

測試:

這是跟剛纔一樣的,輸入url就可以了。(http://localhost:8031/turbine.stream)

七、斷路器

狀態:

熔斷器有三個狀態 CLOSED OPEN HALF_OPEN 熔斷器默認關閉狀態,當觸發熔斷後狀態變更爲 OPEN ,在等待到指定的時間,Hystrix會放請求檢測服務是否開啓,這期間熔斷器會變爲 HALF_OPEN 半 開啓狀態,熔斷探測服務可用則繼續變更爲 CLOSED 關閉熔斷器。
  • Closed:關閉狀態(斷路器關閉),所有請求都正常訪問。代理類維護了最近調用失敗的次數,如果某次調用失敗,則使失敗次數加1。如果最近失敗次數超過了在給定時間內允許失敗的閾值,則代理類切換到斷開(Open)狀態。此時代理開啓了一個超時時鐘,當該時鐘超過了該時間,則切換到半斷開(Half-Open)狀態。該超時時間的設定是給了系統一次機會來修正導致調用失敗的錯誤。
  • Open:打開狀態(斷路器打開),所有請求都會被降級。Hystix會對請求情況計數,當一定時間內失敗請求百分比達到閾值,則觸發熔斷,斷路器會完全關閉。默認失敗比例的閾值是50%,請求次數最少不低於20次。
  • Half Open:半開狀態,open狀態不是永久的,打開後會進入休眠時間(默認是5S)。隨後斷路器會自動進入半開狀態。此時會釋放1次請求通過,若這個請求是健康的,則會關閉斷路器,否則 繼續保持打開,再次進行5秒休眠計時。

下面結合一張小圖,給大家說一下:

大致就是這麼一張圖,還有一個圖,結合一起來說。

(1)用戶對訂單系統發起訪問,訂單系統調用遠程的微服務。調用過程可能發生一些意外,導致熔斷。

(2)起初斷路器是關閉的狀態,放行所有的請求,如果調用某一個接口,一直失敗。這裏有一個閾值(比如20次,50%以上的失敗率)。那麼斷路器的狀態就變成了打開。否則還是關閉。

(3)一旦斷路器打開,所有請求這個接口的請求,均降級處理。這裏打開狀態有一個時間,如果狀態超過了是這個時間,就自動調用一次對應微服務的接口,如果成功,狀態就變成關閉。否則還是打開,再等一段時間測試。

八、隔離策略

微服務使用Hystrix熔斷器實現了服務的自動降級,讓微服務具備自我保護的能力,提升了系統的穩定性,也較好的解決雪崩效應。其使用方式目前支持兩種策略:
  • 線程池隔離策略:使用一個線程池來存儲當前的請求,線程池對請求作處理,設置任務返回處理超時時間,堆積的請求堆積入線程池隊列。這種方式需要爲每個依賴的服務申請線程池,有一定的資 源消耗,好處是可以應對突發流量(流量洪峯來臨時,處理不完可將數據存儲到線程池隊裏慢慢處 理)
  • 信號量隔離策略:使用一個原子計數器(或信號量)來記錄當前有多少個線程在運行,請求來先判斷計數器的數值,若超過設置的最大線程個數則丟棄改類型的新請求,若不超過則執行計數操作請求來計數器+1,請求返回計數器-1。這種方式是嚴格的控制線程且立即返回模式,無法應對突發流量(流量洪峯來臨時,處理的線程超過數量,其他的請求會直接返回,不繼續去請求依賴的服務)
線程池和型號量兩種策略功能支持對比如下:
 

配置文件配置:

  • hystrix.command.default.execution.isolation.strategy : 配置隔離策略ExecutionIsolationStrategy.SEMAPHORE 信號量隔離、ExecutionIsolationStrategy.THREAD 線程池隔離
  • hystrix.command.default.execution.isolation.maxConcurrentRequests : 最大信號量上限
 

 

源碼:[email protected]:Zesystem/springclouddemohystrix.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章