什麼是微內核架構設計?

關於微內核架構設計現在比較熱,聽起來好像是操作系統內核相關的,作爲Java程序員,操作系統內核那麼遙遠的事情,好像和我們沒有什麼關係。但是如果我說微內核其實就是插件化(Plug-in)架構,你一定會一臉疑惑,“你居然向Java程序員解釋什麼是插件化架構?我每天都在用啊,Eclipse、IntelliJ IDEA、OSGi、Spring Plugin、SPI等,哪個不是插件化架構。我的一些項目也是採用插件化設計的,如使用插件實現流程控制定製等等”。但是彆着急,即便是我們每天都在使用的技術,而且大多數人也都知道,如果我們能將其闡述得更清楚,並且能從中發現一些問題,做出一些優化有助於以後的架構設計,那麼大多數人在日常的設計和開發中都能受益,豈不是更好。現在我們就來聊一聊微內核架構設計。

一 微內核設計之操作系統內核

微內核設計其實就是插件體系。我們都知道,操作系統內核誕生得比較早,所以插件化最早被用在內核設計上,於是就有了微內核設計這一稱呼。

微內核是這樣一種內核:它只完成內核不得不完成的功能,包括時鐘中斷、進程創建與銷燬、進程調度、進程間通信,而其他的諸如文件系統、內存管理、設備驅動等都被作爲系統進程放到了用戶態空間。說白了,微內核是相對於宏內核而言的,像Linux就是典型的宏內核,它除了時鐘中斷、進程創建與銷燬、進程調度、進程間通信外,其他的文件系統、內存管理、輸入輸出、設備驅動管理都需要內核完成。

也就是說,微內核是相對宏內核而言的,宏內核是一個包含非常多功能的底層程序,也就是我們現在講的Monolith。它乾的事情非常多,而且不是可插拔的,修改一些小的功能,都會涉及到整個程序的重新編譯等,比如一個功能出現了一個小bug,可能導致整個內核都出問題。這也是很多人將Linux稱爲monolithic OS的原因。而微內核只負責最核心的功能,其他功能都是通過用戶態獨立進程以插件方式加入進來,然後微內核負責進程的管理、調度和進程之間通訊,從而完成整個內核需要的功能。基本一個功能出現問題,但是該功能是以獨立進程方式存在的,不會對其他進程有什麼影響從而導致內核不可用,最多就是內核某一功能現在不可用而已。

微內核就是一個運行在最高級別的程序片段,它能完成用戶態程序不能完成的一些功能。微內核通過進程間通信來協調各個系統進程間的合作,這就需要系統調用,而系統調用需要切換堆棧以及保護進程現場,比較耗費時間;而宏內核則是通過簡單的函數調用來完成各個模塊之間的合作,所以理論上宏內核效率要比微內核高。這個和微服務的架構設計一樣,我們將Monolith應用劃分爲多個小應用後,系統的設計就變得比較複雜了,之前都是應用內部函數調用,現在要涉及網絡通訊、超時等問題,同時響應時間會被拉長。

聊到這裏,相信大家對微內核和宏內核已經有了一個大致的瞭解,看起來各有千秋。但是宏內核有一個最大的問題就是定製和維護陳本。現在的移動設備和IoT設備越來越多,如果要把一個龐大複雜的內核適配到某一設備上,是一件非常複雜的事情,如果很簡單的話,那麼把Linux內核適配到Android內核,甚至到Tesla等車載系統,基本上人人都可以做了。

因此我們更需要一個微內核的架構設計,方便定製,而且非常小,可以實現功能的熱替換或者在線更新等,這就是微內核被提出來的核心需求。但是微內核有一個運行的效率問題,所以在微內核和宏內核之間,又有了Hybrid內核,主要是想擁有微內核的靈活性,同時在關鍵點上有宏內核的性能。微內核設計在理論上確實有效率問題,但是隨着芯片設計、硬件性能提升等,這方面或許已經有了非常大的提升,已經不再是最關鍵的問題。

總體下來,內核設計有三個形式,如下:

二 插件化(Plug-in)架構設計

上面聊了微內核在操作系統內核設計中的作用,接下來我們就開始討論更通用的插件化架構設計,畢竟這個詞大家都明白。

插件化架構非常簡單,就兩個核心組件:系統核心(Core System)和插件化組件(Plug-in component)。Core System負責管理各種插件,當然Core System也會包含一些重要功能,如插件註冊管理、插件生命週期管理、插件之間的通訊、插件動態替換等。整體結構如下:

