1. 程式人生 > >分布式環境 限流解決方案

分布式環境 限流解決方案

OS 訪問 right -c 拒絕 key ews 可行性分析 聲明

  • 業務背景介紹
    對於web應用的限流,光看標題,似乎過於抽象,難以理解,那我們還是以具體的某一個應用場景來引入這個話題吧。
    在日常生活中,我們肯定收到過不少不少這樣的短信,“雙11約嗎?,千款….”,“您有幸獲得唱讀卡,趕快戳鏈接…”。這種類型的短信是屬於推廣性質的短信。為什麽我要說這個呢?聽我慢慢道來。
    一般而言,對於推廣營銷類短信,它們針對某一群體(譬如註冊會員)進行定點推送,有時這個群體的成員量比較大,譬如京東的會員,可以達到千萬級別。因此相應的,發送推廣短信的量也會增大。然而,要完成這些短信發送,我們是需要調用服務商的接口來完成的。倘若一次發送的量在200萬條,而我們的服務商接口每秒能處理的短信發送量有限,只能達到200條每秒。那麽這個時候就會產生問題了,我們如何能控制好程序發送短信時的速度昵?於是限流這個功能就得加上了
  • 生產環境背景
    1、服務商接口所能提供的服務上限是400條/s
    2、業務方調用短信發送接口的速度未知,QPS可能達到800/s,1200/s,或者更高
    3、當服務商接口訪問頻率超過400/s時,超過的量將拒絕服務,多出的信息將會丟失
    4、線上為多節點布置,但調用的是同一個服務商接口
  • 需求分析
    1、鑒於業務方對短信發送接口的調用頻率未知,而服務商的接口服務有上限,為保證服務的可用性,業務層需要對接口調用方的流量進行限制—–接口限流
  • 需求設計
    方案一、在提供給業務方的Controller層進行控制。
    1、使用guava提供工具庫裏的RateLimiter類(內部采用令牌捅算法實現)進行限流
<!--核心代碼片段-->
private RateLimiter rateLimiter = RateLimiter.create(400);//400表示每秒允許處理的量是400
 if(rateLimiter.tryAcquire()) {
   //短信發送邏輯可以在此處

 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、使用Java自帶delayqueue的延遲隊列實現(編碼過程相對麻煩,此處省略代碼)

3、使用redis實現,存儲兩個key,一個用於計時,一個用於計數。請求每調用一次,計數器增加1,若在計時器時間內計數器未超過閾值,則可以處理任務

 if(!cacheDao.hasKey(API_WEB_TIME_KEY)) {            cacheDao.putToValue(API_WEB_TIME_KEY,0,(long)1, TimeUnit.SECONDS);
}       if(cacheDao.hasKey(API_WEB_TIME_KEY)&&cacheDao.incrBy(API_WEB_COUNTER_KEY,(long)1) > (long)400) {
    LOGGER.info("調用頻率過快");
}
//短信發送邏輯
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

方案二、在短信發送至服務商時做限流處理
方案三、同時使用方案一和方案二

  • 可行性分析
    最快捷且有效的方式是使用RateLimiter實現,但是這很容易踩到一個坑,單節點模式下,使用RateLimiter進行限流一點問題都沒有。但是…線上是分布式系統,布署了多個節點,而且多個節點最終調用的是同一個短信服務商接口。雖然我們對單個節點能做到將QPS限制在400/s,但是多節點條件下,如果每個節點均是400/s,那麽到服務商那邊的總請求就是節點數x400/s,於是限流效果失效。使用該方案對單節點的閾值控制是難以適應分布式環境的,至少目前我還沒想到更為合適的方式。
    對於第二種,使用delayqueue方式。其實主要存在兩個問題,1:短信系統本身就用了一層消息隊列,有用kafka,或者rabitmq,如果再加一層延遲隊列,從設計上來說是不太合適的。2:實現delayqueue的過程相對較麻煩,耗時可能比較長,而且達不到精準限流的效果
    對於第三種,使用redis進行限流,其很好地解決了分布式環境下多實例所導致的並發問題。因為使用redis設置的計時器和計數器均是全局唯一的,不管多少個節點,它們使用的都是同樣的計時器和計數器,因此可以做到非常精準的流控。同時,這種方案編碼並不復雜,可能需要的代碼不超過10行。

  • 實施方案
    根據可行性分析可知,整個系統采取redis限流處理是成本最低且最高效的。
    具體實現

    1、在Controller層設置兩個全局key,一個用於計數,另一個用於計時

private static final String API_WEB_TIME_KEY = "time_key";

    private static final String API_WEB_COUNTER_KEY = "counter_key";
  • 1
  • 2
  • 3

2、對時間key的存在與否進行判斷,並對計數器是否超過閾值進行判斷

if(!cacheDao.hasKey(API_WEB_TIME_KEY)) {

            cacheDao.putToValue(API_WEB_TIME_KEY,0,(long)1, TimeUnit.SECONDS);
            cacheDao.putToValue(API_WEB_COUNTER_KEY,0,(long)2, TimeUnit.SECONDS);//時間到就重新初始化為

        }

        if(cacheDao.hasKey(API_WEB_TIME_KEY)&&cacheDao.incrBy(API_WEB_COUNTER_KEY,(long)1) > (long)400) {


            LOGGER.info("調用頻率過快");

        }
         //短信發送邏輯
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

實施結果
可以達到非常精準的流控,截圖會在後續的過程中貼出來。歡迎有疑問的小夥伴們在評論區提出問題,我看到後盡量抽時間回答的

版權聲明:本文為博主原創文章,未經博主允許不得轉載。 http://blog.csdn.net/Justnow_/article/details/53055299

分布式環境 限流解決方案