日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[重磅] 如何更好地实现服务调用和消息推送

發(fā)布時間:2024/9/27 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [重磅] 如何更好地实现服务调用和消息推送 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

第四屆阿里中間件性能挑戰(zhàn)賽是由阿里巴巴集團發(fā)起,阿里巴巴中間(Aliware)、阿里云天池聯(lián)合舉辦,是集團少有的工程性品牌賽事。大賽的初衷是為熱愛技術的年輕人提供一個挑戰(zhàn)世界級技術問題的舞臺,希望選手在追求性能極致的同時,能深刻體會技術人的匠心精神,用技術為全社會創(chuàng)造更大的價值。


本文是亞軍選手“做作業(yè)”的解題思路,來自南京理工大學的95后開發(fā)者

?

初賽部分


一、賽題重述及理解


初賽主要的考察點是實現(xiàn)一個高性能的http服務器,一套優(yōu)秀的負載均衡算法。而協(xié)議轉換僅需要對協(xié)議進行詳細了解即可實現(xiàn),屬于基本技能考察。而服務的注冊和發(fā)現(xiàn)參考demo中的實現(xiàn)即可,屬于比較常用的處理邏輯。


針對高性能的http服務器,想要在競賽環(huán)境下取得較好的成績,使用常用通用http框架是較難取得高分的,因為consumer agent和consumer共享一個docker運行環(huán)境,在512連接測試環(huán)境下,系統(tǒng)資源就已經(jīng)很緊張了,需要在舍棄一部分http特性的情況下才能將consumer性能發(fā)揮到極致。所以自己造輪子是一個比較合適的方案。Consumeragent的總體結構(所有邏輯基本集中在這)如圖1所示。


圖1 Consumer Agent結構


二、網(wǎng)絡編程模型的選擇


高并發(fā)網(wǎng)絡模型首選epoll,在連接數(shù)增多的情況下,其性能不會像select和poll一樣明顯下降,是由于內核對其進行了特殊優(yōu)化,不是線性遍歷所有連接。實際測試中發(fā)現(xiàn),因為請求是在收到響應后才會繼續(xù)發(fā)出,所以不僅要求高并發(fā),同時要求低延遲,因為延遲疊加的原因也會導致QPS下降。


而我們使用的常見的異步網(wǎng)絡庫libuv、libevent等都是one loop per thread的,即一個線程一個事件循環(huán),這種設計具有較高的吞吐量,但請求只能綁定到一個事件循環(huán)上,一旦請求不均勻,則會導致一核有難眾核圍觀的情況,對于動態(tài)均衡線程負載很不適合。


基于此原因我決定自己造輪子,讓多個線程處理一個epoll循環(huán),讓操作系統(tǒng)的線程調度均衡請求,同時所有連接均綁定到一個事件循環(huán)上,也便于處理。最終我選擇使用多線程的et模式的epoll模型,整體框架代碼如圖2所示。


圖2 多線程epoll框架


整體框架非常簡潔,所有網(wǎng)絡處理類僅需繼承CepollCallback完成網(wǎng)絡數(shù)據(jù)的讀取和寫入即可。


圖3 epoll回調抽象類


三、程序整體流程


Consumer端agent負責解析http請求轉換為dubbo請求,以udp形式發(fā)給provider agent處理,provider agent收到udp后直接傳給provider dubbo服務處理,將結果以udp形式回傳,consumer agent處理后返回http形式結果。



圖4、5 CA/PA處理流程


程序初始化的時候provider agent調用etcd接口將自己的服務注冊,consumer agent調用etcd接口獲取到注冊的provider agent地址,由于采用udp協(xié)議通信,故不用提前建立連接。Provider agent端也僅在第一個請求到達時建立到provider的tcp連接,保證provider已經(jīng)啟動完畢可以接受請求。


四、中間協(xié)議設計


考慮到高吞吐量,低延遲,采用了udp協(xié)議,請求為單包單請求,數(shù)據(jù)格式為dubbo請求格式,相應包為單包多相應,數(shù)據(jù)格式為dubbo響應。即consumer agent端實現(xiàn)協(xié)議轉換,provider agent端實現(xiàn)udp轉tcp。


由于請求僅存在于一個包中,所以無需考慮亂序情況,直接將收到的數(shù)據(jù)包進行重組發(fā)到dubbo的tcp連接上,提供最大的吞吐量和最低的延遲。具體處理操作如圖3。


