天天看點

架構:服務限流。什麼叫限流?限流算法并發限流接口限流分布式系統限流

什麼叫限流?

即限制流量進入

  • 緩存,是用來增加系統吞吐量,提升通路速度提供高并發。
  • 降級,是在系統某些服務元件不可用的時候、流量暴增、資源耗盡等情況下,暫時屏蔽掉出問題的服務,繼續提供降級服務,給使用者盡可能的友好提示,傳回兜底資料,不會影響整體業務流程,待問題解決再重新上線服務
  • 限流,是指在使用緩存和降級無效的場景。比如當達到門檻值後限制接口調用頻率,通路次數,庫存個數等,在出現服務不可用之前,提前把服務降級。隻服務好一部分使用者。

在我們使用微信、支付寶、短信等等這些api的時候,每個接口都會有調用上的限流。

限流是對某一時間視窗内的請求數進行限制,保持系統的可用性、穩定性和安全性,防止因流量暴增而導緻的系統運作緩慢或當機。

限流算法

計數器算法

  • 簡單粗暴
  • 比如線程池大小,資料庫連接配接池大小、nginx連接配接數等都屬于計數器算法。
  • 全局或某段時間範圍達到門檻值則限流。

漏桶算法

架構:服務限流。什麼叫限流?限流算法并發限流接口限流分布式系統限流
  • 削峰
  • 緩沖
  • 消費速度固定 因為計算性能固定
  • 保證桶不能忙

令牌桶算法

架構:服務限流。什麼叫限流?限流算法并發限流接口限流分布式系統限流
  • 平滑的流入速率限制,消費/秒。
  • 可以用于對外服務接口,内部叢集調用

差別

  • 令牌桶是按照固定速率從桶裡拿令牌消費,如果令牌為0,則拒絕新請求
  • 漏桶是按照固定速率流出請求,流入速率不控制,當桶内請求達到門檻值,新請求則被拒絕。
  • 令牌桶支援每次拿多個令牌,平均流入速率,并支援突發流入,還可以支援緩慢提升流入速度

并發限流

設定系統門檻值總的qps個數

Tomcat中配置的

  • acceptCount 響應連接配接數
  • maxConnections 瞬時最大連接配接數
  • maxThreads 最大線程數

接口限流

接口總數

可以使用atomic類或者semaphore進行限流

這種方式簡單粗暴。沒有平滑處理。使用限制某個接口的總并發數,或限制某賬号服務調用總次數。

比如某些開放平台限制試用賬号。

if (atomic.incrementAndGet() > 100){
    // 拒絕
}finally{
	atomic.decrementAndGet();

}
           

接口時間視窗

此時可以使用

Guava Cache

,類似于一個

ConcurrentMap

,但并不完全一樣。

最基礎的不同是

ConcurrentMap

儲存所有的元素知道它們被明确删除,

Guava Cache

可以配置自動過期

//計數器
counter;
// 限制數量
limit;
// 限制機關 1000=秒
unit;
// 獲得目前時間
current = system.currentTimeMillis() / unit
//判斷時間窗内是否限制通路

if (counter.get(current).incrementAndGet() > limit){
    // 拒絕
}
           

使用guava實作令牌桶

穩定模式(SmoothBursty:令牌生成速度恒定)

RateLimiter.create(2) //容量和突發量,令牌桶算法允許将一段時間内沒有消費的令牌暫存到令牌桶中,用來突發消費。
           

漸進模式(SmoothWarmingUp:令牌生成速度緩慢提升直到維持在一個穩定值)

// 平滑限流,從冷啟動速率(滿的)到平均消費速率的時間間隔
RateLimiter limiter = RateLimiter.create(2,1000l,TimeUnit.MILLISECONDS);
           

分布式系統限流

Nginx + Lua

可以使用resty.lock保持原子特性,請求之間不會産生鎖的重入

https://github.com/openresty/lua-resty-lock

使用lua_shared_dict存儲資料

local locks = require "resty.lock"

local function acquire()
    local lock =locks:new("locks")
    local elapsed, err =lock:lock("limit_key") --互斥鎖 保證原子特性
    local limit_counter =ngx.shared.limit_counter --計數器

    local key = "ip:" ..os.time()
    local limit = 5 --限流大小
    local current =limit_counter:get(key)

    if current ~= nil and current + 1> limit then --如果超出限流大小
       lock:unlock()
       return 0
    end
    if current == nil then
       limit_counter:set(key, 1, 1) --第一次需要設定過期時間,設定key的值為1,
過期時間為1秒
    else
        limit_counter:incr(key, 1) --第二次開始加1即可
    end
    lock:unlock()
    return 1
end
ngx.print(acquire())
           

繼續閱讀