從分層架構到微服務架構(四)之微內核架構

前言

微內核架構(Microkernel Architecture),也被稱爲插件式架構(plug-in architecture),作爲一個在幾十年前就被創建出來的架構模式,它如今仍然被廣泛應用在各個領域中。比如在 Web 瀏覽器領域,谷歌的 Chrome 瀏覽器之所以被認爲功能強大,一個很重要的原因是它有着豐富的插件類型;在開發工具領域,微軟的 VS Code 初始安裝後還只是個簡單的文本編輯器,但用戶可以安裝各種插件,從而讓它搖身一變成爲功能強大的 IDE。

Chrome 和 VS Code 都是微內核架構的典型應用例子,它們提供一個具備最基礎能力的核心繫統,並定義好插件的開發接口。至於需要開發或安裝哪種類型的插件,則完全由普通開發者和用戶決定,這樣的設計讓系統具備了極強的可定製化和可擴展能力

架構視圖

微內核架構由以下兩部分組成:核心系統(core system)和插件(plug-in component),將應用系統的業務邏輯拆分成核心繫統和插件,能夠提供很好的可擴展性和靈活性,極大地方便了後續需求的新增和修改

微內核架構架構視圖

核心系統

核心系統通常只需提供能夠支撐整個系統正常運行的基本功能,比如前文所舉的 VS Code 例子,用戶初始安裝的是 VS Code 的核心繫統,它只是一個提供了打開文件、編輯文件內容和保存文件等基本功能的文本編輯器,其他的擴展功能(如語法檢查)都是通過安裝插件集成的。將複雜的業務邏輯從核心系統中剝離出來,並通過插件實現,能夠提升系統的可擴展性和可維護性。同時,因爲複雜的功能都成了互不干擾的插件,系統的可測性也得到了提高。

考慮現在需要實現一個電子設備回收系統,在回收之前,每種型號的手機設備的回收流程都不一樣,那麼我們可以這樣去實現:

public void assessDevice(String deviceID) {
   if (deviceID.equals("iPhone6s")) {
      assessiPhone6s();
   } else if (deviceID.equals("iPad1"))
      assessiPad1();
   } else if (deviceID.equals("Galaxy5"))
      assessGalaxy5();
   } else ...
      ...
   }
}

如果我們把assessDevice看成是核心系統,那麼後面每次新增一個型號的手機,都需要新增一個if分支,也即對核心系統進行了改動。這樣的設計會導致核心系統非常地脆弱,正所謂改的越多,出問題的概率也越大

比起這種將所有的可定製業務邏輯放在覈心繫統上的設計,更好的應該是將它們實現爲插件的形式,這樣不僅每個設備回收邏輯都解耦了,還提供了強大的可擴展性:添加一個新的回收設備類型,只需新增一種插件即可,核心系統無需變動。

public void assessDevice(String deviceID) {
  String plugin = pluginRegistry.get(deviceID);
 DevicePlugin devicePlugin =
  (DevicePlugin)constructor.newInstance();
 DevicePlugin.assess();
}

微內核架構在實現時通常都結合了其他架構模式,這主要體現在覈心繫統的設計上,比如根據具體的業務特點,我們可以將核心系統設計成 technically partitioned 的分層架構,或者是 domain partitioned 的模塊化架構。

核心系統的架構設計

插件

插件就是一些包含了定製化業務邏輯、擴展功能、附加功能的獨立組件,用於擴充核心系統的功能。插件之間是獨立的,插件與核心系統之間則一般是 “點對點” 通信:核心系統通過調用插件提供的接口(比如插件類的方法)使用擴展功能。

插件可以劃分爲編譯時插件和運行時插件兩種類型,前者每次變更都需要重新構建和部署整個系統,但實現較爲簡單;後者則可以在系統運行時進行插件的新增和刪除操作,相對地,實現也較爲複雜。

編譯時插件

在編譯時插件中,插件通常以 package 或 namespace 實現,比如在 package 中可以以這樣的命名規則來區分插件:app.plug-in.<domain>.<context>

編譯時插件實現

運行時插件

運行時插件中插件的實現通常是動態庫的形式,比如.jar.so.dll文件。在上述的設備回收系統的例子中,每種型號的手機設備回收邏輯包含在一個獨立的.jar文件中:

運行時插件實現

遠端插件

當然,插件和核心繫統並非只能通過本地接口調用進行通信,還可以採用 REST / 消息隊列 / RPC 等方式,這種場景下,插件就變成了一個獨立部署的服務。遠程插件具備運行時插件的特點,而且能夠提供更好的 scalability:插件和核心繫統甚至都不必使用相同的技術棧實現,只需遵守既定的 REST 接口即可

遠端插件

爲了提升系統處理請求的 responsiveness,我們還可以將核心系統調用插件的過程實現爲異步通信。以前文的電子設備回收系統爲例,在異步通信的架構下,系統通過一個線程觸發插件啓動對某個設備的回收流程。之後,該線程無需一直等待回收結束,它可以去繼續回收別的設備。當設備回收結束後,插件會通過異步隊列告知核心系統。這樣的異步設計可以減少無謂的等待流程,明顯改善系統的 responsiveness。

