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、降級調用備用服務、返回緩存的數據等

 

  后記

  降級也可以叫做“備胎計划”...

 

  代碼開源

  代碼已經開源、托管到我的GitHub、碼雲:

  GitHub:https://github.com/huanzi-qch/springCloud

  碼雲:https://gitee.com/huanzi-qch/springCloud


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM