1. 程式人生 > 其它 >springcloud元件梳理之hystrix

springcloud元件梳理之hystrix

在微服務架構體系中,各服務中間的相互呼叫是常態,沒有哪個服務能保證自身百分百不會出問題,然後再加上網路的波動以及環境等問題,服務間呼叫的穩定性無法保證,這時候就需要一個有容錯能力的元件來介入,當調用出現問題時能夠做出及時響應,確保使用者的體驗和服務本身不受影響;而hystrix就是這樣一個具備容錯能力的元件,可以通過hystrix來實現服務的熔斷、降級和隔離等功能, 從而提升服務的可用性與容錯性 ;下面先簡單介紹下熔斷、降級和隔離;

熔斷

當服務調用出現問題時實現快速失敗的一種手段,避免佔用伺服器資源造成宕機甚至雪崩的風險;

降級

當服務不可用時給客戶端友好響應的一種處理手段,具體可根據實際業務需求角度來考慮降低的具體方案;

隔離

當請求量激增時為了保護整個微服務不被搞垮可以使用服務內的服務隔離來解決;hystrix的隔離策略分為訊號量隔離和執行緒池隔離兩種方式,預設使用的是執行緒池隔離;

訊號量隔離維護的是web容器(如tomcat)的執行緒,不需要服務內部開啟執行緒,更輕量;由於使用的是web容器的請求執行緒,導致其不支援非同步呼叫,不能單獨為其設定超時機制;而執行緒池隔離是由hystrix自己維護的執行緒進行遠端呼叫,可以做成非同步呼叫,但其又新增了執行緒的開銷和維護;所以當服務屬於io密集型時可以選擇執行緒池隔離策略,而當服務更多的是執行本地計算等cpu密集型時可考慮使用訊號量隔離;當請求達到訊號量或是執行緒池設定的執行緒數的上限時,請求會被直接拒絕,等到訊號量有餘量或者執行緒池有空閒了再接納請求,這樣即使微服務中的某一請求出現異常了也不至於導致整個服務異常;

下面上原始碼來看看hystrix該如何配置上述功能

首先引入依賴:

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

hystrix整合restTemplate

  啟動類新增@EnableCircuitBreaker註解,然後再需要做熔斷的方法上新增@HystrixCommand註解並指定熔斷後處理的方法即可,程式碼如下:

package com.darling.eureka.consumer.service.impl;

import com.darling.eureka.consumer.service.TestRestTemplateService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URI;

/**
 * @description:
 * @author: dll
 * @date: Created in 2021/9/23 11:21
 * @version:
 * @modified By:
 */
@Service
public class TestRestTemplateServiceImpl implements TestRestTemplateService {

    @Resource
    private RestTemplate restTemplate;

    @Override
    @HystrixCommand(fallbackMethod = "sayRestHiCallBack")
    public String sayRestHi(String name) {
        String url = "http://EUREKA-PROVIDER//serverApis/test/sayHi?name="+name;
        String object = restTemplate.getForObject(url, String.class);
        return object;
    }

    /**
     * 當sayRestHi方法發起遠端呼叫失敗時呼叫本方法
     * @param name
     * @return
     */
    public String sayRestHiCallBack(String name) {
        return "向"+name+"sayRestHi失敗啦!";
    }
}

hystrix整合Feign

由於feign本身支援hystrix,所以只需在配置檔案開啟hystrix的開關即可:feign.hystrix.enabled=true;有兩種整合方式,一種是回撥普通類另一種是回撥一個工廠類,具體配置如下:

  回撥普通類:

  在@FeignClient註解上新增屬性fallback,值為指定的處理熔斷方法的類,該類需實現@FeignClient註解所在的service;配置程式碼如下:

package com.darling.eureka.consumer.service;

import com.darling.api.service.UserService;
import com.darling.eureka.consumer.model.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/**
 * @description:  通過openFeign遠端呼叫服務提供者
 * @author: dll
 * @date: Created in 2021/9/14 12:29
 * @version: 1.0
 * @modified By:
 */
@FeignClient(name = "EUREKA-PROVIDER",fallback = UserClientFallback.class)
public interface UserApiService extends UserService{

    @GetMapping("/serverApis/test/sayHi?name={name}")
    String sayHi(@PathVariable String name);

    /**
     * 測試 插入一條資訊
     * @param user
     * @return
     */
    @GetMapping("/serverApis/test/insertInfo")
    String insertInfo(@RequestBody User user);

}

  處理熔斷方法的類程式碼如下:

package com.darling.eureka.consumer.service;

import com.darling.api.model.UserInfo;
import com.darling.eureka.consumer.model.User;
import org.springframework.stereotype.Component;

/**
 * @description: 基於htstrix封裝的處理feign調用出錯的熔斷策略
 * @author: dll
 * @date: Created in 2021/9/22 12:08
 * @version:
 * @modified By:
 */
@Component
public class UserClientFallback implements UserApiService {

    @Override
    public String sayHi(String name) {
        return "我被降級了。。。";
    }

    @Override
    public String insertInfo(User user) {
        return null;
    }

    @Override
    public UserInfo test() {
        return null;
    }

    @Override
    public UserInfo getInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setServerPort("我被降級啦");
        return userInfo;
    }
}

  回撥工廠類:

  在@FeignClient註解上新增屬性fallbackFactory,值為指定的處理熔斷方法的工廠類,該類需實現FallbackFactory;配置程式碼如下:

package com.darling.eureka.consumer.service;

import com.darling.api.service.UserService;
import com.darling.eureka.consumer.model.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/**
 * @description:  通過openFeign遠端呼叫服務提供者
 * @author: dll
 * @date: Created in 2021/9/14 12:29
 * @version: 1.0
 * @modified By:
 */
@FeignClient(name = "EUREKA-PROVIDER",fallbackFactory = UserClientFallbackFactory.class)
public interface UserApiService extends UserService{

    @GetMapping("/serverApis/test/sayHi?name={name}")
    String sayHi(@PathVariable String name);

    /**
     * 測試 插入一條資訊
     * @param user
     * @return
     */
    @GetMapping("/serverApis/test/insertInfo")
    String insertInfo(@RequestBody User user);

}

工廠類程式碼如下:

package com.darling.eureka.consumer.service;

import com.darling.api.model.UserInfo;
import com.darling.eureka.consumer.model.User;
import com.netflix.hystrix.exception.HystrixTimeoutException;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * @description: 基於htstrix封裝的處理feign調用出錯的熔斷策略工廠
 * @author: dll
 * @date: Created in 2021/9/22 13:06
 * @version:
 * @modified By:
 */
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserApiService> {


    @Override
    public UserApiService create(Throwable throwable) {

        return new UserApiService() {
            @Override
            public String sayHi(String name) {
                System.out.println("throwable = " + throwable);
                if (throwable instanceof HystrixTimeoutException) {
                    return "連線超時了";
                }
                return "系統異常";
            }

            @Override
            public String insertInfo(User user) {
                return null;
            }

            @Override
            public UserInfo test() {
                return null;
            }

            @Override
            public UserInfo getInfo() {
                UserInfo userInfo = new UserInfo();
                userInfo.setServerPort("我被降級啦");
                return userInfo;
            }
        };
    }
}

  兩種回撥方法總結:

使用普通類回撥無法定位具體錯誤,通過工廠類回撥由於引數會傳入一個Throwable物件,呼叫者可以根據不同的錯誤型別做不同的降級策略,個人認為會更友好;

希望每get一個知識點都能堅持用部落格記錄下來,加油!