圖3 udp重組轉發(fā)tcp


同時為了減少系統(tǒng)調用,可以一直取udp包,當緩沖滿后一次性寫入tcp的緩沖中。同理在tcp轉udp時,可以將數(shù)據(jù)湊成接近MTU,減少網(wǎng)絡壓力。如圖4所示。


圖4 udp單包多相應


還有很多工程上的技巧,比如調整緩沖區(qū)大小等,保證每次異步調用都能成功,減少系統(tǒng)調用次數(shù)。


五、負載均衡算法


負載均衡采用計數(shù)方法實現(xiàn),每次請求發(fā)送到和配置負載比例差距最大的Provider上,如果3臺都超出200負載,則進行請求排隊,當有請求返回時,優(yōu)先調度排隊請求。整體如圖5所示。


圖5 負載均衡算法


六、創(chuàng)新點及工程價值


  • 1、consumer agent實現(xiàn)負載均衡和請求控制,能夠有效在consumer使用排隊機制端遏制過載,不把過載傳遞到網(wǎng)絡傳輸上。

  • 2、兩個agent間使用udp傳輸,利用udp的低損耗,因為是單包單請求,即使亂序也能隨意合并重組,丟包不會造成協(xié)議出錯,僅造成超時出錯,非常適合這種實時性和吞吐量要求高的情況。

  • 3、在請求id中存儲了調用者和時間戳,能正確相應合適的http調用者的同時,能夠統(tǒng)計性能數(shù)據(jù),作為調參依據(jù)。

  • 4、多線程等待epoll的et模式,能夠充分挖掘網(wǎng)卡通信潛力,不存在單線程拷貝速度比網(wǎng)絡傳輸慢的情況,同時省去了任務隊列,每個處理線程具備一定的CPU計算處理能力,也能最小化響應時間,使用自旋鎖后保證一個連接的消息不會被多個線程處理,健壯性有保障。

  • 5、設置合適的tcp緩沖區(qū)大小,在不阻塞情況下保證每次write調用都會成功。


復賽部分


一、? 賽題重述及理解


復賽的主要考察點是單機實現(xiàn)100W規(guī)模的隊列信息存儲與讀取,具體考察如何在單進程空間中的實現(xiàn)1M個隊列的信息存儲,如何設計合理的數(shù)據(jù)存儲及索引結構,在特定硬盤的情況下完成高性能的順序寫、隨機讀和順序讀功能。同時由于測評環(huán)境是4C8G的,如何合理分配利用系統(tǒng)資源也是很重要的(任何對隊列存儲的東西都會放大100W倍,同時維護這么多數(shù)據(jù)需要占用更多的系統(tǒng)資源)。


針對上述考察點,本隊提出了一套完整的隊列消息數(shù)據(jù)存儲方案,稀疏索引存儲方案。程序開發(fā)迭代中的總共提出了3套寫入方案,2套讀方案,分別在Java和C++平臺上進行了實現(xiàn)。


二、? 整體系統(tǒng)架構


系統(tǒng)的整體結構較為簡單和清晰,主要分為以下幾個部分:每個隊列對應一個對象,用于維護buffer等必要結構;一個容器負責將隊列名映射到隊列對象;文件管理對象負責維護讀寫文件;負責整體讀寫狀態(tài)切換和管理的控制模塊。如圖6所示。


圖6 復賽系統(tǒng)整體架構


三、? 消息存儲方案


考慮到順序寫、隨機讀、順序讀的使用場景,參考文件系統(tǒng)以簇為單位的文件存儲方式,設計了以塊為單位的隊列消息存儲方式。


圖7 隊列消息存儲方式


考慮到不定長消息及超長消息的情況,設計了變長整形+消息體的結構記錄單條消息,同時支持跨塊的消息。


圖8 消息數(shù)據(jù)存儲方式


每個塊開頭都是變長整形,每個VarInt存儲的都是目前還剩的消息體長度(便于快速跳過或判斷消息是否繼續(xù)跨塊),這種結構支持任意長度的消息,如果塊剩余空間不足寫VarInt,則padding。


考慮到節(jié)省磁盤空間,在CPU充足的情況下,可以采用消息數(shù)據(jù)壓縮,但是線上CPU緊張,考慮到通用性并未使用特化壓縮算法。實際測試中由于消息生成性能及CPU性能的問題,未開啟壓縮功能,但在程序中預留了壓縮接口,以適應各種場合。圖9所示為預留的壓縮接口。


