日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
優(yōu)化 Golang 分布式行情推送的性能瓶頸

優(yōu)化 Golang 分布式行情推送的性能瓶頸

作者:峰云就她了 2021-07-05 08:58:17

開發(fā)

前端

分布式 最近一直在優(yōu)化行情推送系統(tǒng),有不少優(yōu)化心得跟大家分享下。性能方面提升最明顯的是時(shí)延,在單節(jié)點(diǎn)8萬(wàn)客戶端時(shí),時(shí)延從1500ms優(yōu)化到40ms,這里是內(nèi)網(wǎng)mock客戶端的得到的壓測(cè)數(shù)據(jù)。

創(chuàng)新互聯(lián)公司基于分布式IDC數(shù)據(jù)中心構(gòu)建的平臺(tái)為眾多戶提供服務(wù)器托管 四川大帶寬租用 成都機(jī)柜租用 成都服務(wù)器租用。

本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)桃花源」,作者峰云就她了 。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)桃花源公眾號(hào)。

最近一直在優(yōu)化行情推送系統(tǒng),有不少優(yōu)化心得跟大家分享下。性能方面提升最明顯的是時(shí)延,在單節(jié)點(diǎn)8萬(wàn)客戶端時(shí),時(shí)延從1500ms優(yōu)化到40ms,這里是內(nèi)網(wǎng)mock客戶端的得到的壓測(cè)數(shù)據(jù)。

對(duì)于訂閱客戶端數(shù)沒(méi)有太執(zhí)著量級(jí)的測(cè)試,弱網(wǎng)絡(luò)下單機(jī)8w客戶端是沒(méi)問(wèn)題的。當(dāng)前采用的是kubenetes部署方案,可靈活地?cái)U(kuò)展擴(kuò)容。

架構(gòu)圖

push-gateway是推送的網(wǎng)關(guān),有這么幾個(gè)功能:第一點(diǎn)是為了做鑒權(quán);第二點(diǎn)是為了做接入多協(xié)議,我們這里實(shí)現(xiàn)了websocket, grpc, grpc-web,sse的支持;第三點(diǎn)是為了實(shí)現(xiàn)策略調(diào)度及親和綁定等。

push-server 是推送服務(wù),這里維護(hù)了訂閱關(guān)系及監(jiān)聽(tīng)mq的新消息,繼而推送到網(wǎng)關(guān)。

問(wèn)題一:并發(fā)操作map帶來(lái)的鎖競(jìng)爭(zhēng)及時(shí)延

推送的服務(wù)需要維護(hù)訂閱關(guān)系,一般是用嵌套的map結(jié)構(gòu)來(lái)表示,這樣造成map并發(fā)競(jìng)爭(zhēng)下帶來(lái)的鎖競(jìng)爭(zhēng)和時(shí)延高的問(wèn)題。

  
 
 
 
  1. // xiaorui.cc  
  2. {"topic1": {"uuid1": client1, "uuid2": client2}, "topic2": {"uuid3": client3,  "uuid4": client4}   ... }  

已經(jīng)根據(jù)業(yè)務(wù)拆分了4個(gè)map,但是該訂閱關(guān)系是嵌套的,直接上鎖會(huì)讓其他協(xié)程都阻塞,阻塞就會(huì)造成時(shí)延高。

加鎖操作map本應(yīng)該很快,為什么會(huì)阻塞?上面我們有說(shuō)過(guò)該map是用來(lái)存topic和客戶端列表的訂閱關(guān)系,當(dāng)我進(jìn)行推送時(shí),必然是需要拿到該topic的所有客戶端,然后進(jìn)行一個(gè)個(gè)的send通知。(這里的send不是io.send,而是chan send,每個(gè)客戶端都綁定了緩沖的chan)

