高併發系統設計之開放平臺API接口調用頻率控制系統

先描述下基本場景: 算法

系統API接口日均調用次數預計1億次,提供5臺服務器。 數據庫

須要作兩種層面的控制: 緩存

> 單IP、單應用每小時調用次數不超過10000次 服務器

> 單應用、單用戶、單接口每小時調用次數不超過1000次 數據結構

要求每次對頻控系統的調用的響應時間在20ms內。 app

此外,應用開發者和開放平臺所屬公司關心調用次數統計數據,如當天某應用全部接口被調用總次數、當天某應用某接口被調用次數、當天某應用用戶使用數等。性能

    根據上面,咱們能夠直接獲得系統響應度要求和計算獲得系統吞吐量要求,計算公式以下: 測試

優化

ui

頻控系統吞吐量(系統每秒可以處理的請求數)  

   = 80% * 1億 / (24小時 * 60分鐘 * 60秒 * 40% * 5) = 4630tps 

    80%、40%是指一天中有80%的請求發生在40%的時間內,是粗略的估算值。5是服務器數量。因此獲得吞吐量要求爲4630tps。前期設計系統時必須參考這些性能指標,後期壓測系統時必須根據這些指標設計測試計劃。 

    總結下系統設計須要達成的目標: 

  • 請求的響應足夠快 

  • 能支撐4630tps 

  • 佔用的CPU、內存等硬件資源不能太誇張(隱性設計目標)


A、數據結構設計

    計數是典型的key-value數據結構。 

    可能想到的最簡單最天然的方式是下面這樣的: 

K(app_id, ip) => V(count, startTime, lastTime) 

K(app_id, uid, interface_id) => V(count, startTime, lastTime) 

    startTime記錄的是第一次調用的發生時刻,lastTime記錄的是最近一次調用的發生時刻,它們用來判斷是否應該重置計數值count和是否該拒絕調用。 

    爲了節省內存,有必要對key和value作特殊設計,最次的方案固然是直接拼接上面各個字段。但這是很是不合理的,咱們來簡單估算下: 

假設應用有10,000個,平均每一個應用的用戶數爲100,000,接口數爲50,獨立訪問IP地址爲1,000,000,那麼數據項總共爲: 

10,000 * 1,000,000 + 10,000 * 100,000 * 50 = 600億 

那麼若是每一個數據項節省1個字節,可以節省的總數據存儲是600G,這是很是可觀的。 

    對於Key,一種更優方案是先拼接Key的字符串,而後MD5獲得32位定長字符串做爲Key,Key定長的話或許對性能提高也會有必定幫助。 

    對於Value,count、startTime、lastTime信息不能丟失,那麼或許能夠考慮下面兩種優化方案: 

  • 無損壓縮Value字符串,好比使用Snappy字符串壓縮算法,但壓縮和解壓縮會帶來額外的CPU計算消耗,須要權衡 

  • 計數不須要太精確,因此能夠犧牲必定精確度換取空間節省。或許咱們能夠利用 CountingBloomFilter?Key須要從新設計爲:MD5(app_id, interface_id, 如今距離1970年1月1號的小時數),Value就是CountingBloomFilter數據結構了,每一個調用先根據app_id、 interface_id、如今距離1970年1月1號的小時數計算32位MD5值,而後獲得所屬的CountingBloomFilter(若是沒有就 建立),而後每次先檢查是否已達到最大插入次數,若是是則直接返回,若是不是才插入。但 是咱們別忘了一點:CountingBloomFilter支持最大重複插入次數爲15,遠小於這裏的1000次和10000次。因此很殘酷,CountingBloomFilter不適合這種方案。但這是一個很好的起點,Value的數據結構雖然不能用 CountingBloomFilter,但或許能夠用其餘的優化數據結構,請看:http://blog.csdn.net/hguisu/article/details/7856239。

    另外頻率控制通常能夠採用「令牌桶算法」,這裏再也不深刻,能夠參考: 

    http://en.wikipedia.org/wiki/Token_bucket

B、數據存儲設計

    考慮到性能要求,確定須要用到Cache,這裏打算選用Redis。再根據上面的估算,數據項總共有600億,因此不可能把全部數據項所有放到Redis Cache中(假設每一個Cache項佔100個字節,估算下須要多少內存。 

因此我這裏採用冷熱數據分離方案。有這麼三類數據: 

  • 冷數據存放在MySQL數據庫,按照app_id、uid進行水平Shard 

  • 不冷不熱數據採用Hash結構壓縮存儲在Redis,具體結構下面會提到 

  • 熱數據放在另外的Redis庫中,而且「展開式」存儲以改善訪問性能 

   

    熱數據的所謂「展開式」結構是指將上面兩個維度的計數分開,即存成相似下面這兩種結構: 

K取MD5(app_id, ip, 如今距離1970年1月1號的小時數),V取一個長整型值表示計數 

K取MD5(app_id, interface_id, uid, 如今距離1970年1月1號的小時數),V取一個長整型值表示計數 

    Redis Cache失效時間:

    全部Redis Cache數據的失效時間設置爲1小時到1小時1分鐘之間的某個隨機值,這樣能某種程度上避免緩存集體失效引發的「雪崩」。 

    冷熱數據遷移過程:

    數據的冷熱一直在發生着改變,因此冷熱數據之間須要進行遷移。 

    第一種方案是由後臺進程按期將

  • 熱數據中符合冷數據標準的數據移動到不冷不熱數據緩存

  • 將不冷不熱數據中符合熱數據標準的數據遷移到熱數據緩存

  • 將不冷不熱數據中符合冷數據標準的數據遷移到MySQL數據庫

  • 將MySQL數據庫中符合不冷不熱數據標準的數據遷移到不冷不熱數據緩存

    判斷冷熱的標準是基於天天計算一次的歷史平均每小時調用次數。 

    第二種方案是在調用時主動進行遷移,基於最近50次調用的平均時間間隔來判斷(也就是對於每個數據項還要存儲一個它最近50次調用的平均時間間隔),遷移過程同第一種。

相關文章
相關標籤/搜索