Dubbo服務暴露分析

来源:https://www.cnblogs.com/maratong/archive/2020/02/19/12333658.html
-Advertisement-
Play Games

Dubbo的服務暴露是一個重要的特性,瞭解其機制很重要。之前有很多人寫了有關的源代碼分析,在本文中不再重新分析。官方文檔中的一篇寫的就很好,本文主要是有關內容進行補充與總結。 傳送門: "服務導出" 為什麼要服務暴露 服務暴露分為遠程暴露和本地暴露。在遠程服務暴露中會將服務信息上傳到註冊中心。這時客 ...


Dubbo的服務暴露是一個重要的特性,瞭解其機制很重要。之前有很多人寫了有關的源代碼分析,在本文中不再重新分析。官方文檔中的一篇寫的就很好,本文主要是有關內容進行補充與總結。
傳送門:服務導出

為什麼要服務暴露

服務暴露分為遠程暴露和本地暴露。在遠程服務暴露中會將服務信息上傳到註冊中心。這時客戶端要調用某個服務時會從註冊中心找到該服務的遠程地址等信息。然後客戶端根據這個地址進行遠程調用。服務端接收到遠程調用請求後會最終調用getInvoker()方法進行查找對用的invoker。在getInvoker()方法中會從一個HashMap中進行查找,如果在這個Map中查找不到就會拋出異常。在遠程服務暴露中,會按照規則將實例Invoker存儲在HashMap中,其中Key名包含埠、介面名、介面版本和介面分組。所以進行服務暴露很重要。
本地服務暴露是暴露在JVM中,不需要遠程通信。Dubbo會預設把遠程服務用injvm協議再暴露一份。
為什麼會有本地服務暴露
在Dubbo中,一個服務可以即是provider,又是Consumer,因此就存在它自己調用自己服務的時候,如果再通過網路去訪問,那麼就是捨近求遠,因此有了本地暴露服務這個設計。消費者可以直接消費同一個JVM內部的服務,避免了跨網路進行遠程通信。

服務暴露起點

我們會通過XML或註解的方式來指定要暴露的服務。l例子如下:

<bean id=“xxxService” class=“com.xxx.XxxServiceImpl” /> 
<!-- 增加暴露遠程服務配置 -->
<dubbo:service interface=“com.xxx.XxxService” ref=“xxxService” /> 

這時會創建出一個ServiceBean對象。

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, BeanNameAware,
        ApplicationEventPublisherAware 
public class ServiceConfig<T> extends ServiceConfigBase<T>
public abstract class ServiceConfigBase<T> extends AbstractServiceConfig {
    private static final long serialVersionUID = 3033787999037024738L;
    protected String interfaceName;
    //要暴露服務類的介面類
    protected Class<?> interfaceClass;
    //實現類引用
    protected T ref;
    //服務名 
    protected String path;
    protected ProviderConfig provider;
    protected String providerIds;
    protected volatile String generic;
    protected ServiceMetadata serviceMetadata;

ServiceBean和Spring有關,它繼承了InitializingBean和ApplicationEvent。在Bean初始化完成後會調用InitializingBean.afterPropertiesSet方法來執行服務暴露的準備工作。在Spring的context完成初始化後,會觸發ApplicationEventListener事件進行服務暴露,會執行onApplicationEvent方法。這時服務服務暴露就開始了。

public void onApplicationEvent(ContextRefreshedEvent event) {
    // 是否有延遲導出 && 是否已導出 && 是不是已被取消導出
    if (isDelay() && !isExported() && !isUnexported()) {
        // 導出服務
        export();
    }
}

整體流程

Dubbo真正的服務暴露入口是ServiceConfig#doExport()方法。首先ServiceConfig類拿到對外提供服務的實際類ref,然後通過ProxyFactory類的getInvoker方法使用ref生成一個AbstractProxyInvoker實例,到這一步就完成具體服務到Invoker的轉化。然後就是把Invoker通過具體的協議(比如Bubbo)轉化成Exporter
整體流程
參考了簡書肥朝的一篇文章: dubbo源碼解析-服務暴露原理