解決方法:在每個(gè)業(yè)務(wù)里劃分256個(gè)map和讀寫鎖,這樣鎖的粒度降低到1/256。除了該方法,開始有嘗試過(guò)把客戶端列表放到一個(gè)新的slice里返回,但造成了 GC 的壓力,經(jīng)過(guò)測(cè)試不可取。

  
 
 
 
  1. // xiaorui.cc 
  2.  
  3. sync.RWMutex 
  4. map[string]map[string]client 
  5.  
  6. 改成這樣 
  7.  
  8. m *shardMap.shardMap 

分段map的庫(kù)已經(jīng)推到github[1]了,有興趣的可以看看。

問(wèn)題二:串行消息通知改成并發(fā)模式

簡(jiǎn)單說(shuō),我們?cè)谕扑头?wù)維護(hù)了某個(gè)topic和1w個(gè)客戶端chan的映射,當(dāng)從mq收到該topic消息后,再通知給這1w個(gè)客戶端chan。

客戶端的chan本身是有大buffer,另外發(fā)送的函數(shù)也使用 select default 來(lái)避免阻塞。但事實(shí)上這樣串行發(fā)送chan耗時(shí)不小。對(duì)于channel底層來(lái)說(shuō),需要goready等待channel的goroutine,推送到runq里。

下面是我寫的benchmark[2],可以對(duì)比串行和并發(fā)的耗時(shí)對(duì)比。在mac下效果不是太明顯,因?yàn)閙ac cpu頻率較高,在服務(wù)器里效果明顯。

