推送架构的演进
架構(gòu)是為了更好的為業(yè)務(wù)提供更好的服務(wù)。架構(gòu)最終會(huì)以產(chǎn)品的方式提供給客戶(hù)使用。因此,在我們開(kāi)始討論這套系統(tǒng)架構(gòu)的演進(jìn)之前,請(qǐng)由我對(duì)我們系統(tǒng)做一個(gè)簡(jiǎn)單的介紹:
TD-Push,產(chǎn)品代號(hào)魔推。如上圖所示,TD-Push是一款為移動(dòng)APP提供的一套推送營(yíng)銷(xiāo)組件。我們的SDK擁有體積小、耗電少的特點(diǎn),同時(shí)支持公有、私有云的部署。對(duì)于推送內(nèi)容的報(bào)文,在傳輸過(guò)程中是全程密文,并且每個(gè)終端的秘鑰都不相同。它使用了Go語(yǔ)言編寫(xiě),擁有部署簡(jiǎn)單,成本低廉的特點(diǎn)。同時(shí)它的功能豐富,能夠?qū)γ看瓮扑偷男ЧM(jìn)行跟蹤。
首先通過(guò)一個(gè)圖來(lái)對(duì)我們的系統(tǒng)進(jìn)行初步的了解:
如上圖所示,客戶(hù)的APP通過(guò)集成Push的SDK,既可完成集成的工作。通過(guò)系統(tǒng)提供的Portal,即可完成推送和運(yùn)營(yíng)等相關(guān)的工作。好的開(kāi)始是成功的一半。系統(tǒng)從一開(kāi)始,就采用分布式的架構(gòu),這套架構(gòu)可以支持橫向擴(kuò)展低廉的硬件,來(lái)支撐更大的數(shù)據(jù)和更高的并發(fā)。從數(shù)據(jù)庫(kù),到每個(gè)系統(tǒng)組件都是分布式的,支持橫向擴(kuò)容。
先對(duì)系統(tǒng)的架構(gòu)組件進(jìn)行一個(gè)系統(tǒng)的了解,如圖所示:
從這個(gè)系統(tǒng)架構(gòu)圖上,可以清晰的看業(yè)務(wù)到系統(tǒng)的業(yè)務(wù)組件之間,都是通過(guò)Rest API進(jìn)行調(diào)度的。系統(tǒng)組件分為SDK、Connector、APNS、A3PNS、WPNS、Gateway、Controller 。在Gateway和Controller里面,包含了Collector, Dispatcher, DataService, TaskContainer, Portal。企業(yè)的員工,可以通過(guò)Portal執(zhí)行相關(guān)的營(yíng)銷(xiāo)推送。企業(yè)的應(yīng)用可以通過(guò)調(diào)用API,對(duì)業(yè)務(wù)進(jìn)行更多更靈活的處理。
隨著業(yè)務(wù)的發(fā)展、需求的演進(jìn)、數(shù)據(jù)量級(jí)的增加,百萬(wàn)、千萬(wàn)、數(shù)億、數(shù)十億,架構(gòu)和組件并未做太多調(diào)整。但也確實(shí)碰到了一些棘手的問(wèn)題。我把這些棘手的問(wèn)題,稱(chēng)之為甜蜜的負(fù)擔(dān),也可以叫做“成長(zhǎng)的煩擾”。我們把問(wèn)題列舉如下:
1> 數(shù)據(jù)庫(kù)不堪重負(fù):? 究竟是程序編寫(xiě)的問(wèn)題?還是數(shù)據(jù)庫(kù)選型需要調(diào)整?在傳統(tǒng)應(yīng)用系統(tǒng)編程的那些非常實(shí)用方法、技巧、規(guī)律,在數(shù)據(jù)量級(jí)提升,并發(fā)數(shù)的提升下。表現(xiàn)不盡人意。
2> 系統(tǒng)突如其來(lái)的請(qǐng)求高峰: 系統(tǒng)會(huì)存在大量密集的請(qǐng)求。峰值的請(qǐng)求,通常都會(huì)是普通情況下的好幾倍甚至更多。如果把系統(tǒng)的容量,根據(jù)峰值進(jìn)行評(píng)估,那會(huì)是平時(shí)的硬件的好幾倍。雖然這樣可以解決問(wèn)題,但這對(duì)于客戶(hù)和我們自己的云平臺(tái)來(lái)說(shuō),都是一種極大的浪費(fèi)。
3> 系統(tǒng)出現(xiàn)大量的Time Wait: 系統(tǒng)的業(yè)務(wù)組件之間,通過(guò)Rest API進(jìn)行調(diào)度。業(yè)務(wù)組件和數(shù)據(jù)庫(kù)之間,通過(guò)Socket進(jìn)行操作。當(dāng)系統(tǒng)業(yè)務(wù)量很高的時(shí)候,業(yè)務(wù)組件和業(yè)務(wù)組件之間,業(yè)務(wù)組件和數(shù)據(jù)庫(kù)之間,會(huì)存在大量的網(wǎng)絡(luò)操作。此時(shí),從系統(tǒng)運(yùn)行的日志里面,會(huì)發(fā)現(xiàn)一些奇怪的日志。比如 Request time out , Aerospike 出現(xiàn) EOF,使用Netstat進(jìn)行查看,發(fā)現(xiàn)有大量的Time wait。
4> 臨界區(qū)鎖保護(hù),性能提升不明顯:? 敏感資源,我們稱(chēng)為臨界區(qū)。我們使用鎖、讀寫(xiě)鎖 進(jìn)行保護(hù)。但當(dāng)并發(fā)量越來(lái)越大的時(shí)候,發(fā)現(xiàn)鎖其實(shí)也很耗費(fèi)資源。
5> 內(nèi)存寶貴,如何榨取更多硬件資源: 在我們系統(tǒng)的組件里面,使用了Map為Cache,使用了隊(duì)列等數(shù)據(jù)結(jié)構(gòu)。這些結(jié)構(gòu)的數(shù)據(jù),能帶來(lái)性能的提升,但通常隨著數(shù)據(jù)量的增大,內(nèi)存耗費(fèi)也會(huì)增大。對(duì)于CPU來(lái)說(shuō),用滿(mǎn)了,也不會(huì)產(chǎn)生明顯的硬傷。但作對(duì)內(nèi)存來(lái)說(shuō),一旦你用滿(mǎn)了,程序就會(huì)出現(xiàn)OOM。但是我們需要更高的性能,更大的容量,更少的網(wǎng)絡(luò)請(qǐng)求。
這都是我們遇到的一些問(wèn)題。面對(duì)這些問(wèn)題,我們都一一的,在日常的工作中,進(jìn)行了處理:
1> 我們把一些可以拆分的并發(fā)邏輯處理單元,把它們拆分成CSP的結(jié)構(gòu)。
2> 從普通的Sync Map到多元化的Cache。
3> 數(shù)據(jù)庫(kù)程序的優(yōu)化,從Open Session In View,到基于代理的數(shù)據(jù)庫(kù)連接。
4> 針對(duì)Dispatch組件進(jìn)行升級(jí),負(fù)載和調(diào)度的算法進(jìn)行升級(jí)。
5> 使用http2的協(xié)議,針對(duì)組件之間的調(diào)度進(jìn)行網(wǎng)絡(luò)I/O的優(yōu)化。
?
CSP模型介紹
CSP是Communicating Sequential Processes的縮寫(xiě),它的意思是順序通信進(jìn)程。也就是通過(guò)通信的方式,來(lái)代替函數(shù)的調(diào)用。
如圖所示:圖中的Channel 就是通信的管道,Worker就是處理單元。? Channel可以打個(gè)簡(jiǎn)單的比方,它和系統(tǒng)常見(jiàn)的隊(duì)列看起來(lái)很相似。Worker和Java的線(xiàn)程相似。當(dāng)然這里說(shuō)的是相似,也就是說(shuō)它們之間存在差異。大家可以先按照Channel是隊(duì)列,Work是線(xiàn)程的這種方式去理解。這套模型雖然我們是在Go語(yǔ)言中是使用,同時(shí)這套模型在其它的語(yǔ)言理也同樣適用,大家可以基于自己的理解,進(jìn)行靈活的應(yīng)用。模型中,Worker之間不直接彼此聯(lián)系,而是通過(guò)不同Channel進(jìn)行消息發(fā)布和偵聽(tīng)。消息的發(fā)送者和接收者之間通過(guò)Channel松耦合,發(fā)送者不知道自己消息被哪個(gè)接收者消費(fèi)了,接收者也不知道是哪個(gè)發(fā)送者發(fā)送的消息。
函數(shù)
在Golang的CSP模型里,它的Worker,就是一個(gè)普通的函數(shù)。在函數(shù)的前面放上一個(gè)Go 進(jìn)行執(zhí)行,這個(gè)函數(shù)便是一個(gè)并發(fā)的攜程。在Golang里面,函數(shù)是一等公民。函數(shù)可以作為變量、參數(shù)、返回值。如果您擅長(zhǎng)函數(shù)式編程的話(huà),Golang的函數(shù)也能支持您基于函數(shù)式編程。剛才我們提到了,函數(shù)可以作為變量、參數(shù)、返回值。函數(shù)作為返回值,可以提高函數(shù)的抽象層級(jí)、并且可以減少全局變量的暴露。
?
GoRoutine
模型中的每個(gè)Worker,都是一個(gè)GoRoutine。 協(xié)程(Coroutine)這個(gè)概念最早是Melvin Conway在1963年提出的,是并發(fā)運(yùn)算中的概念。構(gòu)建一個(gè)協(xié)程默認(rèn)只會(huì)產(chǎn)生4K的內(nèi)存,構(gòu)建一個(gè)線(xiàn)程的時(shí)候,會(huì)產(chǎn)生4M左右的內(nèi)存。線(xiàn)程的調(diào)度依賴(lài)于操作系統(tǒng)進(jìn)行調(diào)度。協(xié)程的調(diào)度是輕量級(jí)的,它是在進(jìn)程里進(jìn)行調(diào)度的。如圖所示:
Go的調(diào)度器內(nèi)部有三個(gè)重要的結(jié)構(gòu):M,P,S
M:代表真正的內(nèi)核OS線(xiàn)程,和POSIX里的Thread差不多,真正干活的人
G:代表一個(gè)Goroutine,它有自己的棧,Instruction Pointer和其它信息(正在等待的Channel等等),用于調(diào)度。
P:代表調(diào)度的上下文,可以把它看做一個(gè)局部的調(diào)度器,使Go代碼在一個(gè)線(xiàn)程上跑,它是實(shí)現(xiàn)從N:1到N:M映射的關(guān)鍵。
?
Channel
在CSP模型中,Channel是一個(gè)數(shù)據(jù)傳輸?shù)募~帶。在申明Channel的時(shí)候,可以指定Channel的只讀、只寫(xiě)、讀寫(xiě)權(quán)限和Buffer大小。在對(duì)Channel進(jìn)行操作的時(shí)候,可以使用阻塞和非阻塞的方式。這里列舉一些Channel的應(yīng)用:
??????? 基于Channel返回大量的數(shù)據(jù):
基于Channel返回大量的數(shù)據(jù)。可以在函數(shù)中返回一個(gè)Channel 。然后一邊往Channel里面寫(xiě),一邊往Channel里面讀。一方面節(jié)省了內(nèi)存,另一方面提高了運(yùn)算。
??????? 使用Channel實(shí)現(xiàn)超時(shí)、心跳等:
使用定時(shí)和超時(shí)的Channel來(lái)驅(qū)動(dòng)處理函數(shù),來(lái)實(shí)現(xiàn)超時(shí)、心跳等多項(xiàng)自定義業(yè)務(wù)。
??????? 基于Channel實(shí)現(xiàn)Latch,控制并發(fā)數(shù):
如上圖所示,通過(guò)一個(gè)固定長(zhǎng)度的Channel,實(shí)現(xiàn)并發(fā)數(shù)的控制。獲取并發(fā)權(quán)限的時(shí)候,往Channel里面寫(xiě)入一個(gè)對(duì)象。釋放并發(fā)權(quán)限的時(shí)候,往Channel里面讀取一個(gè)對(duì)象。Channel的長(zhǎng)度,就是并發(fā)數(shù)。
??????? 使用Channel實(shí)現(xiàn)Recycling memory buffers
左側(cè)是Give Channel , 右側(cè)是Get Channel .中間是 Recyling Buffer.左側(cè)的Give Channel負(fù)責(zé),收回已經(jīng)借出去的內(nèi)存.中間的Recycling Buffer負(fù)責(zé)在缺少內(nèi)存時(shí)產(chǎn)生Buffer,并根據(jù)過(guò)期規(guī)則過(guò)期多余的buffer.右側(cè)的Get Channel負(fù)責(zé),把Buffer借出去。
使用了CSP模型之后,給系統(tǒng)帶來(lái)了穩(wěn)定的收益。這些收益來(lái)自于:
無(wú)鎖: 使用通訊代替共享內(nèi)存的方式,減少鎖競(jìng)爭(zhēng)帶來(lái)的性能下降。
Channel: 使用了Channel,把大量處理的任務(wù)以Worker的方式執(zhí)行,在負(fù)載過(guò)高時(shí),系統(tǒng)依舊平穩(wěn)。
GoRoutine:為系統(tǒng)帶來(lái)了計(jì)算和并發(fā)性能提升
CSP模型:可以讓代碼的結(jié)構(gòu)清晰,易于維護(hù)。從而增加了軟件的可維護(hù)性和可擴(kuò)展性。
多元化的Cache
??? 在最起初的時(shí)候,數(shù)據(jù)量很小,我們使用一個(gè)普通的Map,作為Cache,放在內(nèi)存中。對(duì)于部分業(yè)務(wù),甚至沒(méi)有Cache,直接查數(shù)據(jù)庫(kù)。隨著數(shù)據(jù)量和并發(fā)量的提升。這種做法已經(jīng)不能應(yīng)對(duì)當(dāng)前的情況了。因此,我們?cè)谘葸M(jìn)的過(guò)程中Cache實(shí)現(xiàn)了多元化。如圖所示:
?
?
首先我們?cè)?/span>Map的基礎(chǔ)上,引入了Heap 。在Heap里面存放了基于Score排序好的Key。這些Key可以是最后一次使用時(shí)間或者過(guò)期時(shí)間或者使用頻次等。由此我們就有了Timer Cache, LRU Cache.
其次在數(shù)據(jù)過(guò)期的時(shí)候,我們想處理更多的邏輯,因此我們引入了回調(diào)函數(shù)。回調(diào)函數(shù)作為閉包,在構(gòu)造的時(shí)候,傳入。過(guò)期時(shí)Cache會(huì)自動(dòng)執(zhí)行回調(diào)函數(shù)
隨著數(shù)據(jù)量變得更大之后,我們發(fā)現(xiàn)內(nèi)存十分寶貴。我們需要向硬盤(pán)索取更大的容量。我們采用了LevelDB存儲(chǔ)引擎,把較大的Value存放在磁盤(pán)上。
在分布式環(huán)境中,當(dāng)一臺(tái)服務(wù)器更新了Cache數(shù)據(jù),而其它主機(jī)沒(méi)有同步刷新此條Cache的時(shí)候,會(huì)出現(xiàn)一些數(shù)據(jù)不一致的問(wèn)題。因此我們系統(tǒng)引入了哨兵,來(lái)保證Cache數(shù)據(jù)的一致性。
至此,我們?cè)?/span>Cache的優(yōu)化的道路上,一直努力著。比如:前段時(shí)間我們發(fā)現(xiàn),即便你有各種過(guò)期算法,但也不能明確和量化Cache具體使用的內(nèi)存大小。因此目前我們?cè)?/span>Cache的容量上限也做了一定的控制。
?
數(shù)據(jù)層的優(yōu)化
我們架構(gòu)的這套系統(tǒng)是一套業(yè)務(wù)系統(tǒng),數(shù)據(jù)庫(kù)的性能,直接影響程序的性能。數(shù)據(jù)層的優(yōu)化如下圖所示:
系統(tǒng)在早期的雛形中,使用了MongoDB和Redis. MongoDB負(fù)責(zé)所有業(yè)務(wù)的數(shù)據(jù)存儲(chǔ)。Redis支撐起了離線(xiàn)消息存儲(chǔ)。在系統(tǒng)的程序里,我們使用依賴(lài)注入和Open Session In View的模式。
隨著數(shù)據(jù)量的上升。SDK會(huì)周期性的上報(bào)自己的基礎(chǔ)信息。系統(tǒng)的寫(xiě)比例遠(yuǎn)高于讀取的比例,MongoDB寫(xiě)入出現(xiàn)瓶頸。此時(shí)引入了分布式內(nèi)存數(shù)據(jù)庫(kù)Aerospike。讓Aerospike提供無(wú)中心的、高效的讀寫(xiě)性能。
就這樣系統(tǒng)平穩(wěn)了好一段時(shí)間。突然有一天,系統(tǒng)發(fā)現(xiàn)Mongo連接池不夠用。一開(kāi)始我們懷疑是否自己程序有問(wèn)題?驅(qū)動(dòng)有使用不恰當(dāng)?shù)牡胤?#xff1f;最后經(jīng)過(guò)我們發(fā)現(xiàn),其實(shí)是我們的Mongo連接資源占用的時(shí)間較長(zhǎng)導(dǎo)致的。再此情況下,我們編寫(xiě)了一個(gè)數(shù)據(jù)源代理程序,用于減少敏感的數(shù)據(jù)庫(kù)連接的占用時(shí)間。就這樣,連接數(shù)降低一個(gè)數(shù)量級(jí)。而且也穩(wěn)定在這個(gè)數(shù)量級(jí)。
如此往復(fù)系統(tǒng)又平穩(wěn)了好一段時(shí)間。突然發(fā)現(xiàn)系統(tǒng)的吞吐量下降了。QPS、TPS都下降了。為何之前一直運(yùn)行平穩(wěn)的系統(tǒng),性能出現(xiàn)抖動(dòng)?加入直方圖,監(jiān)控一些性能影響因素,我們發(fā)現(xiàn)了特定代碼的在特定情況下的表現(xiàn)出現(xiàn)了2ms的抖動(dòng),導(dǎo)致了這一現(xiàn)象的發(fā)生。直方圖對(duì)問(wèn)題的定位起到了很好的作用。
現(xiàn)在我們針對(duì)Mongo客戶(hù)端的使用,進(jìn)行一個(gè)總結(jié)。如下圖所示:
圖的左側(cè)是Open Session In View的方式。也是我們系統(tǒng)起初使用的方式。它能夠確保每個(gè)http請(qǐng)求,都有數(shù)據(jù)處理的能力。同時(shí)又能保證每個(gè)數(shù)據(jù)庫(kù)連接都能夠被關(guān)閉?;陬?lèi)型注入,對(duì)變量的作用域,也有一個(gè)很好的管理。這種方式,在并發(fā)量不大的時(shí)候,還是不錯(cuò)的。隨著并發(fā)量的增大,它的缺點(diǎn)也暴露出來(lái)了:
圖中小方塊,每一格,代表一個(gè)單位的時(shí)間。Open Session In View的方式占用的Session的時(shí)間太長(zhǎng),以至于出現(xiàn)了等待、連接池不夠用的現(xiàn)象。
為了應(yīng)對(duì)這一現(xiàn)象,我們引入了連接代理。圖片的右側(cè)沒(méi)有等待連接的現(xiàn)象
而且每個(gè)數(shù)據(jù)庫(kù)操作的時(shí)間非常短
同時(shí)它也保證了之前Open Session In View里擁有的優(yōu)點(diǎn),代碼改動(dòng)也很小。
那對(duì)于新的這種方式,我們做了以下工作:
??????? 沿用了OSIV
??????? 使用了代理連接,也就是說(shuō):每次Open Session的時(shí)候,Open的是一個(gè)代理類(lèi),并未產(chǎn)生真正的數(shù)據(jù)庫(kù)連接
??????? 代理連接支持自動(dòng)打開(kāi)和重復(fù)關(guān)閉
??????? 在真正操作數(shù)據(jù)庫(kù)的時(shí)候,建立連接。
?
在Aerospike的使用過(guò)程中,我們積極的向社區(qū)反饋問(wèn)題。比如驅(qū)動(dòng)拋出的錯(cuò)誤日志、數(shù)據(jù)庫(kù)在特定場(chǎng)景下出現(xiàn)的一些問(wèn)題等等。
凡事預(yù)則立不預(yù)則廢。對(duì)于硬件擴(kuò)容需要有一個(gè)規(guī)劃。容量和內(nèi)存評(píng)估,為硬件規(guī)劃提供很好的理論基礎(chǔ)。
運(yùn)維監(jiān)控:可以通過(guò)運(yùn)維,觀察到系統(tǒng)的數(shù)據(jù)總量、以及增長(zhǎng)速度。并且為系統(tǒng)存在風(fēng)險(xiǎn)進(jìn)行預(yù)警。
在客戶(hù)端的讀寫(xiě)參數(shù)調(diào)整:我們根據(jù)不同的數(shù)據(jù)等級(jí),采用了不同的寫(xiě)入策略。針對(duì)不同查詢(xún)數(shù)量級(jí),使用不同的查詢(xún)策略。
在項(xiàng)目的初期,我們把Aerospike放在云的虛擬機(jī)上。即便是分布式內(nèi)存數(shù)據(jù)庫(kù),面對(duì)高峰時(shí)的成噸的并發(fā),也會(huì)有不盡人意的地方。我們?cè)诳蛻?hù)端,將密集的寫(xiě)請(qǐng)求改成CSP的模式。讓性能穩(wěn)定在一個(gè)最佳值。與此同時(shí)優(yōu)化讀寫(xiě)策略。內(nèi)存如此寶貴,把Aerospike從全內(nèi)存模式,變成SSD的模式。同等內(nèi)存條件下,存儲(chǔ)容量得到了1個(gè)數(shù)量級(jí)的提升。通過(guò)容量評(píng)估,為硬件規(guī)劃提供很好的理論基礎(chǔ)。在客戶(hù)端的數(shù)據(jù)訪(fǎng)問(wèn)層我們做了一些優(yōu)化工作:
1> 引入Cache,減少讀取壓力
2> 將并發(fā)密集的寫(xiě),改成CSP模式
3> 引入直方圖監(jiān)控,量化性能指標(biāo),實(shí)現(xiàn)針對(duì)性的優(yōu)化。
?
任務(wù)分發(fā)與調(diào)度的演進(jìn)
任務(wù)分發(fā)和調(diào)度演進(jìn),大致可以通過(guò)下圖進(jìn)行一個(gè)概括:
在之前的調(diào)度流程中,我們包含Dispatch/任務(wù)分發(fā)/健康檢查的工作內(nèi)容。
l? Dispatch: 當(dāng)一個(gè)終端連接上來(lái)的時(shí)候,需要根據(jù)Connector當(dāng)前的負(fù)載和權(quán)值,進(jìn)行連接地址的指定。
l? 任務(wù)分發(fā):推送消息下發(fā)。由于下發(fā)任務(wù)的服務(wù)器,并不清楚,哪個(gè)連接歸屬于哪臺(tái)服務(wù)器,因此需要做二次發(fā)送,第一次在在線(xiàn)發(fā)送,并且記錄發(fā)送失敗的設(shè)備。在第二次發(fā)送的時(shí)候,Shuffle打亂在進(jìn)行離線(xiàn)發(fā)送。
l? 健康檢查:是定期檢查Connector的負(fù)載,并記錄。
但其實(shí)這么發(fā)送,是存在一定的資源浪費(fèi)的。針對(duì)這些資源的讓費(fèi),我們使用了一致性哈希算法,優(yōu)化了這些流程。我們先來(lái)看看基于權(quán)值和監(jiān)控檢查的路由和分發(fā)是如何工作又存在什么問(wèn)題。這個(gè)流程大致如下圖方式進(jìn)行工作:
圖片的左上角是Dispatch流程,中間是健康檢查流程,最右邊是任務(wù)下發(fā)流程。隨著結(jié)點(diǎn)數(shù)的增加,(健康檢查、任務(wù)下發(fā))的網(wǎng)絡(luò)請(qǐng)求會(huì)成倍增加。隨著Dispatch數(shù)量級(jí)的增加,并發(fā)也相應(yīng)增加。又因?yàn)榻】禉z查存在時(shí)間窗口,導(dǎo)致誤差會(huì)隨著設(shè)備數(shù)量級(jí)的增加而增加。
下發(fā)性能會(huì)隨著下發(fā)的數(shù)量的增加而成倍增加,導(dǎo)致任務(wù)下發(fā)出現(xiàn)瓶頸。面臨這個(gè)現(xiàn)狀,我們使用了一致性哈希算法,調(diào)整后的流程如下圖:
首先,我們砍掉了健康檢查這個(gè)流程,無(wú)須了解各個(gè)Connector的負(fù)載
其次,在Dispatch,直接根據(jù)哈希算法進(jìn)行指定Connector
然后,在任務(wù)下發(fā)的時(shí)候,具有很強(qiáng)的針對(duì)性。一步到位,減少了往復(fù)的流程。
?
網(wǎng)絡(luò)I/O的優(yōu)化
我們?cè)诰W(wǎng)絡(luò)I/O上確實(shí)碰到了一些偶發(fā)性的問(wèn)題。即便Aerospike服務(wù)器和客戶(hù)端都做了很多優(yōu)化的工作。但日志里面仍然會(huì)出現(xiàn)EOF的問(wèn)題。在我們系統(tǒng)的組件之間,存在著頻繁、密集的交互。這些交互都是Http1的協(xié)議。當(dāng)系統(tǒng)之間頻繁交互(如:獲取推送業(yè)務(wù)指標(biāo)和狀態(tài)、密集的單推和廣播等推送業(yè)務(wù))。系統(tǒng)會(huì)在極端的情況下出現(xiàn)Time Wait,Close Wait等現(xiàn)象。針對(duì)上述現(xiàn)象。我們使用了Http2的協(xié)議,在代碼小幅度調(diào)整的情況下,實(shí)現(xiàn)了請(qǐng)求的連接的多路復(fù)用。起初我們思考,為什么非得使用HTTP而不用RPC呢?Rest API,既能用于 前端頁(yè)面的展現(xiàn),又便于其它語(yǔ)言進(jìn)行集成。Http相對(duì)于RPC更輕量級(jí)。使用Passive Feed Back的方式,把系統(tǒng)里面可以合并的請(qǐng)求進(jìn)行合并。將請(qǐng)求從被動(dòng)的Post,變成主動(dòng)的Get。使用Redis Pipe Line 的方式,合并Redis的操作,將多個(gè)操作打包成一個(gè)Pipe Line進(jìn)行發(fā)送。節(jié)省了socket成本。
?
?
在這篇博文中,涵蓋了CSP并發(fā)編程模型、數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)層的優(yōu)化、Dispatch流程的優(yōu)化,多元化的Cache以及網(wǎng)絡(luò)I/O相關(guān)的話(huà)題。如果大家對(duì)Push和相關(guān)的技術(shù)感興趣,可以通過(guò)TalkingData聯(lián)系到我,進(jìn)行相關(guān)的探討。
?
?
總結(jié)
- 上一篇: android 最低兼容版本,vue c
- 下一篇: 简单的wchar_t 和 char 转换