  • 調用ServiceConfig中的export方法,對配置進行檢查與更新,例如provider是否為空,註冊中心是否為空,protocols是否為空。然後檢測是否應該暴露,如果不應該暴露,則直接結束;然後檢測是否配置了延遲載入,如果是,則使用定時器來實現延遲載入的目的。
  • 調用expoer方法裡面的doExport()方法。通過exported變數判斷是否暴露,如果為true,直接返回;否則先設置為true,然後執行doExportUrls()方法。
  • 調用doExportUrls()方法,先通過loadRegistries載入註冊中心鏈接,然後遍歷ProtocolConfig集合,它是用戶指定的協議集合(比如Dubbo、REST),在裡面執行doExportUrlsFor1Protocol(protocolConfig, registryURLs)方法。
  • 執行doExportUrlsFor1Protocol(protocolConfig, registryURLs)方法,剛開始是組裝URL。(1)它把metrices、application、module、provider、protocol等所有配置都放入到map中,(2) 通過反射獲取interfaceClass的方法列表,先做簽名校驗,判斷該服務是否有配置的方法存在,然後該方法簽名是否有這個參數存在,都核對成功才將method的配置加入到map中;(3)將範化調用、版本號、method或者methods、token等信息加入到map;(4)獲取服務暴露地址和埠號,利用map內數據組裝成URL。
  • 然後根據url中的scope參數決定服務導出方式。scope=none,不導出服務;scope!=remote,導出到本地;scope!=local,導出到遠程。
  • 執行proxyFactory.getInvoker(ref, (Class)interfaceClass, url)方法,來獲取invoker,Dubbo預設的ProxyFactory實現類是JavasistProxyFacoty方法。

這時,第一大步服務轉化成Invoker已經完成,然後執行第二大步Invoker轉化成Exporter
先介紹本地服務的暴露機制

  • 調用exportLocal(url)。首先判斷URL的協議頭如果等於injvm,說明已經導出到本地了,無需再次導出。如果不是,則創建一個新的URL並將協議頭設置為injvm,並另外設置主機名和埠。然後創建invoker,並調用InjvmProtocalexport()方法暴露服務。
  • InjvmProtocolexport方法僅創建一個InjvmExporter,無其他邏輯。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
    super(invoker);
    this.key = key;
    this.exporterMap = exporterMap;
    exporterMap.put(key, this);
}

然後介紹遠程服務暴露
如果有註冊中心,服務暴露後需要向註冊中心註冊服務信息
如果沒有註冊中心,直接暴露服務。

有註冊中心的暴露

  1. 調用doLocalExport()方法暴露服務。
  2. 向註冊中心註冊服務
  3. 向註冊中心進行訂閱ovrride數據
  4. 創建並返回DestroyableExporter
    調用doLocalExport()方法暴露服務
  • 首先根據invoker得到key,從bounds緩存變數中嘗試獲取exporter,如果獲取不到,則調用Protocal的export方法,預設的協議是dubbo,所以調用的DubboProtocol的export方法。
  • 調用DubboProtocol的export方法,首先從invoker中獲取對用的URL,然後根據服務組名、服務名、服務版本號和埠組成key,然後創建一個DubboExporter。
// demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880
String key = serviceKey(url);
// 創建 DubboExporter
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
// 將 <key, exporter> 鍵值對放入緩存中
exporterMap.put(key, exporter);
  • 調用openServer(url)方法。首先根據url獲取host:port,用於標識當前的伺服器實例。在同一臺機器上,同一個埠僅允許啟動一個伺服器實例。若某個埠上已有伺服器實例,此時調用reset方法重置一些伺服器的配置;如果沒有則調用createServer(url)方法創建一個伺服器實例。
  • 調用createServer(url)方法,有三個核心的邏輯,首先檢測是否存在server參數所代表的Transporter拓展,即網路傳輸方式(如Netty, Mina),如果沒有,則拋出異常;然後通過Exchangers.bind(url, requestHandler)方法創建伺服器實例;最後是檢測是否支持client參數所表示的Transporter拓展,不存在也是拋出異常。
  • 調用Exchangers.bind(url, requestHandler)方法,裡面又調用了下麵幾個方法。只保留了主要邏輯部分。
getExchanger(url).bind(url, handler);
   
