1. 程式人生 > >SpringCloud系列——限流、熔斷、降級

SpringCloud系列——限流、熔斷、降級

  前言

  分散式環境下,服務直接相互呼叫,一個複雜的業務可能要呼叫多個服務,例如A -> B -> C -> D,當某個服務出現異常(呼叫超時、呼叫失敗等)將導致整個流程阻塞崩潰,嚴重的整個系統都會崩掉,為了實現高可用,必要的保護機制必不可少

  本文記錄限流、熔斷、降級的實現處理

 

  限流

  我們採用令牌桶限流法,並自己實現一個簡單令牌桶限流

  有個任務執行緒以恆定速率向令牌桶新增令牌

  一個請求會消耗一個令牌,令牌桶裡的令牌大於0,才會放行,反正不允許通過

/**
 * 簡單的令牌桶限流
 */
public class RateLimiter {

    /**
     * 桶的大小
     */
    private Integer limit;

    /**
     * 桶當前的token
     */
    private static Integer tokens = 0;

    /**
     * 構造引數
     */
    public RateLimiter(Integer limit, Integer speed){
        //初始化桶的大小,且桶一開始是滿的
        this.limit = limit;
        tokens = this.limit;

        //任務執行緒:每秒新增speed個令牌
        new Thread(() ->{
            while (true){
                try {
                    Thread.sleep(1000L);

                    int newTokens = tokens + speed;
                    if(newTokens > limit){
                        tokens = limit;
                        System.out.println("令牌桶滿了!!!");
                    }else{
                        tokens = newTokens;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * 根據令牌數判斷是否允許執行,需要加鎖
     */
    public synchronized boolean execute() {
        if (tokens > 0) {
            tokens = tokens - 1;
            return true;
        }
        return false;
    }
}

  main簡單測試

    public static void main(String[] args) {
        //令牌桶限流:峰值每秒可以處理10個請求,正常每秒可以處理3個請求
        RateLimiter rateLimiter = new RateLimiter(10, 3);

        //模擬請求
        while (true){
            //在控制檯輸入一個值按回車,相對於發起一次請求
            Scanner scanner = new Scanner(System.in);
            scanner.next();

            //令牌桶返回true或者false
            if(rateLimiter.execute()){
                System.out.println("允許訪問");
            }else{
                System.err.println("禁止訪問");
            }
        }
    }

  在SpringCloud分散式下實現限流,需要把令牌桶的維護放到一個公共的地方,比如Zuul路由,當然也可以同時針對具體的每個服務進行單獨限流

  另外,guava裡有現成的基於令牌桶的限流實現,引入

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>26.0-jre</version>
        </dependency>

  具體用法這裡就不闡述了

 

  我們找出之前的springcloud專案,在zuul-server中的AccessFilter過濾器進行限流,其他的都不變,只需要做如下修改

  PS:我這裡為了方便測試,調小了令牌桶的大小,跟速率,正常情況下要伺服器的承受能力來定

/**
 * Zuul過濾器,實現了路由檢查
 */
public class AccessFilter extends ZuulFilter {
    //令牌桶限流:峰值每秒可以處理10個請求,正常每秒可以處理3個請求
//PS:我這裡為了方便測試,調小了令牌桶的大小,跟速率,正常情況下按伺服器的承受能力來定 private RateLimiter rateLimiter = new RateLimiter(2, 1); //業務不變,省略其他程式碼... /** * 過濾器的具體邏輯 */ @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); HttpServletResponse response = ctx.getResponse(); //限流 if(!rateLimiter.execute()){ try { ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(200); //直接寫入瀏覽器 response.setContentType("text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); writer.println("系統繁忙,請稍後在試!<br/>System busy, please try again later!"); writer.flush();return null; } catch (Exception e) { e.printStackTrace(); } } //業務不變,省略其他程式碼.. } }

  按照我們設定的值,一秒能處理一個請求,峰值一秒能處理兩個請求,下面瘋狂重新整理進行測試

 

 

  熔斷

  yml配置開啟Hystrix熔斷功能,進行容錯處理

feign:
  hystrix:
    enabled: true

  設定Hystrix的time-out時間

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000 #毫秒
      #或者設定從不超時
      #timeout:
      #  enabled: false

  在使用Feign呼叫服務提供者時配置@FeignClient的 fallback,進行容錯處理(服務提供者發生異常),如果需要獲取到異常資訊,則要配置fallbackFactory<T>

@FeignClient(name = "sso-server", path = "/",/*fallback = SsoFeign.SsoFeignFallback.class,*/fallbackFactory = SsoFeign.SsoFeignFallbackFactory.class)

 

    /**
     * 容錯處理(服務提供者發生異常,將會進入這裡)
     */
    @Component
    public class SsoFeignFallback implements SsoFeign {

        @Override
        public Boolean hasKey(String key) {
            System.out.println("呼叫sso-server失敗,進行SsoFeignFallback.hasKey處理:return false;");
            return false;
        }
    }

 

    /**
     * 只打印異常,容錯處理仍交給 SsoFeignFallback
     */
    @Component
    public class SsoFeignFallbackFactory implements FallbackFactory<SsoFeign> {
        private final SsoFeignFallback ssoFeignFallback;

        public SsoFeignFallbackFactory(SsoFeignFallback ssoFeignFallback) {
            this.ssoFeignFallback = ssoFeignFallback;
        }

        @Override
        public SsoFeign create(Throwable cause) {
            cause.printStackTrace();
            return ssoFeignFallback;
        }
    }

 

  FallbackFactory也可以這樣寫

    /**
     * 容錯處理
     */
@Component
public class SsoFeignFallbackFactory implements FallbackFactory<SsoFeign> { @Override public SsoFeign create(Throwable cause) { //列印異常 cause.printStackTrace(); return new SsoFeign() { @Override public Boolean hasKey(String key) { System.out.println("呼叫sso-server失敗:return false;"); return false; } }; } }

 

  因為我們沒有啟動Redis,報錯,但我們進行容錯處理,所以還是返回了false

 

 

  降級

   當呼叫服務傳送異常,容錯處理的方式有多種,我們可以:

  1、重連,比如服務進行了限流,本次連線被限制,重連一次或N次就可以得到資料

  2、直接返回一個友好提示

  3、降級呼叫備用服務、返回快取的資料等

 

  後記

  降級也可以叫做“備胎計劃&rdquo