您好,登錄后才能下訂單哦!
??事件驅(qū)動(dòng)編程是一種編程范式,這里程序的執(zhí)行流由外部事件來(lái)決定。它的特點(diǎn)是包含一個(gè)事件循環(huán),當(dāng)外部事件發(fā)生時(shí)使用回調(diào)機(jī)制來(lái)觸發(fā)相應(yīng)的處理。多線(xiàn)程是另一種常用編程范式,并且更容易理解。
??高性能通用型C++網(wǎng)絡(luò)框架 Nebula 是基于事件驅(qū)動(dòng)的多進(jìn)程網(wǎng)絡(luò)框架(適用于即時(shí)通訊、數(shù)據(jù)采集、實(shí)時(shí)計(jì)算、消息推送等應(yīng)用場(chǎng)景),已有即時(shí)通訊、埋點(diǎn)數(shù)據(jù)采集及實(shí)時(shí)分析的生產(chǎn)應(yīng)用案例。經(jīng)常有人問(wèn)Nebula的每個(gè)進(jìn)程里是單線(xiàn)程還是多線(xiàn)程的?又問(wèn)為什么不用多線(xiàn)程?不用多線(xiàn)程又怎么處理并發(fā)問(wèn)題?
??最近 Nebula 將會(huì)用于一個(gè)新的生產(chǎn)項(xiàng)目——推薦引擎,在此之前團(tuán)隊(duì)已有使用某知名度較高的RPC框架多線(xiàn)程版推薦引擎(業(yè)界許多推薦引擎都用了目前比較知名的開(kāi)源RPC框架來(lái)開(kāi)發(fā))。本文不做Nebula與各知名RPC框架的比較,也無(wú)意說(shuō)明哪個(gè)框架更適合做推薦引擎,只說(shuō)明Nebula可以用于推薦引擎,且有信心效果會(huì)很好。最終結(jié)果如何,等推薦引擎研發(fā)出來(lái),拭目以待。
??為什么是事件驅(qū)動(dòng)而不是多線(xiàn)程?事件驅(qū)動(dòng)無(wú)須多線(xiàn)程。我們先來(lái)回顧一下服務(wù)器編程范式。
??《UNIX網(wǎng)絡(luò)編程》卷一里介紹了9種服務(wù)器設(shè)計(jì)范式:
??九種服務(wù)器設(shè)計(jì)范式并不是全都有實(shí)用價(jià)值,在《UNIX網(wǎng)絡(luò)編程》卷一最后一節(jié)里給出了幾種TCP服務(wù)器設(shè)計(jì)范式代碼示例:
??Nginx采用的是九種服務(wù)器設(shè)計(jì)范式里的第5種“預(yù)先派生子進(jìn)程,使用互斥鎖上鎖方式保護(hù)accept”,Nebula采用的是九種服務(wù)器設(shè)計(jì)范式里的第6種“預(yù)先派生子進(jìn)程,由父進(jìn)程向子進(jìn)程傳遞套接字文件描述符”。
??一個(gè)典型的事件驅(qū)動(dòng)的程序,就是一個(gè)死循環(huán),并以一個(gè)線(xiàn)程的形式存在,這個(gè)死循環(huán)包括兩個(gè)部分,第一個(gè)部分是按照一定的條件接收并選擇一個(gè)要處理的事件,第二個(gè)部分就是事件的處理過(guò)程。程序的執(zhí)行過(guò)程就是選擇事件和處理事件,而當(dāng)沒(méi)有任何事件觸發(fā)時(shí),程序會(huì)因查詢(xún)事件隊(duì)列失敗而進(jìn)入睡眠狀態(tài),從而釋放cpu。
??某種意義上說(shuō),服務(wù)端程序大多是事件驅(qū)動(dòng)的,或者說(shuō)是IO請(qǐng)求事件驅(qū)動(dòng)的。這里比較的編程模型里的事件驅(qū)動(dòng)是指事件處理部分是異步的,即不僅IO請(qǐng)求事件驅(qū)動(dòng),還有IO響應(yīng)事件驅(qū)動(dòng),它的特點(diǎn)是當(dāng)外部IO響應(yīng)事件發(fā)生時(shí)使用回調(diào)機(jī)制來(lái)觸發(fā)相應(yīng)的處理。
??在單線(xiàn)程同步模型中,任務(wù)按照順序執(zhí)行。如果某個(gè)任務(wù)因?yàn)镮/O而阻塞,其他所有的任務(wù)都必須等待,直到它完成之后它們才能依次執(zhí)行。這種明確的執(zhí)行順序和串行化處理的行為是很容易推斷得出的。如果任務(wù)之間并沒(méi)有互相依賴(lài)的關(guān)系,但仍然需要互相等待的話(huà)這就使得程序不必要的降低了運(yùn)行速度。
??在多線(xiàn)程模型,每個(gè)任務(wù)分別在獨(dú)立的線(xiàn)程中執(zhí)行。這些線(xiàn)程由操作系統(tǒng)來(lái)管理,在多處理器系統(tǒng)上可以并行處理,或者在單處理器系統(tǒng)上交錯(cuò)執(zhí)行。這使得當(dāng)某個(gè)線(xiàn)程阻塞在某個(gè)資源的同時(shí)其他線(xiàn)程得以繼續(xù)執(zhí)行。與完成類(lèi)似功能的同步程序相比,這種方式更有效率,但程序員必須寫(xiě)代碼來(lái)保護(hù)共享資源,防止其被多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)。多線(xiàn)程程序更加難以推斷,因?yàn)檫@類(lèi)程序不得不通過(guò)線(xiàn)程同步機(jī)制如鎖、可重入函數(shù)、線(xiàn)程局部存儲(chǔ)或者其他機(jī)制來(lái)處理線(xiàn)程安全問(wèn)題,如果實(shí)現(xiàn)不當(dāng)就會(huì)導(dǎo)致出現(xiàn)微妙且令人痛不欲生的bug。另一個(gè)問(wèn)題,操作系統(tǒng)內(nèi)核在切換線(xiàn)程的同時(shí)也要切換線(xiàn)程的上下文,當(dāng)線(xiàn)程數(shù)量過(guò)多時(shí),時(shí)間將會(huì)被耗用在上下文切換中。所以在大并發(fā)量時(shí),多線(xiàn)程結(jié)構(gòu)還是無(wú)法做到強(qiáng)大的伸縮性。
??在事件驅(qū)動(dòng)版本的程序中,3個(gè)任務(wù)交錯(cuò)執(zhí)行,但仍然在一個(gè)單獨(dú)的線(xiàn)程控制中。當(dāng)處理I/O或者其他昂貴的操作時(shí),注冊(cè)一個(gè)回調(diào)到事件循環(huán)中,然后當(dāng)I/O操作完成時(shí)繼續(xù)執(zhí)行。回調(diào)描述了該如何處理某個(gè)事件。事件循環(huán)輪詢(xún)所有的事件,當(dāng)事件到來(lái)時(shí)將它們分配給等待處理事件的回調(diào)函數(shù)。這種方式讓程序盡可能的得以執(zhí)行而不需要用到額外的線(xiàn)程。當(dāng)無(wú)IO操作時(shí)每個(gè)任務(wù)占用cpu的時(shí)間又比較少,進(jìn)程就會(huì)處于空閑狀態(tài)。同等并發(fā)量情況下,事件驅(qū)動(dòng)占用的系統(tǒng)資源會(huì)更好,負(fù)載足夠大時(shí),事件驅(qū)動(dòng)程序可以將cpu利用到100%。事件驅(qū)動(dòng)型程序比多線(xiàn)程程序更容易推斷出行為,因?yàn)槌绦騿T不需要關(guān)心線(xiàn)程安全問(wèn)題。
??事件驅(qū)動(dòng)的一個(gè)非常有代表性的實(shí)現(xiàn)Node.js和redis,都是一個(gè)單進(jìn)程(單線(xiàn)程)的服務(wù)(redis的數(shù)據(jù)落地或主從同步線(xiàn)程排除,其服務(wù)就是單線(xiàn)程的),事件處理都通過(guò)異步回調(diào)執(zhí)行。第二節(jié)中單線(xiàn)程、多線(xiàn)程、事件驅(qū)動(dòng)編程模型等類(lèi)似比較中看起來(lái)事件驅(qū)動(dòng)是單線(xiàn)程的,Node.js這一典型的事件驅(qū)動(dòng)服務(wù)也是單線(xiàn)程的,導(dǎo)致許多人以為事件驅(qū)動(dòng)只能是單線(xiàn)程的,不能充分利用多CPU多核資源。其實(shí)不然,Nginx也是一個(gè)典型的事件驅(qū)動(dòng)服務(wù),而Nginx是多進(jìn)程的。從邏輯上劃分后端服務(wù),Nginx歸為接入通信層(openresty這種nginx+lua實(shí)現(xiàn)業(yè)務(wù)邏輯的不在討論范圍),Node.js歸為業(yè)務(wù)邏輯層。接入通信層的特點(diǎn)都是IO行為幾乎不大消耗CPU是天然適合事件驅(qū)動(dòng)的,也比較容易實(shí)現(xiàn),而業(yè)務(wù)邏輯層的特點(diǎn)決定了事件驅(qū)動(dòng)方式實(shí)現(xiàn)非常復(fù)雜,但這并意味著業(yè)務(wù)邏輯層的多線(xiàn)程事件驅(qū)動(dòng)難以實(shí)現(xiàn)。
??Nebula就是一個(gè)多進(jìn)程事件驅(qū)動(dòng)服務(wù)的典型。事件驅(qū)動(dòng)的每一個(gè)進(jìn)程都足夠高效,多個(gè)進(jìn)程(多線(xiàn)程)又充分利用多CPU多核資源。Nebula的進(jìn)程模型與Nginx相似,區(qū)別在于Nginx是各worker互斥鎖上鎖accept,而Nebula是由master進(jìn)程accept后將連接對(duì)應(yīng)的文件描述符傳送給worker進(jìn)程(跟Memcached相似)。Nebula是從滿(mǎn)足即時(shí)通訊應(yīng)用而開(kāi)發(fā)的Starship框架發(fā)展而來(lái)的,與nginx的進(jìn)程(線(xiàn)程)模型存在相似純屬偶然。為什么Nebula選擇傳送文件描述符而不是各worker進(jìn)程搶accept?跟Nebula定位有關(guān)系,Nebula不僅需要做接入通信層、數(shù)據(jù)代理層,更要做業(yè)務(wù)邏輯層,分布式服務(wù)的各層服務(wù)都可以且應(yīng)該用Nebula實(shí)現(xiàn),這意味著每一個(gè)worker進(jìn)程接近于分布式服務(wù)的一個(gè)節(jié)點(diǎn)的功能,如果是worker搶占式accept就無(wú)法做定向路由。為什么選擇多進(jìn)程而不是多線(xiàn)程?先看看多進(jìn)程與多線(xiàn)程的優(yōu)缺點(diǎn)比較:
??多進(jìn)程:
??多線(xiàn)程:
??多進(jìn)程的前三點(diǎn)都是優(yōu)點(diǎn),第四點(diǎn)是缺點(diǎn)。Nebula選擇多進(jìn)程就不需要考慮鎖和同步資源問(wèn)題,數(shù)據(jù)和錯(cuò)誤隔離,worker進(jìn)程崩潰不會(huì)影響整個(gè)節(jié)點(diǎn)服務(wù),會(huì)被master進(jìn)程迅速拉起。第四點(diǎn)缺點(diǎn)在Nebula不需要考慮,因?yàn)镹ebula事件驅(qū)動(dòng)的進(jìn)程之間是不需要切換的,可以近似地認(rèn)為每個(gè)worker進(jìn)程都是一個(gè)節(jié)點(diǎn),節(jié)點(diǎn)與節(jié)點(diǎn)之間只有網(wǎng)絡(luò)通信,不需要共享資源更不需要做切換。
??對(duì)于IO密集型的業(yè)務(wù),事件驅(qū)動(dòng)比多線(xiàn)程同步的并發(fā)能力要高很多,可以說(shuō)不是一個(gè)數(shù)量級(jí)的。而大部分互聯(lián)網(wǎng)業(yè)務(wù)都屬于IO密集型業(yè)務(wù),因此事件驅(qū)動(dòng)的適用場(chǎng)景非常廣泛。程序中有許多高度獨(dú)立的任務(wù),在等待事件到來(lái)時(shí),某些任務(wù)會(huì)阻塞,單個(gè)任務(wù)需要占用較少CPU資源。
??Nebula 適用于即時(shí)通訊、數(shù)據(jù)采集、實(shí)時(shí)計(jì)算、消息推送等應(yīng)用場(chǎng)景,也適用于web后臺(tái)服務(wù)。Nebula已有即時(shí)通訊、埋點(diǎn)數(shù)據(jù)采集及實(shí)時(shí)分析的生產(chǎn)應(yīng)用案例,很快將有一個(gè)面向億級(jí)用戶(hù)的推薦引擎生產(chǎn)應(yīng)用案例。
??說(shuō)到推薦系統(tǒng),首先被想到的可能是基于內(nèi)容、協(xié)同過(guò)濾、基于人口統(tǒng)計(jì)學(xué)、基于知識(shí)、基于社區(qū)、混合推薦等推薦技術(shù)。推薦技術(shù)的實(shí)施通?;趆adoop,用hive、spark、storm、flink等來(lái)實(shí)現(xiàn)。這些通常被稱(chēng)為推薦的數(shù)據(jù)挖掘部分。
??推薦引擎是推薦系統(tǒng)核心之一,負(fù)責(zé)將數(shù)據(jù)挖掘的結(jié)果按一定排序推送給用戶(hù),這就是推薦引擎的主要功能。
??已知業(yè)界推薦引擎有使用C++開(kāi)發(fā)也有使用Java開(kāi)發(fā),C++開(kāi)發(fā)占大多數(shù)。在Bwar了解到的C++開(kāi)發(fā)的推薦引擎中多使用rpc框架,使用thrift的4個(gè),使用brpc的2個(gè),使用grpc的1個(gè),使用tars的1個(gè)。因這些開(kāi)源rpc框架不是專(zhuān)為推薦引擎所開(kāi)發(fā)的框架,開(kāi)發(fā)人員通常會(huì)在這些框架之上再架設(shè)一層框架,然后才是業(yè)務(wù)邏輯開(kāi)發(fā)。Bwar接觸的一個(gè)推薦引擎就是基于brpc再開(kāi)發(fā)了自己的框架然后才做業(yè)務(wù)邏輯開(kāi)發(fā),其開(kāi)發(fā)難度比較大,且不容易擴(kuò)展。也許是開(kāi)發(fā)人員對(duì)這些開(kāi)源rpc框架理解不夠深入,導(dǎo)致業(yè)務(wù)邏輯開(kāi)發(fā)比較復(fù)雜,對(duì)后續(xù)需求擴(kuò)展不易。
??Nebula是Bwar開(kāi)發(fā)的C++網(wǎng)絡(luò)框架,生而為分布式服務(wù),經(jīng)過(guò)兩個(gè)生產(chǎn)環(huán)境的應(yīng)用。Nebula不是rpc框架而是一個(gè)基proactor(框架層實(shí)現(xiàn)proactor而非操作系統(tǒng)支持)事件驅(qū)動(dòng)(回調(diào))的框架。并不像大多數(shù)異步事件回調(diào)框架那樣開(kāi)發(fā)者需要自己注冊(cè)回調(diào)函數(shù),Nebula同時(shí)也是個(gè)IoC框架,通過(guò)actor類(lèi)的巧妙設(shè)計(jì)實(shí)現(xiàn)降低了異步編程的復(fù)雜度,開(kāi)發(fā)者真正意義上只需聚焦業(yè)務(wù)邏輯開(kāi)發(fā)。
??Nebula框架提供的Cmd類(lèi)非常適合推薦服務(wù)的邏輯入口,支持動(dòng)態(tài)加載,隨時(shí)不停機(jī)升級(jí)推薦算法推薦模型。Step類(lèi)異步獲取redis等存儲(chǔ)中的數(shù)據(jù),無(wú)阻塞等待讓cpu資源只用于推薦邏輯。session類(lèi)用于緩存用戶(hù)、item、模型等數(shù)據(jù)。所有的數(shù)據(jù)獲取、傳遞均可通過(guò)session智能指針十分方便而高效地得到。
??在那些基于rpc框架的推薦引擎中,許多開(kāi)發(fā)人員提到了反射功能,并且通過(guò)大量宏以很費(fèi)勁很難理解的方式實(shí)現(xiàn)了所謂的反射功能。這些都不是IoC框架,Bwar不理解為什么需要實(shí)現(xiàn)反射功能,如果用Nebula來(lái)做將是非常簡(jiǎn)單的事,Nebula是IoC框架,所有的actor實(shí)例創(chuàng)建都是通過(guò)反射創(chuàng)建的,無(wú)須開(kāi)發(fā)者做業(yè)務(wù)邏輯之外的任何事情。Nebula的反射實(shí)現(xiàn)很優(yōu)雅,如果感興趣,可以參考這篇文章《C++反射機(jī)制:可變參數(shù)模板實(shí)現(xiàn)C++反射》。
??開(kāi)發(fā)Nebula框架目的是致力于提供一種基于C++快速構(gòu)建高性能的分布式服務(wù)。如果覺(jué)得本文對(duì)你有用,別忘了到Nebula的 Github 或 碼云 給個(gè)star,謝謝。
參考資料:
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。