1. 程式人生 > >Dubbo的微核心機制

Dubbo的微核心機制

Wikipedia上微核心(Microkernel)的定義

In computer science, a microkernel (also known as μ-kernel) is the near-minimum amount of software that can provide the mechanisms needed to implement an operating system (OS). These mechanisms include low-level address space management, thread management, and inter-process communication (IPC)

. If the hardware provides multiple rings or CPU modes, the microkernel may be the only software executing at the most privileged level, which is generally referred to as supervisor or kernel mode. Traditional operating system functions, such as device drivers, protocol stacks and file systems, are typically removed from the microkernel itself and are instead run in user space.

Monolithic vs. Microkernel

wikipedia上的定義特指這是一種作業系統核心設計風格,其對標的核心設計風格是monolithic kernel。

本文提到的微核心

本文講到的微核心架構(Microkernel architecture)更寬泛一些,不侷限於作業系統核心設計問題域,是一種設計範型design paradigm),更接近於《Software Architecture Patterns》一書中所寫:

The microkernel architecture pattern allows you to add additional application features as plug-ins to the core application, providing extensibility as well as feature separation and isolation. The microkernel architecture pattern consists of two

types of architecture components: a core system and plug-in modules. Application logic is divided between independent plug-in modules and the basic core system, providing extensibility, flexibility, and isolation of application features and custom processing logic.

Microkernel architecture pattern

微核心架構由兩大架構模組組成:核心系統外掛模組。設計一個微核心體系關鍵工作全部集中於核心系統怎麼構建。

所有的軟體存在的目的都是為了去解決某個現實世界中具體領域的問題,簡稱問題域。比如dubbo的問題域是服務化與服務治理、maven的問題域是編譯打包與軟體專案管理

如果某個問題域發現有如下特徵,就可以考慮使用微核心設計思想:

  1. 問題域能夠沉澱一層比較核心的概念、流程或功能,這些元素可以被穩定維護在一個核心之中;
    • Maven將程式碼編譯打包場景定義為三套生命週期:cleandefaultsite;其核心的default生命週期中20多個編譯步驟將問題域進行了高度抽象;Maven的plugin(又名mojo)在定義時都需要將自己掛載到某個goal和step上;
    • Dubbo將SOA呼叫高度抽象為20餘個核心SPI,這些SPI又類似協議棧分層的設計細分為核心的7、8個層次(proxyclusterprotocol等);dubbo將自己主要的SOA服務呼叫功能實現都定義為這些SPI的具體擴充套件實現(plugin);有了這些抽象的SPI,plugin也就有了依附的基礎;
  2. 問題域有開放封閉的迫切需求,其中封閉的部分、可擴充套件部分分別由不同的團隊、工程來維護與組織。比如:
    • Framework的實現封閉 vs. 依賴Framework的應用擴充套件開放,比如dubbo這樣的中介軟體設計場景;
    • 作業系統核心實現封閉 vs. 作業系統應用層開放,比如所有微核心作業系統設計場景;
    • 平臺級業務系統核心邏輯實現封閉 vs. 具體業務系統擴充套件開放,比如阿里中臺核心平臺系統的設計場景;

上面的兩個問題域特徵,剛好帶出了在進行微核心架構核心系統設計時的兩個關鍵點:

  1. 對問題域的核心概念、流程、功能的洞察與抽象;有了這些核心元素,plugin的擴展才能有所依附、與其程式碼之間的互動才能實際落地發生;
  2. 設計一套機制用於規範和管理plugin生命週期:定義載入銷燬等;

Dubbo架構概要介紹

Dubbo主要解決了服務化架構中的幾個關鍵問題:

  1. 遠端呼叫(RPC)
    • 解決遠端的程序到程序的呼叫;
  2. 叢集邏輯(cluster invoke cluster)
    • 更進一步,解決服務叢集到服務叢集的呼叫問題,例如軟負載機制;
  3. 服務發現與服務治理(Registry / Governance)
    • 集中解決服務治理所需要的基礎功能:服務的註冊與發現、註冊中心裡的基礎服務治理功能

採用了類似協議棧的分層設計,歸納下來主要分為三層:

  1. Service層 (Service/Config/Proxy)
    • 解決provider側服務暴露 / consumer側消費服務的問題
  2. 叢集層(Registry / Cluster)
    • 解決服務註冊與發現、叢集呼叫策略領域關鍵問題
  3. RPC層(Protocol / Exchange/ Transport / Serialize)
    • 解決點到點的同步遠端呼叫領域的問題