插件化架構對微服務架構設計幫助非常大,考慮到隔離性,插件可能是以獨立進程方式運行的,那麼這些進程如果擴展到網絡上,分佈在衆多的服務器上,這個就是微服務架構的原型,所以瞭解微內核的同學都不屑於和你討論微服務架構,相信你也明白了,除了IT傳統的鄙視鏈因素,原理上確實就是這麼回事。

回到微服務架構設計場景,我們將Plug-in component重新命名爲服務(Service),這個和微內核設計中的服務也差不多,這個時候微服務和微內核就差不多了,都涉及到服務註冊、管理和服務之間的通訊等。那我們看一下微內核是如何解決服務之間的通訊問題的?以下摘自維基百科:

因爲所有服務行程都各自在不同地址空間運行,因此在微核心架構下,不能像宏內核一樣直接進行函數調用。在微核心架構下,要創建一個進程間通信機制,通過消息傳遞的機制來讓服務進程間相互交換消息,調用彼此的服務,以及完成同步。採用主從式架構,使得它在分佈式系統中有特別的優勢,因爲遠程系統與本地進程間,可以採用同一套進程間通信機制。

也就是說,採取的是基於消息的進程間通訊機制。消息最簡單,就兩個接口:send和receive,消息發送出去,然後等着收消息,處理後再發消息就可以了,這裏大家應該也知道了,這個是異步的。回到插件化架構設計中,Plug-in組件設計包含交互規範,也就是和外界相互通訊的接口,如果是基於消息通訊的話,就是send和receive接口,可以說是非常簡單的。

但是這裏還有一個問題,那就是進程間通訊。你可能會問,這個有什麼好疑問的,就是兩個進程之間相互發消息唄。但是這裏有一個最大的疑問,那就是進程間通訊是否有第三者介入?如下圖:

當然在操作系統的內核設計中,一定是通過內核進行轉發的,就是我們理解的總線架構,內核負責協調各個進程間的通訊。這個大家也能理解,如果進程A直接發給另外一個進程B,必然要了解對應的內存地址,微內核中的服務是可以被隨時替換的,如果服務不可用或者被替換,這個時候要通知和其通訊的其他進程,是不是太複雜?剛纔已經提到,只有send和receive接口,沒有其他通知下線、服務不可用的接口。在微內核的設計中,一定是通過總線結構,進程向Kernel發送消息,然後kernel再發送給對應的進程,這樣的一個總線設計。實際上很多應用內部在做Plug-in組件解耦時,都會使用EventBus的結構,其實就是總線的設計機制。

爲何婆婆媽媽說這些?因爲非常關鍵。分佈式的進程通訊是微服務的核心,我們理解的服務到服務的通訊,就是服務A啓動監聽端口,服務B會和服務A建立連接,然後兩者通訊即可。這個方式和微內核設計中內核負責消息接收和轉發的總線架構設計是不一樣的。如採用HTTP,HSF等通訊協議時,相當於kernel告知通訊的雙方各自的地址,然後它們之間就可以通訊了。然後就沒有Kernel什麼事情了,也不會用到什麼總線的結構設計,這個就是傳統的服務發現機制。

但是還有一種模式,就是完全透明的插件化通訊機制,如下圖:

Plug-in組件,也就是微服務架構中的服務,是不能直接通訊的,而是需要Core System進行轉發。這樣做的好處和微內核架構一樣,插件相互之間無直接聯繫,彼此之間非常透明,例如服務A下線後,完全不需要通知其他服務;服務A被替換,也不需要通知其他服務;服務A從數據中心1到數據中心2,也不用通知其他服務;即便服務N和服務A之間網絡不互通,兩者之間也能通訊。

這裏有個問題:性能問題。我們都知道,兩點之間,直線段最短。爲何要多繞一下到Core System呢?這就是微內核和宏內核之間的爭論之處,使用函數調用非常快,而進程間的消息通訊則是非常慢的,但是這種通過中介進行通訊機制的好處也是非常明顯的。那麼如何提升這種基於總線的通訊性能呢?當然有,比如選擇高性能的二進制協議,HTTP 1.1這種文本協議就不需要了;採用Zero Copy機制,可以快速進行網絡包轉發;好的網絡硬件,如RDMA;好的協議,如基於UDP的QUIC等。總結下來,和微內核一樣,這種微服務通訊的性能是可以提升的。當然如果實在受不了這種性能,在關鍵場景,你可以採用Hybrid模式,混入一些服務之間直接通訊的設計,但只能在性能極致的場景中使用。

