1. 程式人生 > 其它 >SpringCloud-Hystrix

SpringCloud-Hystrix

Hystrix介紹

在分散式系統中,每個服務都可能會呼叫很多其他服務,被呼叫的那些服務就是依賴服務,有的時候某些依賴服務出現故障也是很正常的。

Hystrix可以讓我們在分散式系統中對服務間的呼叫進行控制,加入一些呼叫延遲或者依賴故障的容錯機制。

Hystrix通過將依賴服務進行資源隔離,進而組織某個依賴服務出現故障的時候,這種故障在整個系統所有的依賴服務呼叫中進行蔓延,同時Hystrix還提供故障時的fallback降級機制

總而言之,Hystrix通過這些方法幫助我們提升分散式系統的可用性和穩定性。

雪崩問題

微服務中,服務間呼叫關係錯綜複雜,一個請求,可能需要呼叫多個微服務接口才能實現,會形成非常複雜的呼叫鏈路

假如,我們此時的 微服務I 發生了異常,請求阻塞,使用者請求就不會得到響應,則該執行緒就不會得到釋放,於是越來越多的使用者請求到來,就會有越來越多的執行緒阻塞

伺服器支援的執行緒和併發數有限,請求一直阻塞,會導致伺服器資源耗盡,從而導致所有其它服務都不可用,形成雪崩效應。

更直白的說,多個微服務之間呼叫的時候,假設微服務A呼叫微服務B和微服務C,微服務B和微服務C又呼叫其他的微服務,這就是所謂的“扇出”,如果扇出的鏈路上某個微服務的呼叫響應時間過長,或者不可用,對微服務A的呼叫就會佔用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”。

Hystrix的使用

服務熔斷

當扇出鏈路的某個微服務不可用或者響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的呼叫,快速返回錯誤的響應資訊,防止系統雪崩,直接熔斷整個服務,而不是一直等到此服務超時

。所以我們可以針對某個介面中的所有方法總的來做一次熔斷處理。檢測到該節點微服務呼叫響應正常後恢復呼叫鏈路。在SpringCloud框架裡熔斷機制通過Hystrix實現。Hystrix會監控微服務間呼叫的狀況,當失敗的呼叫到一定閥值預設是5秒內20次呼叫失敗,就會啟動熔斷機制。熔斷機制的註解是:@HystrixCommand


服務熔斷通常在我們的服務提供方進行編寫

相關Maven

<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-eureka-client</artifactId> </dependency>

主要關注controller層,有點類似於捕獲異常,當我們某個請求丟擲異常時,使用@HystrixCommand指定丟擲異常後走的另一個備用的方法

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/getUsername")
    @HystrixCommand(fallbackMethod = "hystrixGetUserByName")
    public User getUserByName(@RequestParam("username") String username){
        User user = userService.getUserByName(username);
        if(user==null){      //這裡需要做判斷丟擲異常,然後熔斷接收到異常才會去走備份的服務方法
            throw new RuntimeException();
        }
        return user;
    }

    //熔斷備選方案  
    public User hystrixGetUserByName(@RequestParam("username") String username){
        return new User().setUsername(username + "使用者不存在");
    }
}

application.yml

server:
  port: 8081