這主要的三層符合分層架構風格的特徵,即: 上層邏輯無需關注下層實現細節。 這個特徵使得dubbo的擴充套件方可以採用類似搭積木的方式進行擴充套件。比如:徹底更換RPC協議,而共享上層的叢集呼叫與服務治理實現; 更多的奧妙就不在此文展開,感興趣的同學可以仔細研究下圖(摘自Dubbo官方文件),內涵與細節非常豐富:

Dubbo分層架構

Dubbo與微核心架構

Dubbo為什麼會使用微核心架構?最直觀的原因:為了推廣方便

Dubbo在設計之初,正值Alibaba B2B進行服務化轉型的關鍵時期。所要推廣的應用系統要麼還處於“恐龍級單體”應用狀態;要麼用“土辦法”解決簡單的叢集間呼叫。

想要順利推廣,得具備這幾個關鍵特徵:

  1. 效能好
    • 在一次服務呼叫中,框架所佔用的資源和時間要縮小到對應用層基本可以忽略的程度;
  2. 魯棒性好
    • 各種設計細節都需要兜底和防呆,避免因為一些次要的原因,導致整個應用系統崩潰(最經典的案例就是因為註冊中心bug導致服務的提供者被全部剔除惡性事件了,其中各種心酸,具體的心得可以另開一篇文章專門探討);
  3. 引入的依賴少
    • 最小化對業務程式碼的侵入:可以做到應用容器無關(不同的web容器、homemade應用容器都不影響使用)、框架無關(不強依賴Spring)
  4. 可擴充套件性好(能做到開放封閉)
    • 依賴於自己抽象的SPI以及plugin載入機制,能做到應用方非常自由地通過SPI方式插入自己想要的邏輯,而不需要修改Dubbo本身

最後這一項,就是Dubbo採用微核心設計的主要原因: dubbo所要支援的應用系統千差萬別,在一個組織中推行服務化,dubbo需要面臨諸多的擴充套件需求,舉幾個場景:

  • 場景一:

    • 遺留系統是Python寫的單體應用,想要用Java來進行領域拆分改造,有一些RPC呼叫的場景,老系統採用Rest + VIP的方式進行遠端呼叫;
  • 場景二:

    • 使用者想要實現自己的分散式呼叫跟蹤,在這個基礎之上建立自己的運維工具體系。
  • 場景三:

    • 希望在SOA的呼叫鏈條上插入自己的filter邏輯,去實現呼叫審計的需求;

還有很多不一一列舉。作為一個開源框架,嘗試將所有上面的需求都不加區分的在框架內實現一定是不可取的,眾口難調。

如何做到幹好自己的活兒,又不擋使用者的道兒呢?

答案不言自明。

Dubbo的擴充套件點實現舉例

以擴充套件實現Filter SPI為例。

Filter SPI 定義:

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.extension.SPI;

/**
 * Filter. (SPI, Singleton, ThreadSafe)
 */
@SPI
public interface Filter {

    /**
     * do invoke filter.
     * <p>
     * <code>
     * // before filter
     * Result result = invoker.invoke(invocation);
     * // after filter
     * return result;
     * </code>
     *
     * @param invoker    service
     * @param invocation invocation.
     * @return invoke result.
     * @throws RpcException
     * @see org.apache.dubbo.rpc.Invoker#invoke(Invocation)
     */
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}
複製程式碼

filter是鏈式組裝的,要實現自己的filter邏輯,只需要實現下面的invoke介面即可。filter的組裝需要注意官方文件中記錄的約定:

  • 使用者自定義 filter 預設在內建 filter 之後。
  • 特殊值 default,表示預設擴充套件點插入的位置。比如:filter="xxx,default,yyy",表示 xxx 在預設 filter 之前,yyy 在預設 filter 之後。
  • 特殊符號 -,表示剔除。比如:filter="-foo1",剔除新增預設擴充套件點 foo1。比如:filter="-default",剔除新增所有預設擴充套件點。
  • provider 和 service 同時配置的 filter 時,累加所有 filter,而不是覆蓋。比如:<dubbo:provider filter="xxx,yyy"/> 和 <dubbo:service filter="aaa,bbb" />,則 xxx,yyy,aaa,bbb 均會生效。如果要覆蓋,需配置:<dubbo:service filter="-xxx,-yyy,aaa,bbb" />