如果涉及到讀寫數據庫,爲了能夠維持插件的獨立性,每個插件最好能夠擁有獨立的數據庫。如果插件間有着無可避免的數據交互,則可以爲核心系統配置一箇中心數據庫,並通過它來進行數據中轉。

插件的的獨立數據庫

插件中心

核心系統在加載插件前,必須得知道_當前有哪些可用的插件_,以及_這些插件在哪裏可以獲取_。這要求系統有一個地方去管理插件,這就是**插件中心**(plug-in registry)的功能。插件中心類似於服務化架構中服務註冊中心的作用,它保存了所有插件的基本信息,包括名稱、數據契約、通信協議、加載地址等。

我們可以簡單地將插件中心實現爲一個本地的map表,其中 key 可以是插件名稱,value 爲獲取插件的地址:

Map<String, String> registry = new HashMap<String, String>();
static {
  //point-to-point access example
  registry.put("iPhone6s""Iphone6sPlugin");

  //messaging example
  registry.put("iPhone6s""iphone6s.queue");

  //restful example
  registry.put("iPhone6s""https://atlas:443/assess/iphone6s");
}

爲了實現一些較爲複雜的功能,如插件上下線通知等,我們還可以藉助 Apache ZooKeeper、ETCD 這類的分佈式協同系統實現遠程插件中心

通信契約

通信契約定義了插件與核心系統之間的通信方式、交互行爲和數據格式。通信方式可以是本地接口調用、REST、RPC、消息隊列等;交互行爲則可以理解爲插件對核心系統提供的接口,比如本地的函數 / 方法、REST 的 URI 等;對本地插件而言,數據格式通常是一個類 / 結構體,對遠程插件而言,常用的數據格式有 JSON、XML、ProtoBuf 等。

考慮電子設備回收系統的例子,系統有着如下定義的通信契約:

public interface AssessmentPlugin {
  // 回收設備流程
 public AssessmentOutput assess();
  // 將該插件註冊到插件中心
 public String register();
  // 從插件中心去註冊
 public String deregister();
}

public class AssessmentOutput {
  // 回收報告,僅僅用於展示結構給用戶看,核心系統無需瞭解該格式
 public String assessmentReport;
  // 用於標識該設備是否可以在二手市場上重新售賣
 public Boolean resell;
  // 表示該設備的價值
 public Double value;
  // 表示推薦的售賣價格
 public Double resellPrice;
}

從該契約定義中可以看出,通信方式爲本地接口調用(AssessmentPlugin接口);它有着 3 個交互行爲,assess()爲回收設備流程、register()表示將該插件註冊到插件中心、deregister表示去註冊;數據格式則是AssessmentOutput類,它定義了回收流程的結果。

架構評分

微內核架構的架構評分

和之前介紹的分層架構、管道架構一樣,微內核架構同樣屬於單體架構,因此 Simplicity 和 Overall cost 是該架構模式主要優勢;而 Elasticity、Fault tolerance 和 Scalability 是主要劣勢。

另外,微內核架構的 Testability、Deployability、Reliability、Modularity 之所以能夠取得 3 顆星,得益於不同的功能能夠被拆分至獨立的插件上,特別地,運行時插件的增刪無需重新部署系統。這使得系統能夠快速響應需求變更,具備很高的擴展性。比如對於前面的電子設備回收系統,如果需要新增一種新的電子設備回收流程,只需新增一個插件即可;如果某種設備不再需要回收,則去除對應插件即可。

微內核架構比較特別的一點是,它既可以是 technically partitioned,也可以是 domain partitioned,這取決於核心系統的實現方式,前文也有介紹。

總結

Robert C.Martin 曾經說過,軟件開發技術發展的歷史就是一個如何想方設法方便地增加插件,從而構建一個可擴展、可維護的系統架構的故事。在敏捷開發的潮流之下,需求的變更如同家常便飯,系統不應該因爲某一部分發生變更從而導致其他不相關的部分出現問題。將系統設計爲微內核架構,就等於構建起了一面變更無法逾越的防火牆,插件發生的變更就不會影響系統的核心業務邏輯。

微內核架構的設計思想,能夠極大提升系統的可擴展性和健壯性,在其他的一些軟件方法論裏,我們也隱約能看到它的影子。比如在領域驅動設計中,領域層就相當於核心系統,它定義了系統的核心業務邏輯;基礎設施層則相當於插件,切換不同的基礎設施並不會影響系統的業務邏輯,這得益於基礎設施層依賴倒置的設計原則。

當然,作爲微內核架構也有着一些缺點,它天然具備了單體架構的一些劣勢,比如核心系統作爲架構的中心節點並不具備 Fault tolerance 能力。因此,該架構模式往往被廣泛應用於一些着重提供很強的用戶定製化功能的小型產品,如 VS Code 等,它們對系統的 Elasticity、Fault tolerance 和 Scalability 並沒有很高的要求。

每種架構模式都有其合適的應用場景,只有熟悉常用的幾種架構模式,才能設計出更好的軟件系統。下一篇文章,我們將繼續介紹面向服務的架構


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4vofgxx-vasf-957Y_dcww