Dubbo的微核心機制

餓了麼物流技術團隊發表於2018-09-11

作者:劉昊旻 / 伯昊 / ludvik / haomin_liu(at)hotmail.com


摘要

最近一直在思考蜂鳥物流系統中臺化能否引入微核心機制。作為思考作業,首先把dubbo的微核心設計進行了總結沉澱。希望也對大家有用。

本文藉由Dubbo採用微核心設計的緣由作為引子,簡單地探討了微核心架構Microkernel Architecture)設計實踐的思想。本文適合對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定義上千,如何治理?
  • 如何做到複雜業務流程配置所見即所得?
  • 中臺系統修改之後,如何做到對現有業務系統不影響?

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




餓了麼蜂鳥物流招聘啦!

蜂鳥物流現急招資深Java工程師!

在這裡,你將能夠深度參與行業領先的即時配送核心系統開發工作、瞭解餓了麼微服務架構,更能夠在日常開發工作中深度踐行餓了麼多活的核心技術

歡迎有意象的同學踴躍投遞簡歷! 簡歷投遞郵箱:wei.chensh@ele.me

崗位描述

職責

1. 負責物流業務系統相關的需求分析、程式碼開發、程式碼審查工作
2. 配合架構師、技術Leader確保業務系統技術產出質量,對系統可用性進行設計,程式碼質量進行把控,確保系統穩定性等
複製程式碼

崗位要求

1. 本科及以上學歷(985/211優先),紮實的計算機基礎
2. 有過複雜、高併發交易系統的架構設計和優化經驗,尤其是深度參與過網際網路業務架構設計的優先,擁有和工作年限相稱的廣度和(或)深度
3. 3年及以上工作經驗,長期使用JAVA及開源框架進行專案開發,並有一定得專案管理經驗;深入使用Java,熟悉掌握常用的Java類庫及框架,如多執行緒、併發處理、I/O與網路通訊,Spring、Mybatis等;有系統排障經驗,可以快速排查定位問題
4. 至少對高併發、分散式、快取、jvm 調優、序列化、微服務等一個或多個領域有過研究,並且有相關實踐經驗
5. 熟悉 MySQL 應用開發,熟悉資料庫原理和常用效能優化技術,以及 NoSQL,Queue 的原理、使用場景以及限制。
6. 學習能力強,認真負責,對技術有熱情有渴望
7. 具備良好的分析解決問題能力,能獨立承擔任務
8. 具有良好的溝通、團隊協作、計劃和主動性思考的能力,在網際網路或業界有一定影響力公司的工作經驗者優先
複製程式碼




閱讀部落格還不過癮?

歡迎大家掃二維碼通過新增群助手,加入交流群,討論和部落格有關的技術問題,還可以和博主有更多互動

Dubbo的微核心機制

部落格轉載、線下活動及合作等問題請郵件至 shadowfly_zyl@hotmail.com 進行溝通

相關文章