擴充套件配置

<!-- 消費方呼叫過程攔截 -->
<dubbo:reference filter="xxx,yyy" />
<!-- 消費方呼叫過程預設攔截器,將攔截所有reference -->
<dubbo:consumer filter="xxx,yyy"/>
<!-- 提供方呼叫過程攔截 -->
<dubbo:service filter="xxx,yyy" />
<!-- 提供方呼叫過程預設攔截器,將攔截所有service -->
<dubbo:provider filter="xxx,yyy"/>
複製程式碼

XxxFilter.Java

package com.xxx;
 
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;
 
public class XxxFilter implements Filter {
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // before filter ...
        Result result = invoker.invoke(invocation);
        // after filter ...
        return result;
    }
}
複製程式碼

擴充套件實現Jar包Maven 專案結構:

src
 |-main
    |-java
        |-com
            |-xxx
                |-XxxFilter.java (實現Filter介面)
    |-resources
        |-META-INF
            |-dubbo
                |-com.alibaba.dubbo.rpc.Filter (純文字檔案,內容為:xxx=com.xxx.XxxFilter)
複製程式碼

META-INF/dubbo/com.alibaba.dubbo.rpc.Filter:

xxx=com.xxx.XxxFilter
複製程式碼

xxx就是com.xxx.XxxFilter全限定名的別名了,它會出現在dubbo的provider或者consumer的配置檔案中,dubbo會按需載入組裝。

按照這樣的方式定義其他的擴充套件點,以此類推,執行時dubbo會把自帶的、以及應用自己擴充套件的實現全部載入進來,如下圖所示(假設該應用還擴充套件了LoadBalance以及Protocol另外兩個擴充套件點):

dubbo-microkernel-example

Dubbo擴充套件點載入機制實現

擴充套件點載入

擷取自dubbo官方文件《開發者指南-擴充套件點載入》, Dubbo的擴充套件點載入由JDK 標準的 SPI (Service Provider Interface) 擴充套件點發現機制加強而來,主要針對這三個缺點:

  • JDK 標準的 SPI 會一次性例項化擴充套件點所有實現,如果有擴充套件實現初始化很耗時,但如果沒用上也載入,會很浪費資源。
  • 如果擴充套件點載入失敗,連擴充套件點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine,通過 getName() 獲取指令碼型別的名稱,但如果 RubyScriptEngine 因為所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類載入失敗,這個失敗原因被吃掉了,和 ruby 對應不起來,當用戶執行 ruby 指令碼時,會報不支援 ruby,而不是真正失敗的原因。
  • 增加了對擴充套件點 IoC 和 AOP 的支援,一個擴充套件點可以直接 setter 注入其它擴充套件點。

擴充套件點載入原始碼位於dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java

擴充套件點裝配

ServiceConfig.export()ReferenceConfig.refer()是dubbo bootstrap時組裝執行時擴充套件點的關鍵入口,可以根據程式碼順藤摸瓜。看程式碼時可能會被各種xxxConfig搞暈,可以參考dubbo官方文件《開發者指南-實現細節

寫在後面

對於Dubbo來講,本文中描述的微核心機制足夠使用了。但對於上文中提到過的中臺業務系統而言,僅僅依靠核心系統+plugin載入機制又遠遠不夠。中臺業務系統面臨的是更為嚴苛的工程挑戰:

  • 當擴充套件點SPI定義上千,如何治理?
  • 如何做到複雜業務流程配置所見即所得?
  • 中臺系統修改之後,如何做到對現有業務系統不影響?

這些問題,阿里中臺的星環系統都給出了自己答案,不得不佩服阿里中臺戰略堅定推進的勇氣以及其實現者的智慧。

進群:可以領取免費的架構師學習資料。

進群:瞭解最新的學習動態

進群:瞭解最新的阿里,京東招聘資訊

進群:獲取更多的面試資料

1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加群。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加群。

3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加群。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加群。

5. 群號:835638062 點選連結加入群:https://jq.qq.com/?_wv=1027&k=5S3kL3v 6.阿里Java高階大牛直播講解知識點,分享知識,上面五大專題都是各位老師多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!