此外,插件化架構中的插件組件是各種各樣的,通訊的機制也各不一樣,一些是RPC的,一些是Pub/Sub的,一些是無需ACK的(如Beacon接口),還有一些是雙向通訊的等等。當然你可以選擇不同的通訊協議,但是這裏有一個問題,就是Core System需要理解這個協議,然後才能進行消息路由。這個時候Core System需要編寫大量的Adapter來解析這些協議,例如Envoy包含各種filter來支持不同的協議,如HTTP、MySQL、ZooKeeper等,但是因此Core System就會變得非常複雜且不穩定。

另外可以選一種通用的協議,Core System只支持這一種協議,各個插件之間都基於該協議通訊,至於服務和其他外部系統如何通訊,如數據庫、github集成等,這些Core System並不關心,那只是Service內部的事情。目前比較通用的協議是gRPC,如K8s內部都會採用該協議,另外Dapr也採用gRPC協議做服務集成,因爲gRPC提供的通訊模型基本可以滿足大多數的通訊場景。當然另外一個就是RSocket,提供更豐富的通訊模型,也適用於Core System這種服務間通訊場景。對比gRPC,RSocket可以運行在各種傳輸層上,如TCP、UDP、WebSocket、RDMA等,相反的,gRPC目前只能運行在HTTP 2之上。

三 服務通訊的延伸

前面說到,最好由插件化架構設計的Core System作爲服務之間消息通訊的路由,如果是這樣的話,就會產生一種Broker模式,當然也有可能是Agent。這裏大家一定會想到Service Mesh,沒錯。當然你可以選擇Agent Sidecar模式,也可以選擇中心化的Broker模式,這兩者的功能都是一樣的,只是處理的方式不一樣而已。Agent基於服務註冊和發現機制,然後找到對方服務的Agent,再進行兩個Agent之間的通訊,只是省掉服務之間的調用的開銷。但是Broker是集中式的,大家都向Broker發送和接收消息,不涉及服務註冊發現機制,不涉及服務元信息推送,就是總線結構。

我現在做的就是基於這種Broker的總線的架構設計,在RSocket Broker中,也是採用微內核架構設計,當然未必做得最好 。RSocket Broker核心就是管理註冊的服務、路由管理、數據採集等,而不會添加過多的功能,和Core System的設計理念一樣,只添加必須的功能。如果你要擴展整個系統更多的功能,如發短信、發郵件、對接雲存儲服務等,需要編寫一個Service ,然後和Broker對接一下,再從broker那裏收消息(receive),處理完畢後再發送(send)給Broker就可以了。總體結構如下:

有不少同學會問,當服務實例的負載太高的時候,Broker如何實現動態擴容呢?Broker會給你提供數據,如一個服務實例QPS,至於是否擴展,你只需要寫一個服務,從Broker上採集數據,分析後,調用K8s API進行擴容即可,Broker並不負載這些業務功能,它只會添加非常必要的功能,這個和Core System設計是一樣的。

回到插件化架構的靈活性上,如果系統中有一個KV存儲的插件,你只要遵循消息格式或者通訊接口,就可以保存KV數據。但是你並不太關心是Redis存儲的,還是Tair存儲的,或者是雲端的KV服務,這就爲服務標準化和可替換性提供了很好的基礎,這對應用上雲或雲原生化幫助非常大,整個系統有非常大的靈活性。

四 總結

其實有非常多的書有關於微內核的介紹,操作系統的圖書就不用說了,另外兩本書也非常不錯,對通用架構設計幫助也非常大,尤其是微服務的場景,我也是參考這兩本書寫這篇文章的。

微內核架構設計對微服務設計有非常好的參考意義,但是微服務有一個非常大的問題就是服務邊界的劃分,對比操作系統,已經發展幾十年,而且非常穩定,功能劃分非常容易。而微服務架構是爲業務服務的,雖然面對的業務可能已經存在上百年,但是軟件化、數字化和流程化並沒有多少年,加上現實業務的複雜性,還有各種妥協,個人認爲微服務架構會更復雜一些。

 

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章