圖9 預留的壓縮接口


四、? 隊列寫入方案


設計好了消息存儲方案,剩下的就是合適的消息讀寫方案了,由于讀取方案比較固定,而寫入由于系統(tǒng)資源的匱乏(4C8G),需要合理設計。


1、mmap方案(Java實現(xiàn))(淘汰)


本方案使用引用計數(shù)機制控制mmap生命周期,同時mmap的塊存儲在hash cache(定長slot基于hash沖突淘汰的cache算法)中,cache也會增加引用計數(shù),這樣在連續(xù)寫文件時保證了較好cache命中率。實現(xiàn)結構如圖10所示。


圖10 mmap方案


  • 優(yōu)點:完全不用flush操作,因為讀寫都是引用mmap實現(xiàn)的。

  • 缺點:由于page cache臟超過20%會block寫入線程,為了不阻塞線程,隊列塊的大小必須非常小(512bytes,1M隊列512MB),性能較差。


2、整體內存引用方案(Java實現(xiàn))(淘汰)


考慮到mmap直接作用在page cache上,受臟頁比例影響,難以提升緩沖大小,故考慮使用整體內存方案,機制和mmap類似。同樣使用引用計數(shù)機制控制寫入緩沖區(qū)內存塊的生命周期,額外使用獨立線程負責將緩沖區(qū)刷盤。該方法能夠等待新緩沖區(qū)內存塊有效保障不寫入過載,同時使用環(huán)形緩沖實現(xiàn)內存塊及等待對象復用。使用2K大小block的java程序線上成績?yōu)?67w。方案整體流程如圖11所示。


圖11整體內存引用方案


  • 優(yōu)點:沒有額外拷貝,異步寫入不會阻塞。

  • 缺點:由于內存限制,存在寫入線程等待刷盤數(shù)據(jù)釋放,block大小受限制(2K,如果使用4K則會存在寫入等待內存交換出來,寫入有波動,性能不是最優(yōu))。


3、分片內存池方案(C++實現(xiàn))(最優(yōu)成績)


綜合前2種方案后,提出采用分片內存池+iovec內存重組寫入的方案。具體實現(xiàn)為將block分為更小的slice,每次緩沖區(qū)申請一個slice,寫滿一個block后用iovec提交。


寫入線程將提交的slice用iovec拼接成更大的part,整體寫入磁盤。寫入后將slice釋放回內存池便于后續(xù)隊列緩沖使用。雖然均勻寫入是一種糟糕的寫入方式,存在緩沖申請激增的情況,但由于分片機制的存在,每次激增為1M*slice的大小,可以通過slice大小控制。具體流程如圖12,13所示。


圖12分片內存池方案


  • 優(yōu)點:更細粒度的內存分配和釋放,4K大小block情況下緩沖區(qū)能均勻移動,不會阻塞,寫入和落盤同步進行;能在有限內存中實現(xiàn)更大的block。


圖13 分片內存方案細節(jié)


內部使用的ring buffer方案及代碼如圖14所示,所有對象均實現(xiàn)了復用。


圖14 ring buffer實現(xiàn)細節(jié)


五、? 索引方案


由于內存空間限制,索引記錄所有消息的偏移并不是個好方案。本系統(tǒng)方案采用了稀疏索引,因為設計的消息存儲結構保證塊內消息能夠自行區(qū)分,僅記錄塊信息即可。只要能知道消息所在的塊的位置和消息是該塊的第幾個即可定位到該消息。


每個消息塊僅需要記錄3個信息:塊所在文件偏移(塊號);塊內消息數(shù);上個塊消息是否跨塊。通過索引定位到具體消息數(shù)據(jù)的方法也十分簡單。


  • 1、通過從前往后累加每個塊的消息數(shù)量,快速定位到消息所在塊;

  • 2、通過索引查找到塊所在文件偏移,并完整讀出塊(4K讀);

  • 3、通過是否跨塊信息和目標消息ID,確定需要跳過的消息數(shù),快速跳過無用消息。

  • 4、讀取目標消息。具體流程如圖15所示。


圖15 消息數(shù)據(jù)定位流程


