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對象,調用者可以根據不同的錯誤類型做不同的降級策略,個人認爲會更友好;

 

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