串行通知,拿到所有客戶端的chan,然后進(jìn)行send發(fā)送。

  
 
 
 
  1. for _, notifier := range notifiers { 
  2.     s.directSendMesg(notifier, mesg) 

并發(fā)send,這里使用協(xié)程池來(lái)規(guī)避morestack的消耗,另外使用sync.waitgroup里實(shí)現(xiàn)異步下的等待。

  
 
 
 
  1. // xiaorui.cc 
  2.  
  3. notifiers := []*mapping.StreamNotifier{} 
  4. // conv slice 
  5. for _, notifier := range notifierMap { 
  6.     notifiers = append(notifiers, notifier) 
  7.  
  8.  
  9. // optimize: direct map struct 
  10. taskChunks := b.splitChunks(notifiers, batchChunkSize) 
  11.  
  12.  
  13. // concurrent send chan 
  14. wg := sync.WaitGroup{} 
  15. for _, chunk := range taskChunks { 
  16.     chunkCopy := chunk // slice replica 
  17.     wg.Add(1) 
  18.     b.SubmitBlock( 
  19.         func() { 
  20.             for _, notifier := range chunkCopy { 
  21.                 b.directSendMesg(notifier, mesg) 
  22.             } 
  23.             wg.Done() 
  24.         }, 
  25.     ) 
  26. wg.Wait() 

按線上的監(jiān)控表現(xiàn)來(lái)看,時(shí)延從200ms降到30ms。這里可以做一個(gè)更深入的優(yōu)化,對(duì)于少于5000的客戶端,可直接串行調(diào)用,反之可并發(fā)調(diào)用。

問(wèn)題三:過(guò)多的定時(shí)器造成cpu開銷加大

行情推送里有大量的心跳檢測(cè),及任務(wù)時(shí)間控速,這些都依賴于定時(shí)器。go在1.9之后把單個(gè)timerproc改成多個(gè)timerproc,減少了鎖競(jìng)爭(zhēng),但四叉堆數(shù)據(jù)結(jié)構(gòu)的時(shí)間復(fù)雜度依舊復(fù)雜,高精度引起的樹和鎖的操作也依然頻繁。

所以,這里改用時(shí)間輪解決上述的問(wèn)題。數(shù)據(jù)結(jié)構(gòu)改用簡(jiǎn)單的循環(huán)數(shù)組和map,時(shí)間的精度弱化到秒的級(jí)別,業(yè)務(wù)上對(duì)于時(shí)間差是可以接受的。

Golang時(shí)間輪的代碼已經(jīng)推到github[3]了,時(shí)間輪很多方法都兼容了golang time原生庫(kù)。有興趣的可以看下。

問(wèn)題四:多協(xié)程讀寫chan會(huì)出現(xiàn)send closed panic的問(wèn)題

解決的方法很簡(jiǎn)單,就是不要直接使用channel,而是封裝一個(gè)觸發(fā)器,當(dāng)客戶端關(guān)閉時(shí),不主動(dòng)去close chan,而是關(guān)閉觸發(fā)器里的ctx,然后直接刪除topic跟觸發(fā)器的映射。

  
 
 
 
  1. // xiaorui.cc 
  2.  
  3. // 觸發(fā)器的結(jié)構(gòu) 
  4. type StreamNotifier struct { 
  5.     Guid  string 
  6.     Queue chan interface{} 
  7.  
  8.  
  9.     closed int32 
  10.     ctx    context.Context 
  11.     cancel context.CancelFunc 
  12.  
  13.  
  14. func (sc *StreamNotifier) IsClosed() bool { 
  15.     if sc.ctx.Err() == nil { 
  16.         return false 
  17.     } 
  18.     return true 
  19.  
  20. ... 

問(wèn)題五:提高grpc的吞吐性能

grpc是基于http2協(xié)議來(lái)實(shí)現(xiàn)的,http2本身實(shí)現(xiàn)流的多路復(fù)用。通常來(lái)說(shuō),內(nèi)網(wǎng)的兩個(gè)節(jié)點(diǎn)使用單連接就可以跑滿網(wǎng)絡(luò)帶寬,無(wú)性能問(wèn)題。但在golang里實(shí)現(xiàn)的grpc會(huì)有各種鎖競(jìng)爭(zhēng)的問(wèn)題。

如何優(yōu)化?多開grpc客戶端,規(guī)避鎖競(jìng)爭(zhēng)的沖突概率。測(cè)試下來(lái)qps提升很明顯,從8w可以提到20w左右。

可參考以前寫過(guò)的grpc性能測(cè)試[4]。

問(wèn)題六:減少協(xié)程數(shù)量

有朋友認(rèn)為等待事件的協(xié)程多了無(wú)所謂,只是占內(nèi)存,協(xié)程拿不到調(diào)度,不會(huì)對(duì)runtime性能產(chǎn)生消耗。這個(gè)說(shuō)法是錯(cuò)誤的。雖然拿不到調(diào)度,看起來(lái)只是占內(nèi)存,但是會(huì)對(duì) GC 有很大的開銷。所以,不要開太多的空閑的協(xié)程,比如協(xié)程池開的很大。

在推送的架構(gòu)里,push-gateway到push-server不僅幾個(gè)連接就可以,且?guī)资畟€(gè)stream就可以。我們自己實(shí)現(xiàn)大量消息在十幾個(gè)stream里跑,然后調(diào)度通知。在golang grpc streaming的實(shí)現(xiàn)里,每個(gè)streaming請(qǐng)求都需要一個(gè)協(xié)程去等待事件。所以,共享stream通道也能減少協(xié)程的數(shù)量。

問(wèn)題七:GC 問(wèn)題

對(duì)于頻繁創(chuàng)建的結(jié)構(gòu)體采用sync.Pool進(jìn)行緩存。有些業(yè)務(wù)的緩存先前使用list鏈表來(lái)存儲(chǔ),在不斷更新新數(shù)據(jù)時(shí),會(huì)不斷的創(chuàng)建新對(duì)象,對(duì) GC 造成影響,所以改用可復(fù)用的循環(huán)數(shù)組來(lái)實(shí)現(xiàn)熱緩存。

后記

有坑不怕,填上就可以了。

參考資料

[1]github: https://github.com/rfyiamcool/ccmap/blob/master/syncmap.go

[2]benchmark: https://github.com/rfyiamcool/go-benchmark/tree/master/batch_notify_channel

[3]github: https://github.com/rfyiamcool/go-timewheel

[4]測(cè)試: https://github.com/rfyiamcool/grpc_batch_test


文章名稱:優(yōu)化 Golang 分布式行情推送的性能瓶頸
分享路徑:http://www.5511xx.com/article/cdgddgs.html