定位完成后可以連續(xù)讀,一旦發(fā)現(xiàn)長度超出當前塊,則說明讀到跨塊消息,繼續(xù)切到下一塊讀取即可。


六、? 隨機讀與連續(xù)讀


隨機讀取的消息落到一個塊內有較大的概率,故即使是讀一個小范圍的數(shù)據(jù),大部分情況能一次IO讀取完成。針對連續(xù)讀,記錄每次讀取到的位置,如果下次訪問是從0或上次讀取的位置,則認為是連續(xù)消費,對隊列的下一個塊發(fā)起readahead操作,將其讀入到page cache中,實測可以提升40%性能。由于塊的大小是4K,連續(xù)讀時不會讀入任何無效數(shù)據(jù),操作系統(tǒng)的page cache能提供非常高的讀取性能。


七、 ?Map容器優(yōu)化


由于消息寫入接口是以隊列名對隊列進行區(qū)分的,隊列名到隊列對象的映射也是系統(tǒng)的瓶頸所在。針對數(shù)字后綴隊列名進行特殊hash計算處理,可以降低沖突概率。對于后綴計算hash沖突的情況,使用正常哈希計算,離散到32個加鎖的unordered map中進行存儲,保證魯棒性和性能。由于后綴的區(qū)分度高,spin lock基本沒有沖突,100W插入比傳統(tǒng)map快幾百ms,100W查找快幾十ms。Suffix map結構如圖16所示。


圖16 suffix map結構


八、? 讀寫狀態(tài)切換


程序一開始就設計有內存和磁盤協(xié)同工作的代碼,但由于緩沖占用內存過大,使得后續(xù)隨機讀和連續(xù)讀(依賴page cache)存在性能下降的問題,采用了讀之前將緩沖全部落盤的策略。


在第一次讀的時候,block所有讀線程,落盤,釋放所有緩沖內存(~5.5G)然后開始讀取操作。由于空余緩沖是全部填充padding的,即使再寫入隊列,數(shù)據(jù)也不會出錯。同時整體狀態(tài)可以切換回寫狀態(tài)(需重新申請緩沖內存)。


九、? 創(chuàng)新性與工程價值


  • 1、針對內存不足使用內存分片+iovec重組寫技術,達到在小內存的情況下寫內存和寫硬盤同時進行的目的,極大提升了寫磁盤性能。線上測試寫時存在CPU idle>0現(xiàn)象,說明生產(chǎn)已大于寫盤速度。

  • 2、針對隊列名稱改進了map,提出suffix map,針對后綴進行hash(現(xiàn)實情況下也大量存在編號結尾的隊列名),極大降低了hash碰撞幾率(測評數(shù)據(jù)中沒有碰撞),同時用自旋鎖和多個細粒度的unordered_map處理了碰撞情況,提升性能的時候保證了map的正確性魯棒性。

  • 3、先寫緩沖,緩沖滿了排隊,最后填滿一個大block后刷盤,極大利用了SSD順序寫入性能,同時在隊列不均勻時也能提供同樣的高性能。

  • 4、變長整形+消息存儲,及跨塊消息,超長消息的處理機制保證任意長度消息都能正確處理,并有同樣的高性能。

  • 5、稀疏索引,只對塊的偏移和消息數(shù)量做記錄,整個索引大小僅29*(4(blockId)+2(number))*1000000≈166MB,完全可以存儲在內存中。

  • 6、使用ring buffer,分散寫入和等待對象,采用引用機制(最后釋放引用的線程負責提交該塊到寫入隊列中),工作期間沒有任何對象創(chuàng)建和銷毀,建立在數(shù)組上,充分利用cpu cache,提升了性能。

  • 7、整體內存分配,然后分片,使用完成后整體釋放,降低了內存管理的cpu消耗。

  • 8、設計時考慮了緩沖和磁盤協(xié)同工作讀,不用將緩沖刷盤僅僅只用等待當前io完成,正確性可以保證,但由于占用太多內存,性能不好,故后期改為全刷盤操作。

  • 9、設計時候考慮了隊列索引自增漲算法,但由于tcmalloc分配內存不釋放,后面改為定長的了(有宏控制開關)。

  • 10、所有長度,池大小等參數(shù)都可以拿宏修改,可以修改適應大內存等各種情況。

總結

以上是生活随笔為你收集整理的[重磅] 如何更好地实现服务调用和消息推送的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。