mybatis:
  type-aliases-package: com.chen.springcloudapi.pojo
  mapper-locations: classpath:mappers/*.xml

spring:
  application:
    name: springcloud-provider-user

  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://192.168.111.129:3306/test?characterEncoding=UTF-8&useSSL=false
    driver-class-name: com.mysql.jdbc.Driver

#Eureka的配置
eureka:
  client:
    service-url:
      defaultZone: http://Eureka-Server1:7001/eureka/,http://Eureka-Server2:7002/eureka/     #Eureka叢集只需要用逗號分隔即可
  instance:
    instance-id: springclou-provider-user-hystrix-8081       #修改在Eureka上的預設描述資訊

啟動類

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix         //開啟Hystrix
public class SpringcloudProviederUserHystrix8081Application {

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

}

此時當我們的服務提供方的 /getUsername 請求丟擲異常時,就會走 hystrixGetUserByName 備用的服務方法

 而沒有做熔斷的方法將會直接丟擲異常

因此,為了避免因某個微服務後臺出現異常或錯誤而導致整個應用或網頁報錯,使用熔斷是必要的

服務降級

服務降級是指 當伺服器壓力劇增的情況下,根據實際業務情況及流量,對一些服務和頁面有策略的不處理,或換種簡單的方式處理,從而釋放伺服器資源以保證核心業務正常運作或高效運作。說白了,就是儘可能的把系統資源讓給優先順序高的服務

資源有限,而請求是無限的。如果在併發高峰期,不做服務降級處理,一方面肯定會影響整體服務的效能,嚴重的話可能會導致宕機某些重要的服務不可用。所以,一般在高峰期,為了保證核心功能服務的可用性,都要對某些服務降級處理。比如當雙11活動時,把交易無關的服務統統降級,如檢視螞蟻深林,檢視歷史訂單等等。

服務降級主要用於什麼場景呢?當整個微服務架構整體的負載超出了預設的上限閾值或即將到來的流量預計將會超過預設的閾值時,為了保證重要或基本的服務能正常執行,可以將一些 不重要 或 不緊急 的服務或任務進行服務的 延遲使用 或 暫停使用。

降級的方式可以根據業務來,可以延遲服務,比如延遲給使用者增加積分,只是放到一個快取中,等服務平穩之後再執行 ;或者在粒度範圍內關閉服務,比如關閉相關文章的推薦。

自動降級分類

1)超時降級:主要配置好超時時間和超時重試次數和機制,並使用非同步機制探測回覆情況

2)失敗次數降級:主要是一些不穩定的api,當失敗呼叫次數達到一定閥值自動降級,同樣要使用非同步機制探測回覆情況

3)故障降級:比如要呼叫的遠端服務掛掉了(網路故障、DNS故障、http服務返回錯誤的狀態碼、rpc服務丟擲異常),則可以直接降級。降級後的處理方案有:預設值(比如庫存服務掛了,返回預設現貨)、兜底資料(比如廣告掛了,返回提前準備好的一些靜態頁面)、快取(之前暫存的一些快取資料)

4)限流降級:秒殺或者搶購一些限購商品時,此時可能會因為訪問量太大而導致系統崩潰,此時會使用限流來進行限制訪問量,當達到限流閥值,後續請求會被降級;降級後的處理方案可以是:排隊頁面(將使用者導流到排隊頁面等一會重試)、無貨(直接告知使用者沒貨了)、錯誤頁(如活動太火爆了,稍後重試)。


服務熔斷是應用於我們的服務提供方,服務降級是應用於我們的呼叫方(消費者)

Feign降級

Feign降級的話要看我們的Feign寫在哪裡,像我們之前Feign是寫在服務提供方API中的,所以我們降級的方法也直接在服務提供方API中編寫即可

UserClientServiceFallbackFactory

不難看出,其實就是重寫了一下Feign的介面方法

@Component
public class UserClientServiceFallbackFactory implements FallbackFactory<UserClientService> {

    @Override
    public UserClientService create(Throwable cause) {
        return new UserClientService() {

            @Override
            public String getUser() {
                return null;
            }

            @Override
            public User getUserByName(String username) {
                return new User().setUsername(username + "使用者不存在,沒有對應的資訊,客戶端提供了降級的資訊,這個服務現在已經被關閉");
            }
        };
    }
}

UserClientService

修改FeignClient,新增fallbackFactory,指向我們的降級類

@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-USER", fallbackFactory = UserClientServiceFallbackFactory.class)  //fallbackFactory指定降級配置類
public interface UserClientService {

    @RequestMapping("/get")
    public String getUser();

    @RequestMapping("/getUsername")
    public User getUserByName(@RequestParam("username") String username);
}

然後消費者中開啟我們的hystrix

application.yml

# 開啟降級feign.hystrix
feign:
  hystrix:
    enabled: true

#springcloud2020版本以後則是使用下面的配置
#feign:
#  circuitbreaker:
#    enabled: true

然後我們需要在啟動類上配置@ComponentScan,將我們服務提供者的API進行掃描,主要是要掃描到我們的降級方法。但是這裡有一個問題,就是我們@SpringbootApplication中是包含了 @ComponentScan 註解的,所以這兩者不能同時用,如果同時用了,@SpringBootApplication 註解自帶的 @ComponentScan 註解就不生效了,這樣會導致啟動類所在的包,除了被自己加的這個 @ComponentScan 關聯的會對映到,原本的反而都對映不到了,所以這時候有幾種方案可以選擇

方法一:掃描多個類,將目標API和本地包都進行掃描

@ComponentScan(basePackages=
        {"com.chen.springcloudapi",
        "com.chen.springcloudconsumeruserfeign"}
)

方法二:如果本地包和目標API包名稱字首是一致的話可以直接寫成下列方式,這樣寫就同樣可以掃描到目標API和本地包了

@ComponentScan("com.chen")

方法三:使用@ComponentScans註解代替@ComponentScan

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.chen.springcloudapi"})  //寫在消費端的話其實可以不用編寫basePackages
@ComponentScans({@ComponentScan("com.chen.springcloudapi")})
public class SpringcloudConsumerUserFeignApplication {

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

}

配置完成後,啟動我們的Eureka、服務提供者、消費者

然後關閉我們手動關閉服務提供者再次訪問

服務熔斷和服務降級區別 

  • 服務熔斷—>服務端:某個服務超時或異常,引起熔斷~,類似於保險絲(自我熔斷)
  • 服務降級—>客戶端:從整體網站請求負載考慮,當某個服務熔斷或者關閉後,服務將不再呼叫,此時在客戶端,我們可以準備一個 FallBackFactory ,返回一個預設的值(預設值)。會導致整體的服務下降,但是好歹能用,比直接掛掉強。
  • 觸發原因不太一樣,服務熔斷一般是某個服務(下游服務)故障引起,而服務降級一般是從整體負荷考慮;管理目標的層次不太一樣,熔斷其實是一個框架級的處理,每個微服務都需要(無層級之分),而降級一般需要對業務有層級之分(比如降級一般是從最外圍服務開始)
  • 實現方式不太一樣,服務降級具有程式碼侵入性(由控制器完成/或自動降級),熔斷一般稱為自我熔斷