public static Exchanger getExchanger(URL url) {
        String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
        return getExchanger(type);
    }
public static Exchanger getExchanger(String type) {
        return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
    }

Exchange層是為了封裝請求/響應模式,例如:把同步請求轉化為非同步請求。預設的擴展點實現類是HeaderExchanger

  • 調用HeaderExchanger的bind方法
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handlers == null || handlers.length == 0) {
        throw new IllegalArgumentException("handlers == null");
    }
    ChannelHandler handler;
    if (handlers.length == 1) {
        handler = handlers[0];
    } else {
        // 如果 handlers 元素數量大於1,則創建 ChannelHandler 分發器
        handler = new ChannelHandlerDispatcher(handlers);
    }
    // 獲取自適應 Transporter 實例,並調用實例方法
    return getTransporter().bind(url, handler);
}
  • 根據自適應擴展機制動態創建一個Transporter,Dubbo預設是NettyTransporter。
  • 調用NettyTransporter.bind(URL, ChannelHandler)方法。創建一個NettyServer實例。
  • 調用NettyServer.doOPen()方法,伺服器被開啟,服務也被暴露出來了。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在上一個版本中利用netty實現了簡單的一對一的RPC,需要手動設置服務地址,限制性較大。 在本文中,利用zookeeper作為服務註冊中心,在服務端啟動時將本地的服務信息註冊到zookeeper中,當客戶端發起遠程服務調用時,先從zookeeper中獲取該服務的地址,然後根據獲得的這個地址來利用n ...
  • 什麼是RPC RPC (Remote Procedure Call Protocol), 遠程過程調用,通俗的解釋就是:客戶端在不知道調用細節的情況下,調用存在於遠程電腦上的某個對象,就像調用本地應用程式中的對象一樣,不需要瞭解底層網路技術的協議。 簡單的整體工作流程 請求端發送一個調用的數據包, ...
  • 在dubbo中,關於註冊中心Registry的有關實現封裝在了dubbo registry模塊中。提供者(Provider)個消費者(Consumer)都是通過註冊中心進行資源的調度。當服務啟動時,provider會調用註冊中心的register方法將自己的服務通過url的方式發佈到註冊中心,而co ...
  • 在網路傳輸中,怎麼確保通道連接的可用性是一個很重要的問題,簡單的說,在網路通信中有客戶端和服務端,一個負責發送請求,一個負責接收請求,在保證連接有效性的背景下,這兩個物體扮演了什麼角色,心跳機制能有效的保證連接的可用性,那它的機制是什麼,下文中將會詳細講解。 網路層的可用性 首先講一下TCP,在du ...
  • 在上文中介紹了基礎類AbstractRegistry類的解釋,在本篇中將繼續介紹該包下的其他類。 FailbackRegistry 該類繼承了AbstractRegistry,AbstractRegistry中的註冊訂閱等方法,實際上就是一些記憶體緩存的變化,而真正的註冊訂閱的實現邏輯在Failbac ...
  • [TOC] python是數據分析的主要工具,它包含的數據結構和數據處理工具的設計讓python在數據分析領域變得十分快捷。它以NumPy為基礎,並對於需要類似 for迴圈 的大量數據處理的問題有非常快捷的數組處理函數。 但是pandas最擅長的領域還是在處理表格型二維以上不同數據類型數據。 基本導 ...
  • 從今天開始,將會逐步介紹關於DUbbo的有關知識。首先先簡單介紹一下DUbbo的整體概述。 概述 Dubbo是SOA(面向服務架構)服務治理方案的核心框架。用於分散式調用,其重點在於分散式的治理。 簡單的來說,可以把它分為四個角色。服務提供方(Provider)、服務消費方(Consumer)、註冊 ...
  • 在上一篇文章 Dubbo之服務暴露分析 中介紹了當遠程暴露時,如果有註冊中心,需要在服務暴露後再將服務註冊到註冊中心。該篇將介紹該功能的有關步驟。 註冊的起點 在 方法包含了服務導出,註冊,以及數據訂閱等邏輯。其中服務註冊先調用 方法。 可以看出,服務註冊主要包括兩部分, 獲取註冊中心實例 和 向註 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...