此次救火討論的是流控,流控能夠很簡單,也能夠很是複雜,特別是動態流控。咱們有一個產品在T國某個運營商遇到了麻煩,這個運營商的母公司是歐洲的運營商,而歐洲的運營商對於產品驗收的苛刻是出了名的,而此次給咱們帶來麻煩的就是流控。算法
這個產品的大體功能就是把訂閱的短消息或者彩信內容發送到戶手機上,就相似於在幾年前火了一段時間的手機報。這個產品的流控有些複雜:數據庫
一、首先這套系統部署是集羣,假設集羣有6臺主機,這6臺主機的每秒下發的消息量不能超過一個值,假設爲10000,爲何有這個要求,是由於下游執行發送消息的短消息網關或彩信網關有流控,你發的快了,下游系統會拒絕你,因此這個總的TPS不能超。性能
二、這些消息的來源都是來自於某個SP或者CP,每一個CP或者SP在簽約平臺的時候,它有一個最大每秒發送量,發送量分爲彩信、短信以及WAP Push渠道。測試
三、除了上面的限制之外,CP和SP又分了優先級,若是低先級和高優先級的時候在一塊兒發送的,必定要先等到高優先級的先發送完成。優化
還有一些規則要求,時間比較久了,我記不是特別清楚了。ui
這套系統在國內和國外很多地方都上線了,不能說質量多好,也沒有太大的問題,我接手這個產品大概幾個月的時候在這個局點遇到了問題,客戶投訴他們經過線上實際的監控發現2個嚴重的問題,若是不解決,他們會******:spa
1,這套系統在高峯期間實際下發的每秒流量沒有達到當時他們購買的流量,好比他們購買爲10000條/秒,可是他們實際監測發現高峯的時候,可能只有9000條/秒.線程
2,系統的流控不穩定,當時合同簽署的流控上下波動不能大於正負10%。舉個例子,假設他們有一個最大的SP,在上午10點到11點獨佔發送消息,他們設定按200條/秒發送,這時沒有任何其它的SP搶佔通道,那麼消息發送速率應該是在180~220條/秒,可是實際發送的速率會在這個區間以外。設計
當時我接手這個產品幾個月時間,對系統的大體實現有了解,可是細節並非清楚,負責這個產品的小組長找到我求助,不懂也要硬着頭皮上。一開始覺得是個小問題,這個產品的開發骨幹試了幾種方案過了一週,發現都很難達到客戶的要求,這時客戶和項目經理失去了耐心,開始把問題往高層領導彙報,一旦高層領導知道,後果可想而知。我當時帶着這個產品的小組長以及開發骨幹開始作這個問題的攻關,先後總共投入了3周左右的時間(有一半的時間基本上是都是在通宵)。內存
通過幾天走讀代碼以及實際測試驗證,我發現原來的流控實現方案存在嚴重的缺陷,原來的方案是存儲過程實現上面的流控的流量的分配動做,這個存儲過程每1秒運行一次,每次運行的時候它會把當前須要下發的任務根據總流控、CP以及SP等計算一遍,而後把須要下發的數據加載到Oracle的分區表中,把要發送的速率插入到任務表中,而後集羣是每一個任務下發的線程從任務表中讀到任務,而後再從分區表中加載上任務要發送的數據,再周而復始的存儲過程不斷計算分配任務、集羣每一個節點加載任務數據發送數據。
這個產品的最初的開發在2004~2005年左右,最初的設計人員找不到了,可是我猜測爲何用數據庫來解決,是由於如何控制每秒發送的消息在集羣下並很差實現,而數據庫存放集中式數據是最簡單的實現方式。這種方案對於大部分運營商對流控的準確性要求沒有那麼高時,其實並非太大的問題,只要系統的負載不是太重,消息能基本準確的發送就能夠了。
回到客戶發現的2個問題,能夠大體感性的分析緣由:
一、由於任務的分配是每秒從新計算一次,計算完成之後,下發線程要再從分區表中加載數據,這都須要時間,即數據不是當即下發,會致使以前分配的速率執行時間被拉長了,因此很難達到總容量
二、流控的不許確性:單個線程發送的流控算法優化問題,我會在後面再討論這個問題
針對第一個問題,咱們最後設計了一個流控中心的應用,拋棄了原來經過數據庫進行任務的分配的邏輯,其它全部發送消息的應用經過Netty鏈接到流控中心,當前任務以什麼速率發送徹底以流控中心的指標爲準,而流控中心它的計算邏輯全在內存中實現。流控中心和消息發送應用之間雙向通訊,當下發的速率要調整的時候,流控中心能夠把速率主動的下發給消息中心。當時由於時間緊,咱們並無用專門的機器來部署流控中心,而是把全部的發送消息的主機在啓動的時候把本身的IP地址寫到數據庫的一張表中,而後在這張表中最早插入數據的那臺機器就兼容來流控,順便發送消息。由於流控並非很耗性能,因此即便發送消息對性能影響不大。若是負責流控的機器掛了的話,再由心跳機制把那臺掛了的機器刪除掉,這時其它發送消息的節點再從新鏈接到新的負責流控的主機上。
看到這,你們可能以爲這招很土,其實當時我想過用JGroup來作集羣的管理,可是若是你在生產環境用過JGroup的話,就會發現這玩意太複雜了,我當時在不少項目都被坑過。那時也沒有見過其它什麼更好的集羣通訊的開源組件了,加上項目時間緊,咱們按更加穩妥的方案實現。
而針對第二個問題,難度相對要小一些。原來的方案採用了一個定時器,這個定時器每秒運行一次,每次運行的時候把一個Semaphore置成須要發送的TPS,每一個發送線程在發送以前accquire,若是能取到就發送,取不到就阻塞,而後再下一個1秒的時候再把這個信號量Release到發送的TPS。這麼實現會致使發送的在1秒內不平均,好比說:我要一秒發送30條消息,有可能在1/3秒的時候就把消息都發完了,而後在剩下的2/3秒什麼也沒發,這樣若是在下游採樣統計TPS的週期不是按1秒來計算,而是按1/6計算的時候,明顯發送速率就不穩定。
改進之後的流控算法是參考了一個兄弟產品的方案,稱之爲滑動窗口流控算法,由於畫圖比較花時間,我簡單描述一下。這個算法將1秒分紅10個小窗口,仍是以上面每秒發送30條爲例,在第一個小窗口時,我須要發送3條消息,到第2個小窗口時我只須要累計發送2*3=6條消息,到第3個小窗口時,我只須要累計發送3*3=9條消息,這個算法的好處是發送的消息速率更加平滑,將下游發送的速率的波動給抹平掉。這個算法還有一個好處,不像上面一個算法要有定時器不斷的清零,而只須要很簡單的獲取當前系統時間-系統啓動時間,就能夠算出來當前處在哪一個發送窗口,系統的開銷不只小並且更準確。
通過上面的2個優化之後,達到了預期的目標,後面這個產品的基線版本的流控算法又從新進行了設計,後面的隨筆再說後來怎麼優化的。1年之後的基線版本優化之後,這個產品的小組長離開了公司,這個產品的開發骨幹去了海外常駐,也沒有了聯繫。可是寫到這,就想到3我的像個落魄鬼同樣在公司不分晝夜的討論方案、改代碼、測試…