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