做者:莫家文,騰訊事務型開發工程師html
商業轉載請聯繫騰訊WeTest得到受權,非商業轉載請註明出處。redis
原文連接:http://wetest.qq.com/lab/view/320.htmlapi
全新的全局流控實現方案,既解決了目前流控的實現難點,同時保證運行穩定且流控準確的前提下,實現更簡單,部署成本更低,容災能力更強。 該方案組件化以後,能夠推廣到別的有須要的部門使用,只需簡單的接入和部署便可得到全局流控的能力。服務器
流控做爲容災體系中必不可少的一環,在請求量超過系統容量的時候,經過拒絕超量請求的方式,防止服務接口的雪崩和系統掛掉。架構
目前部門只具有單機流控的能力,隨着業務的增加和系統複雜度的增長,單機流控愈來愈不能知足須要,升級流控能力日趨重要。併發
升級流控以前,先簡單瞭解不一樣流控方式的優缺點:
框架
對比可知,全局流控能能彌補單機流控的缺點,而動態流控又是在全局流控的基礎上作更精細化的流控。運維
目前全局流控方案主要須要解決兩個實現問題:分佈式
一、全局計數器使用何種存儲高併發
全局計數器存儲可使用redis,也可使用ckv。
分佈式流控很關鍵一點是將流控服務作成原子化。而作流控須要記錄兩個信息,計數和計時。好比全局流控閾值設置了5w/s的值,計數器記錄了當前的請求數(計數),在達到1s時計數器需失效或清零(計時)。
計數和計時要保證原子操做,目前已知的方式有:
1)使用加鎖的方式,好比ckv的cas計數,或者redis+lua技術,但在併發量大的時候,加鎖的性能比較沒法保證;
2)使用incr的方式,因爲目前redis和ckv的incr都沒過時時間設置,要知足要求(計數和計時同時原子操做),需改造redis或ckv,使得incr支持過時時間設置,目前已知有改造redis支持過時時間的案例。
二、 如何上報請求
通常統計的方式分兩種:
1) 請求全量上報,這樣要求存儲的訪問能力足夠強大,優勢是流控實時性獲得保證;
2) 請求定時批量上報,這樣存儲的訪問壓力較小,資源消耗也更少,缺點是流控可能不實時;
通常還須要每臺機器部署agent來完成上報和流控判斷,業務模塊與agent之間要實現通信。
大致的邏輯結構以下:
注:圖片來自文章《MSDK全局流控介紹》
實現難點:
1)將流控服務作成原子化,目前不管使用redis仍是ckv,加鎖方式併發下沒法保證性能,原生的incr方式要解決過時時間的問題,須要的技術門檻和開發成本都比較高;
2)從上報統計方式看,全量上報對請求量巨大的業務部門來講不大可行,定時批量上報又沒法保證明時流控;
3)接入全局流控每臺機器都須要部署agent,agent可否正常工做影響全局流控的使用,同時部署及運維的成本不低;
面對目前困難,咱們提出這樣的疑問:
有沒有一套簡單可行的方案,能解決上述問題的同時,保證開發成本較低,部署簡單,運行穩定,並且流控準確的呢?
方案要點:
一、計數器的key能「計時「
首先選擇使用ckv做爲計數器存儲,相比redis開發會更熟悉,同時維護也更容易,固然該方案也能夠選擇redis做爲計數器存儲。
既然使用ckv的cas用做計數在高併發下有性能瓶頸,那麼只能使用incr的方式,同時要解決計時的問題。
方案沒有對incr增長過時時間的方式,而是將時間信息寫入key,把一天按時間劃分(好比1s劃分一個key)爲若干個key做爲計數器。這樣每一個key既能使用incr方式計數,同時也有」計時「的能力,當超過劃分時間(好比1s後),就順移到下一個key上作計數。
優點:方案用簡單的方式將全局流控服務作成原子化(計數和計時原子化),開發門檻低。
二、請求統計用拉取的方式替換上報
對於請求的統計方式,通常全量上報不可行,全部業務的請求量至少1:1上報到ckv,ckv的容量和是個問題,單key也容易成爲熱點。定時或者定量批量上報,都沒法保證明時流控,特別是請求量大的時候,流控延遲的問題會被放大。
方案拋開原有的上報思惟定式,引入配額拉取的概念,替換通常統計上報的方式,取而代之的是每一個key初始化時寫入流控閾值,每一個業務機器並不是上報請求量,而是訪問ckv拉取配額到本地保存,本地配額消耗完畢再次拉取,相似餘庫存扣減。
優點:方案減小ckv的訪問量,同時保證流控的準確性。
三、部署不須要agent
已有的流控方案都須要每臺業務機器部署agent,完成上報請求和流控判斷的功能。這樣作機器都要部署agent,同時agent的正常使用也要歸入維護。
爲了作更輕量的方案,咱們考慮agent的必要性,分析發現,agent要完成的功能比較簡單,主要功能託管到業務流控api。
這樣的作法會讓業務在調用流控校驗時有額外的開銷,開銷主要是拉取配額訪問ckv的時間消耗,正常是<1ms,只要每次拉取配額的值設置合理,分攤到每一個請求的耗時就少的能夠忽略。
好比拉取配額設置10,即正常10個請求要拉取一次配額,這時流控api會請求一次ckv拉取配額,這個業務請求耗時增長約1ms。
優點:方案不採用agent的方式,部署維護更簡單。
四、全局及單機流控同時啓用
考慮全局流控不可用的狀況,好比ckv掛掉,可否保證業務不受影響且流控可用?
方案對容災作了充分的考慮,主要解決方式是全局及單機流控同時啓用,即基於ckv的全局流控和基於單機共享內存的單機流控都同時工做。
全局流控失效(ckv掛掉或連續超時致使拉取配額失敗),流控api判斷出這種狀況後,暫時中止使用全局流控,而單機流控依然能夠正常工做,流控api按期去探查(好比30s)全局流控是否恢復可用,再啓動全局流控。
優點:方案有很好的容災能力,容災方式簡單有效。
五、解決ckv性能瓶頸,流控性能達百萬/s
因爲使用ckv的incr以及配額拉取的實現方式,全局流控接入服務請求的能力獲得成本增加。
目前方案單獨申請了一塊ckv,容量爲6G,使用incr的方式,壓測性能達到9w+/s。
對業務空接口(Appplatform框架)作流控壓測,使用30臺v6虛擬機,單機50進程,壓測性能達到50w+/s。
單接口50w/s的請求的服務接入,一樣也能知足多接口整體服務請求量50w+/s的全局流控需求。
上述的壓測瓶頸主要是Appplatform框架的性能緣由,因爲拉取配額值是根據流控閾值設定(通常>10),50w+的請求量只有不到5w的ckv訪問量,ckv沒到瓶頸。
優點:方案使用同等的資源(單獨一塊6G的ckv),能知足業務的請求量更高,性能達百萬/s。
六、支持擴容和動態流控升級
支持平行擴展流控能力,一套全局流控部署能知足流控的服務請求量是達百萬/s,更大的服務請求量須要部署多套全局流控。
支持升級到動態流控能力,ckv寫入的流控閾值是經過定時管理器完成,目前業務已經作了健康度上報,定時管理器只須要對接健康度數據,分析接口當前請求狀況,動態調整流控閾值便可達到動態流控能力。
優點:方案總體簡單輕量,擴容和升級都很容易。
接下來詳細介紹一下具體方案的實現。
方案涉及幾個功能簡單、清晰的角色:
一、管理定時器:
根據配置,將頻率限制任務的配額值,寫入多個帶時間信息的key。好比頻率限制任務1配了閾值爲5000/s的全局流控,那麼就以每一秒生成一個kv爲例:
key爲task1_20170617000000、task1_2017061700000一、task1_20170617000002等
value爲5000
二、共享內存:
保存每個任務流控相關的本機信息,包括流控狀態、本地配額、配額鎖等。
三、流控API:
業務經過流控api,請求先扣減本地配額(原子操做),若是配額<=0,就從ckv拉取配額到共享內存中,若是沒配額拉取,就作說明流控生效。
全局流控過程能夠抽象出三個主要狀態:
一、全局非流控狀態指的是全局流控可用的狀況下,但還沒觸發限流,業務請求能夠正常經過;
二、全局流控狀態指的是業務請求觸發限流,請求不能經過;
三、全局失效狀態指的是全局流控因爲異常不可用,好比ckv訪問超時或掛掉,業務請求能夠正常經過;
圍繞三個流控狀態的跳轉,抽象出整個全局流控的核心關鍵流程:
一、當狀態爲全局非流控,首先會先扣減本地配額,本地配額<=0時,就走拉取配額流程;
二、當狀態爲全局流控,本地配額<=0時,先判斷key是否發生變化,做用是同一個時間間隔內配額已經消耗完,減小無效的拉取;
三、當狀態爲全局失效,會判斷時間是否已經超過一個設定值,在失效時間內不會嘗試拉取配額,做用是減小無效的拉取;
四、 拉取配額先獲取原子鎖,做用是當業務進程併發拉取時,只有獲取鎖成功的進程,才能拉取賠額額;
整個流程考慮了全部會發生的狀況,圍繞三個狀態的跳轉,正常及異常流程處理都很好的統一到一個流程裏。
好比發送ckv不可用的故障,經過拉取配額失敗的動做,很好的從正常的全局非流控狀態切換到全局失效狀態,又經過定時拉配額,去探查故障是否消除,若是消除就回復到全局非流控的正常狀態。
因爲以時間間隔作key,劃分不一樣的時間片並寫入流控配額,當機器拉取配額面臨個機器時間是否一致的問題。
據瞭解,時間同步是經過ntp服務來完成,精度在1~50ms之間,通常狀況是<10ms。
目前的時間間隔都是1s以上,ntp服務的精度已經知足。
換句話說只要保證ntp服務正常運行,全局流控的單個時間片的計數是準確的。
若是ntp服務沒正運行,致使機器時間不一致,會致使同一時刻應該訪問同一key的機器,訪問了多個key,則會形成計數不許確。
因爲ntp服務目前處理方式是經過監控流控任務一段時間內的key的變化狀況,及時發現機器時間不一致的狀況。具體作法是若是發現某一時刻超過兩個kv的配額值發生變化,能夠確認機器同一時刻訪問key的分佈超過合理的範圍,有時間有不一致的狀況。
爲了保證併發狀況下配計數的準確性,會使用原子操做的方式處理計數,無需加鎖。
一、全局配額是用ckv的incr方式,保證配額拉取扣減的準確;
二、本地配額累加或扣減,對共享內存使用gcc提供的__sync_add_and_fetch的原子操做方式;
拉取配額使用了加鎖,鎖的方式是對對共享內存使用gcc提供__sync_bool_compare_and_swap的原子操做方式。
極端狀況下,獲取鎖的進程core掉,就會致使鎖沒法釋放,其餘進程須要拉取配額時也獲取不了鎖。死鎖不會影響業務請求正常經過,但因爲沒法拉取配額,會致使全局流控沒法使用。
處理這種狀況目前的方式是,判斷加鎖的時長是否超過合理值 ,具體作法是加鎖記錄當前時間,正常釋放清空這個時間值,獲取不了鎖的進程判斷加鎖的時長,大於設定值(1min),說明有死鎖狀況,主動釋放鎖。
配額拉取的值的設置起到一個很關鍵的一步,影響流控的準確性,拉取的效率以及ckv訪問壓力。
拉取配額值合理,既減小ckv訪問壓力,減輕業務Api額外的拉取耗時(通常<1ms),同時也能保證流控準確。
拉取配額值不合理,設置過大會形成機器剩餘的配額浪費,須要配額的機器可能沒配額,致使產生錯誤流控。設置太小會致使本地配額消耗完(本地配額值<0),配額拉取滯後,形成流控生效延後,拉取次數過多,ckv訪問壓力大,業務api拉取效率低。
配額值的設置是:單機閾值與拉取值的比值爲50。好比全局流控閾值 10000/s,機器數20,平均單機流控閾500/s,配額值設定爲10。
目前是經過壓測觀察的經驗值得來,拉取值設置是否合理,還有待後續觀察和分析。
部署:
一、管理定時器的部署,只需單獨部署到腳本機上;
二、業務模塊添加流控api,已經接入原來單機流控的業務,無需改動業務邏輯代碼,只須要替換舊的靜態庫和依賴的的頭文件便可(待給出詳細接入);
擴展:
方案支持平行擴展,一套全局流控部署能知足流控的服務請求量是50w+/s,更大的服務請求量須要部署多套全局流控。平行擴展一套主要的變動包括:申請新的ckv,使用新的一塊共享內存以及新的流控任務配置。
一、對流控任務作了可視化監控
主要監控及跟蹤各流控任務的基本使用可以信息,以及當前和歷史流量狀況
二、機器時間不一致的監控及上報
主要監控流控任務一段時間內的key的變化狀況,及時發現機器是否時間不一致
一、管理定時器接入zk主從切換組件,在單點掛掉的狀況下能夠切到另一臺機器上,保證timer的可用性。
二、當ckv鏈接超時或沒法訪問時,對應的流控狀態會變成全局失效,過一段時間會自動從新拉起。因此出現ckv不可用的狀況,只須要恢復ckv,接入全局流控的服務會自動恢復可用狀態。
目前流控監控只是對流控任務使用狀況作了簡單的展現,流控的歷史狀況等其餘必要的信息還沒能查詢及展現。
還有待補充的監控有機器時間不一致監控,監控發現的問題須要告警,以便於人工及時介入。
有待規劃的監控和告警後續再補充。
流控升級下一步是從全局流控升級到動態流控,所需健康度數據已經上報,而接入的方式目前能夠直接在管理定時器上面增長配額調整的能力,這個擴展很方便。重點應該是怎麼去根據上報的健康數據分析並實現動調整當前配額值。
配額調整大體的思路以下:
注:圖片來自於理財通的《接入層限流介紹》
WeTest壓測大師運用了沉澱十多年的內部實踐經驗總結,經過基於真實業務場景和用戶行爲進行壓力測試,幫助遊戲開發者發現服務器端的性能瓶頸,進行鍼對性的性能調優,下降服務器採購和維護成本,提升用戶留存和轉化率。
功能目前免費對外開放中,點擊連接:http://wetest.qq.com/gaps 便可體驗!
若是對使用當中有任何疑問,歡迎聯繫騰訊WeTest企業qq:800024531