Dubbo 概述

Dubbo 核心組件

層次 作用
Service 該層與業(yè)務(wù)邏輯相關(guān),根據(jù) provider 和 consumer 的業(yè)務(wù)設(shè)計(jì)對(duì)應(yīng)的接口和實(shí)現(xiàn)
Config 對(duì)外配置接口,以 ServiceConfig 和 ReferenceConfig 為中心慨蛙》温疲可以理解為該層管理了整個(gè) Dubbo 配置
Proxy 使用動(dòng)態(tài)代理的方式為接口創(chuàng)建代理類,Proxy 層最主要的接口就是 ProxyFactory扛禽。其默認(rèn)的擴(kuò)展點(diǎn)有:stub懂牧、jdk侈净、javassist。jdk 使用反射的方式創(chuàng)建代理類,javassist 通過(guò)拼接字符串然后編譯的方式創(chuàng)建代理類畜侦。對(duì)于服務(wù)提供者元扔,代理的對(duì)象是接口的真實(shí)實(shí)現(xiàn)。 對(duì)于服務(wù)消費(fèi)者旋膳,代理的對(duì)象是遠(yuǎn)程服務(wù)的 invoker 對(duì)象
Registry 主要負(fù)責(zé)的就是服務(wù)的注冊(cè)與發(fā)現(xiàn)澎语。這層的主要接口就是 RegistryFactory,其接口方法有 @Adaptive 注解溺忧,會(huì)根據(jù)參數(shù) protocol 來(lái)選擇實(shí)現(xiàn)咏连,默認(rèn)的擴(kuò)展實(shí)現(xiàn)有:zookeeper、redis鲁森、multicast(廣播模式)、內(nèi)存
Cluster 集群容錯(cuò)層振惰,主要負(fù)責(zé):遠(yuǎn)程調(diào)用時(shí)的容錯(cuò)策略(如快速失敗歌溉、失敗重試);選擇具體調(diào)用節(jié)點(diǎn)的負(fù)載均衡策略(如隨機(jī)骑晶、一致性 hash 等)痛垛;特殊調(diào)用路徑的路由策略(如消費(fèi)者只會(huì)調(diào)用某個(gè) IP 的生產(chǎn)者)
Monitor 負(fù)責(zé)監(jiān)控統(tǒng)計(jì)調(diào)用次數(shù)和調(diào)用時(shí)間
Protocal 遠(yuǎn)程調(diào)用層,封裝 RPC 調(diào)用的具體過(guò)程桶蛔。Protocal 是 Invoker 暴露(發(fā)布一個(gè)任務(wù)讓消費(fèi)者調(diào)用)和引用(引用一個(gè)服務(wù)到本地)的主功能入口匙头,它負(fù)責(zé)管理 Invoker 的整個(gè)生命周期。Invoker 是 Dubbo 的核心模型仔雷,它代表一個(gè)可執(zhí)行體蹂析,允許向它發(fā)起 invoke 調(diào)用,它可能執(zhí)行一個(gè)本地接口實(shí)現(xiàn)碟婆,也可能執(zhí)行一個(gè)遠(yuǎn)程接口實(shí)現(xiàn)
Exchange 封裝請(qǐng)求響應(yīng)模式电抚,如如何將同步請(qǐng)求轉(zhuǎn)化為異步請(qǐng)求。以 Request 和 Response 為中心竖共,擴(kuò)展接口為 Exchanger蝙叛、ExchangeChannel、ExchangeClient 和 ExchangeServer
Transport 網(wǎng)絡(luò)傳輸層公给,抽象 Mina 和 Netty 為統(tǒng)一接口借帘。用戶也可以擴(kuò)展接口添加更多網(wǎng)絡(luò)傳輸方式
Serialize 序列化的作用是把對(duì)象轉(zhuǎn)化為二進(jìn)制流,然后在網(wǎng)絡(luò)中傳輸淌铐。負(fù)責(zé)整個(gè)框架網(wǎng)絡(luò)傳輸時(shí)的序列化和反序列化工作

Dubbo 總體調(diào)用流程

服務(wù)暴露過(guò)程

服務(wù)端在框架啟動(dòng)時(shí)肺然,會(huì)初始化服務(wù)實(shí)例,通過(guò) Proxy 組件調(diào)用具體協(xié)議(Protocol)匣沼,把服務(wù)端要暴露的接口封裝成 Invoker(真實(shí)是類型是 AbstractInvoker)狰挡,然后轉(zhuǎn)換成 Exporter,這時(shí)框架會(huì)打開(kāi)服務(wù)端口并記錄服務(wù)實(shí)例到內(nèi)存中,最后通過(guò) Registry 把服務(wù)元數(shù)據(jù)注冊(cè)到注冊(cè)中心加叁。

  • Proxy 組件:Dubbo 中只需要引用一個(gè)接口就可以調(diào)用遠(yuǎn)程服務(wù)倦沧。其實(shí)這是 Dubbo 為我們生成的代理類,調(diào)用的方法是 Proxy 組件生成的代理方法它匕,會(huì)自動(dòng)發(fā)起遠(yuǎn)程/本地調(diào)用展融,并返回結(jié)果。整個(gè)過(guò)程對(duì)用戶全透明
  • Protocol:可以將對(duì)接口的配置分居不同的協(xié)議轉(zhuǎn)化成不同的 Invoker 對(duì)象豫柬。例如協(xié)議為 dubbo 時(shí)會(huì)將遠(yuǎn)程接口配置轉(zhuǎn)換成一個(gè) DubboInvoker
  • Exporter:用于暴露到注冊(cè)中心的對(duì)象告希,持有一個(gè) Invoker 對(duì)象
  • Registry:將 Exporter 注冊(cè)到注冊(cè)中心

以上就是服務(wù)暴露過(guò)程。消費(fèi)者在啟動(dòng)時(shí)會(huì)通過(guò) Registry 在注冊(cè)中心訂閱服務(wù)端的元數(shù)據(jù)(包括 IP 和 端口)烧给,并在第一個(gè)初始化時(shí)從注冊(cè)中心**拉取全量服務(wù)端信息****

消費(fèi)者調(diào)用流程

首先調(diào)用也是從一個(gè) Proxy 開(kāi)始燕偶,Proxy 持有一個(gè) Invoker 對(duì)象并觸發(fā) invoke 調(diào)用。在 invoke 調(diào)用過(guò)程中础嫡,使用 Cluster 進(jìn)行容錯(cuò)指么、路由以及負(fù)載均衡。Cluster 先通過(guò) Directory 獲取可用的遠(yuǎn)程服務(wù) Invoker 列表榴鼎,根據(jù)用戶配置的路由規(guī)則(例如指定某些方法智能調(diào)用某個(gè)節(jié)點(diǎn))將 Invoker 過(guò)濾一遍

然后存活下來(lái)的 Invoker 通過(guò) LoadBalance 方法做負(fù)載均衡伯诬,選出一個(gè)可以調(diào)用的 Invoker。選中的 Invoker 會(huì)在調(diào)用 invoke 之前經(jīng)過(guò)一個(gè)過(guò)濾器鏈(通常處理上下文巫财、限流盗似、計(jì)數(shù)等操作)

接著會(huì)使用 Network Client 做數(shù)據(jù)傳輸。傳輸之前通過(guò) Codec 做私有協(xié)議構(gòu)造平项,然后進(jìn)行序列化傳輸至服務(wù)端赫舒。服務(wù)端接收到數(shù)據(jù)包后也會(huì)使用 Codec 處理協(xié)議頭等,完成后對(duì)數(shù)據(jù)報(bào)文進(jìn)行反序列化處理

隨后遠(yuǎn)程調(diào)用請(qǐng)求(Request)被分配到 ThreadPool 中進(jìn)行處理葵礼。Server 會(huì)處理這些 Request号阿,根據(jù)請(qǐng)求查找對(duì)應(yīng)的 Exporter(內(nèi)部持有 Invoker),Invoker 用裝飾器模式套了很多 Filter鸳粉,因此在 Invoker 調(diào)用之前會(huì)經(jīng)過(guò)服務(wù)端的過(guò)濾鏈

最終得到了服務(wù)端具體的接口實(shí)現(xiàn)并調(diào)用扔涧,然后將結(jié)果原路返回

Dubbo 注冊(cè)中心

在 Dubbo 為服務(wù)體系中,注冊(cè)中心是核心組件之一届谈。Dubbo 通過(guò)注冊(cè)中心實(shí)現(xiàn)了分布式環(huán)境中服務(wù)的注冊(cè)和發(fā)現(xiàn)枯夜,是各個(gè)分布式節(jié)點(diǎn)之前的紐帶。其主要作用如下:

  • 動(dòng)態(tài)加入:一個(gè)服務(wù)提供者通過(guò)注冊(cè)中心可以動(dòng)態(tài)地把自己暴露給其他消費(fèi)者艰山,無(wú)需消費(fèi)者逐個(gè)更新配置文件
  • 動(dòng)態(tài)發(fā)現(xiàn):一個(gè)消費(fèi)者可以動(dòng)態(tài)感知新的配置湖雹、路由規(guī)劃和服務(wù)提供者,無(wú)需重啟服務(wù)使之生效
  • 動(dòng)態(tài)調(diào)整:注冊(cè)中心支持參數(shù)動(dòng)態(tài)調(diào)整曙搬,新參數(shù)自動(dòng)更新到所有相關(guān)服務(wù)點(diǎn)
  • 統(tǒng)一配置:避免本地配置導(dǎo)致每個(gè)服務(wù)的配置不一致問(wèn)題

1 工作流程

注冊(cè)中心整體流程如下圖所示:

  • 服務(wù)提供者啟動(dòng)時(shí)摔吏,會(huì)向注冊(cè)中心寫入自己的元數(shù)據(jù)信息鸽嫂,同時(shí)會(huì)訂閱配置元數(shù)據(jù)信息
  • 消費(fèi)者啟動(dòng)時(shí),會(huì)向注冊(cè)中心寫入自己的元數(shù)據(jù)信息征讲,同時(shí)訂閱服務(wù)提供者据某、配置蛛芥、路由元數(shù)據(jù)信息
  • 服務(wù)治理中心(dubbo-admin)啟動(dòng)時(shí)蒋困,會(huì)同時(shí)訂閱 服務(wù)提供者、消費(fèi)者程拭、配置滤祖、路由元數(shù)據(jù)信息
  • 當(dāng)有服務(wù)離開(kāi)或新的服務(wù)加入時(shí)筷狼,注冊(cè)中心服務(wù)提供者目錄會(huì)發(fā)生變化,變化信息將動(dòng)態(tài)通知給消費(fèi)者和服務(wù)治理中心
  • 當(dāng)消費(fèi)者發(fā)起服務(wù)調(diào)用時(shí)匠童,會(huì)異步將調(diào)用埂材、統(tǒng)計(jì)信息上報(bào)至監(jiān)控中心(dubbo-monitor)

2 數(shù)據(jù)結(jié)構(gòu)

注冊(cè)中心的總體流程相同,但不同的注冊(cè)中心有不同的實(shí)現(xiàn)方式汤求,其數(shù)據(jù)結(jié)構(gòu)也不同楞遏。常見(jiàn)的注冊(cè)中心實(shí)現(xiàn)是 ZookeeperRedis。下面重點(diǎn)介紹注冊(cè)中心的 Zookeeper 的實(shí)現(xiàn)方式

Zookeeper 是樹(shù)形結(jié)構(gòu)的注冊(cè)中心首昔,每個(gè)節(jié)點(diǎn)分為持久節(jié)點(diǎn)、持久順序節(jié)點(diǎn)糙俗、臨時(shí)節(jié)點(diǎn)勒奇、臨時(shí)順序節(jié)點(diǎn)

  • 持久節(jié)點(diǎn):服務(wù)注冊(cè)后保證節(jié)點(diǎn)不會(huì)丟失,注冊(cè)中心重啟仍然節(jié)點(diǎn)存在
  • 持久順序節(jié)點(diǎn):在持久節(jié)點(diǎn)的基礎(chǔ)上增加節(jié)點(diǎn)順序功能
  • 臨時(shí)節(jié)點(diǎn):服務(wù)注冊(cè)后連接丟失或 session 超時(shí)巧骚,注冊(cè)節(jié)點(diǎn)會(huì)被自動(dòng)移除
  • 臨時(shí)順序節(jié)點(diǎn):在臨時(shí)節(jié)點(diǎn)的基礎(chǔ)上增加節(jié)點(diǎn)順序功能

Dubbo 使用 Zookeeper 作為注冊(cè)中心時(shí)赊颠,只使用持久節(jié)點(diǎn)和臨時(shí)節(jié)點(diǎn)

假設(shè) /dubbo/com.test.TestService/providers 是服務(wù)提供者在 Zookeeper 上的注冊(cè)路徑,該結(jié)構(gòu)分為四層:root(根節(jié)點(diǎn)劈彪,默認(rèn)是 dubbo)竣蹦、service(接口名稱,對(duì)應(yīng)于 com.test.TestService)沧奴、四種服務(wù)目錄(示例中的 providers 以及 consumers痘括、routers、configurators)滔吠。在服務(wù)分類節(jié)點(diǎn)下是具體的 DUbbo 服務(wù) URL纲菌,樹(shù)形結(jié)構(gòu)實(shí)例如下:

+ /dubbo
    +-- com.test.TestService
        +-- providers 
        +-- consumers
        +-- routers
        +-- configurators
  • 樹(shù)節(jié)點(diǎn)的根目錄是注冊(cè)中心分組,下面有多個(gè)服務(wù)接口疮绷,分組值來(lái)自用戶配置 < dubbo:registry > 中的 group 屬性
  • 服務(wù)接口下包含 4 個(gè)子目錄(providers翰舌、consumers、routers 以及 configurators)冬骚,都是持久節(jié)點(diǎn)
  • 服務(wù)提供者目錄(/dubbo/com.test.TestService/providers)下面包含多個(gè)服務(wù)提供者 URL 元數(shù)據(jù)信息
  • 服務(wù)消費(fèi)者目錄(/dubbo/com.test.TestService/consumers)下面包含多個(gè)消費(fèi)者 URL 元數(shù)據(jù)信息
  • 路由配置目錄(/dubbo/com.test.TestService/routers)下面包含多個(gè)用于消費(fèi)者路由策略 URL 元數(shù)據(jù)信息
  • 動(dòng)態(tài)配置目錄(/dubbo/com.test.TestService/configurators)下面包含多個(gè)用于服務(wù)提供者動(dòng)態(tài)配置 URL 元數(shù)據(jù)信息

3 訂閱發(fā)布的實(shí)現(xiàn)

3.1 發(fā)布實(shí)現(xiàn)

服務(wù)提供者和服務(wù)消費(fèi)者都需要將自己的元數(shù)據(jù)信息注冊(cè)到注冊(cè)中心椅贱。服務(wù)提供者的注冊(cè)是為了讓服務(wù)消費(fèi)者感知服務(wù)的存在懂算,從而發(fā)起遠(yuǎn)程調(diào)用;也讓服務(wù)治理中心感知有新的服務(wù)提供者上線庇麦。消費(fèi)者的注冊(cè)是讓了讓服務(wù)治理中心可以發(fā)現(xiàn)自己

Zookeeper 實(shí)現(xiàn)發(fā)布的代碼很簡(jiǎn)單计技,只是調(diào)用了 Zookeeper 客戶端在注冊(cè)中心上創(chuàng)建一個(gè)目錄

zkClient.create(toUrlPath(url));

取消發(fā)布也很簡(jiǎn)單,只是把 ZooKeeper 注冊(cè)中心上對(duì)應(yīng)的路徑刪除即可

zkClient.delete(toUrlPath(url));

3.2 訂閱實(shí)現(xiàn)

訂閱通常有 pull 和 push 兩種方式女器,Dubbo 目前采用 pull(拉取方式)酸役,后續(xù)接收事件并重新拉取數(shù)據(jù)

在服務(wù)暴露時(shí),服務(wù)提供者會(huì)訂閱 configurators 用于監(jiān)聽(tīng)動(dòng)態(tài)配置驾胆。消費(fèi)者啟動(dòng)時(shí)會(huì)訂閱 providers涣澡、routers 以及 configurators 這三個(gè)目錄

Dubbo 中 Zookeeper 的客戶端實(shí)現(xiàn)有兩種:

  • Apache Curator(默認(rèn))
  • zKClient

用戶可以通過(guò) < dubbo:registry > 的 client 屬性設(shè)置 curator 或 zkclient 作為 Zookeeper 客戶端的實(shí)現(xiàn)方式

Zookeeper 注冊(cè)中心采用的是 事件通知 + 客戶端拉取 的方式,客戶端第一次連接上注冊(cè)中心時(shí)丧诺,會(huì)獲取對(duì)應(yīng)目錄下的全量數(shù)據(jù)入桂,并在訂閱的節(jié)點(diǎn)上注冊(cè)一個(gè) watcher,客戶端于注冊(cè)中心保持 TCP 長(zhǎng)連接驳阎,后續(xù)每個(gè)節(jié)點(diǎn)有數(shù)據(jù)變化的時(shí)候抗愁,注冊(cè)中心會(huì)根據(jù) watcher 的回調(diào)通知客戶端數(shù)據(jù)有更新,客戶端會(huì)將對(duì)應(yīng)節(jié)點(diǎn)下的數(shù)據(jù)重新從注冊(cè)中心拉取過(guò)來(lái)呵晚。

Dubbo 啟停原理解析

1 優(yōu)雅停機(jī)原理解析

優(yōu)雅停機(jī)特性是所有 RPC 框架中非常重要的特性之一蜘腌,因?yàn)楹诵臉I(yè)務(wù)在服務(wù)器正在執(zhí)行時(shí)突然中斷可能出現(xiàn)嚴(yán)重后果。Dubbo 的優(yōu)雅停機(jī)原理如下圖所示:

Dubbo 中實(shí)現(xiàn)優(yōu)雅停機(jī)有幾下幾步:

  1. 收到 kill -9 進(jìn)程退出信號(hào)饵隙,Spring 容器會(huì)觸發(fā)容器銷毀事件
  2. provider 端取消注冊(cè)服務(wù)元數(shù)據(jù)信息
  3. consumer 端會(huì)收到最新的服務(wù)調(diào)用列表(不包含準(zhǔn)備停機(jī)的地址)
  4. Dubbo 協(xié)議發(fā)送 readonly 事件報(bào)文通知 consumer 服務(wù)不可用
  5. 服務(wù)端等待已執(zhí)行的任務(wù)結(jié)束并拒絕新任務(wù)執(zhí)行
  6. provider 端斷開(kāi)與 consumer 端的 TCP 連接

既然第三步 consumer 端已經(jīng)被通知最新的服務(wù)調(diào)用地址撮珠,provider 端為什么還要發(fā)送 readonly 事件報(bào)文給 consumer 端呢?這里主要考慮到注冊(cè)中心推送服務(wù)有網(wǎng)絡(luò)延遲金矛,以及客戶端計(jì)算服務(wù)列表可能占用一些時(shí)間芯急。Dubbo 協(xié)議發(fā)送 readonly 事件報(bào)文時(shí),consumer 端會(huì)設(shè)置響應(yīng)的 provider 為不可用狀態(tài)驶俊,下次負(fù)載均衡將不再調(diào)用下線的機(jī)器

Dubbo 遠(yuǎn)程調(diào)用

1 Dubbo 通信協(xié)議

Dubbo 支持 9 中通信協(xié)議

2.1 dubbo://(推薦)
20200106104652673.png

描述:Dubbo 默認(rèn)通信協(xié)議娶耍,單一長(zhǎng)連接,進(jìn)行的是 NIO 異步通信饼酿,基于 hessian 作為序列化協(xié)議

連接個(gè)數(shù):?jiǎn)芜B接

連接方式:長(zhǎng)鏈接

傳輸協(xié)議:TCP

傳輸方式:NIO 異步傳輸

序列化:Hessian 二進(jìn)制序列化

適用范圍:

  • 傳入傳出參數(shù)數(shù)據(jù)包較虚啪啤(建議小于100K)
  • 消費(fèi)者比提供者個(gè)數(shù)多,單一消費(fèi)者無(wú)法壓滿提供者
  • 盡量不要用 dubbo 協(xié)議傳輸大文件或超大字符串

適用場(chǎng)景 :

  • 高并發(fā)場(chǎng)景嗜湃,一般是服務(wù)提供者就幾臺(tái)機(jī)器奈应,但是服務(wù)消費(fèi)者有上百臺(tái),可能每天調(diào)用量達(dá)到上億次购披!此時(shí)用長(zhǎng)連接是最合適的杖挣,就是跟每個(gè)服務(wù)消費(fèi)者維持一個(gè)長(zhǎng)連接就可以,可能總共就100個(gè)連接刚陡。然后后面直接基于長(zhǎng)連接 NIO 異步通信惩妇,可以支撐高并發(fā)請(qǐng)求 株汉,如果上億次請(qǐng)求每次都是短連接的話,服務(wù)提供者會(huì)扛不住
  • 因?yàn)樽叩氖菃我婚L(zhǎng)連接歌殃,所以傳輸數(shù)據(jù)量太大的話乔妈,會(huì)導(dǎo)致并發(fā)能力降低。所以一般建議是傳輸數(shù)據(jù)量很小氓皱,支撐高并發(fā)訪問(wèn)

約束 :

  • 參數(shù)及返回值需實(shí)現(xiàn) Serializable 接口
  • 參數(shù)及返回值不能自定義實(shí)現(xiàn) List, Map, Number, Date, Calendar 等接口路召,只能用 JDK 自帶的實(shí)現(xiàn),因?yàn)?hessian 會(huì)做特殊處理波材,自定義實(shí)現(xiàn)類中的屬性值都會(huì)丟失
  • Hessian 序列化股淡,只傳成員屬性值和值的類型,不傳方法或靜態(tài)變量
2.2 rmi://

描述:RMI 協(xié)議采用 JDK 標(biāo)準(zhǔn)的 java.rmi.* 實(shí)現(xiàn)廷区,采用阻塞式短連接和 JDK 標(biāo)準(zhǔn)序列化方式

連接個(gè)數(shù):多連接

連接方式:短鏈接

傳輸協(xié)議:TCP

傳輸方式:同步傳輸

序列化:Java 標(biāo)準(zhǔn)二進(jìn)制序列化

適用范圍:

  • 傳入傳出參數(shù)數(shù)據(jù)包大小混合
  • 消費(fèi)者與提供者個(gè)數(shù)差不多
  • 可傳文件

適用場(chǎng)景 :

  • 常規(guī)遠(yuǎn)程服務(wù)方法調(diào)用唯灵,與原生RMI服務(wù)互操作,一般較少用

約束 :

  • 參數(shù)及返回值需實(shí)現(xiàn) Serializable 接口
  • dubbo 配置中的超時(shí)時(shí)間對(duì) RMI 無(wú)效隙轻,需使用 java 啟動(dòng)參數(shù)設(shè)置 -Dsun.rmi.transport.tcp.responseTimeout=3000
2.3 hessian://

描述:Hessian 協(xié)議用于集成 Hessian 的服務(wù)埠帕,Hessian 底層采用 Http 通訊,采用 Servlet 暴露服務(wù)玖绿,Dubbo 缺省內(nèi)嵌 Jetty 作為服務(wù)器實(shí)現(xiàn)

連接個(gè)數(shù):多連接

連接方式:短鏈接

傳輸協(xié)議:HTTP

傳輸方式:同步傳輸

序列化:Hessian 二進(jìn)制序列化

適用范圍:

  • 傳入傳出參數(shù)數(shù)據(jù)包較大
  • 提供者比消費(fèi)者個(gè)數(shù)多敛瓷,提供者壓力較大
  • 可傳文件

適用場(chǎng)景 :

  • 頁(yè)面?zhèn)鬏?/li>
  • 文件傳輸
  • 原生 Hessian 服務(wù)互操作

約束 :

  • 同 dubbo 協(xié)議
2.4 http://

描述:基于 HTTP 表單的遠(yuǎn)程調(diào)用協(xié)議,采用 Spring 的 HttpInvoker 實(shí)現(xiàn)

連接個(gè)數(shù):多連接

連接方式:短鏈接

傳輸協(xié)議:HTTP

傳輸方式:同步傳輸

序列化:表單序列化

適用范圍:

  • 傳入傳出參數(shù)數(shù)據(jù)包大小混合斑匪,提供者比消費(fèi)者個(gè)數(shù)多琐驴,可用瀏覽器查看,可用表單或 URL 傳入?yún)?shù)秤标,暫不支持傳文件

適用場(chǎng)景 :

  • 需同時(shí)給應(yīng)用程序和瀏覽器 JS 使用的服務(wù)

約束 :

  • 參數(shù)及返回值需符合 Bean 規(guī)范
2.5 webservice://

描述:基于 WebService 的遠(yuǎn)程調(diào)用協(xié)議,基于 Apache CXF 的 frontend-simple 和 transports-http 實(shí)現(xiàn)

連接個(gè)數(shù):多連接

連接方式:短鏈接

傳輸協(xié)議:HTTP

傳輸方式:同步傳輸

序列化:同步傳輸 SOAP 文本序列化

適用場(chǎng)景 :

  • 系統(tǒng)集成宙刘,跨語(yǔ)言調(diào)用
2.6 thrift://

描述:當(dāng)前 dubbo 支持 的 thrift 協(xié)議是對(duì) thrift 原生協(xié)議的擴(kuò)展苍姜,在原生協(xié)議的基礎(chǔ)上添加了一些額外的頭信息,比如 service name悬包,magic number 等

2.7 memcached://

描述:基于 memcached 實(shí)現(xiàn)的 RPC 協(xié)議

2.8 redis://

描述:基于 Redis 實(shí)現(xiàn)的 RPC 協(xié)議

2.9 rest://

描述:基于標(biāo)準(zhǔn)的 Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的簡(jiǎn)寫)實(shí)現(xiàn)的REST調(diào)用支持

2 dubbo:// 協(xié)議詳解

Dubbo 數(shù)據(jù)包分為消息頭和消息體衙猪,消息頭用于存儲(chǔ)一些元信息,比如魔法數(shù)(Magic)布近,數(shù)據(jù)包類型(Request/Response)垫释,消息體長(zhǎng)度(Data Length)等。消息體中用于存儲(chǔ)具體的調(diào)用消息撑瞧,比如方法名稱棵譬,參數(shù)列表等。下面簡(jiǎn)單列舉一下消息頭的內(nèi)容

偏移量(Bit) 字段 取值
0 ~ 7 魔數(shù)高位 0xda00
8 ~ 15 魔數(shù)低位 0xbb
16 數(shù)據(jù)包類型 0 - Response, 1 - Request
17 調(diào)用方式 僅在第16位被設(shè)為1的情況下有效预伺,0 - 單向調(diào)用订咸,1 - 雙向調(diào)用
18 事件標(biāo)識(shí) 0 - 當(dāng)前數(shù)據(jù)包是請(qǐng)求或響應(yīng)包曼尊,1 - 當(dāng)前數(shù)據(jù)包是心跳包
19 ~ 23 序列化器編號(hào) 2 - Hessian2Serialization 3 - JavaSerialization 4 - CompactedJavaSerialization 6 - FastJsonSerialization 7 - NativeJavaSerialization 8 - KryoSerialization 9 - FstSerialization
24 ~ 31 狀態(tài) 20 - OK 30 - CLIENT_TIMEOUT 31 - SERVER_TIMEOUT 40 - BAD_REQUEST 50 - BAD_RESPONSE ......
32 ~ 95 請(qǐng)求編號(hào) 共 8 字節(jié)存儲(chǔ) RPC 請(qǐng)求的唯一 id,運(yùn)行時(shí)生成脏嚷,用來(lái)將請(qǐng)求和響應(yīng)做關(guān)聯(lián)
96 ~ 127 消息體長(zhǎng)度 運(yùn)行時(shí)計(jì)算

在網(wǎng)絡(luò)通信中(基于 TCP)需要解決網(wǎng)絡(luò)粘包/解包的問(wèn)題骆撇,一些常用的方法是用回車、換行父叙、固定長(zhǎng)度和特殊分隔符進(jìn)行處理神郊。Dubbo 就是用魔法數(shù)(0xdabb)來(lái)分割處理粘包問(wèn)題的

一般情況下,服務(wù)消費(fèi)方會(huì)并發(fā)調(diào)用多個(gè)服務(wù)趾唱,每個(gè)用戶線程發(fā)送請(qǐng)求后涌乳,會(huì)調(diào)用不同 DefaultFuture 對(duì)象的 get 方法進(jìn)行等待。 一段時(shí)間后鲸匿,服務(wù)消費(fèi)方的線程池會(huì)收到多個(gè)響應(yīng)對(duì)象爷怀。這個(gè)時(shí)候要考慮一個(gè)問(wèn)題,如何將每個(gè)響應(yīng)對(duì)象傳遞給相應(yīng)的 DefaultFuture 對(duì)象带欢,且不出錯(cuò)

答案是通過(guò) 請(qǐng)求編號(hào)运授。DefaultFuture 被創(chuàng)建時(shí),會(huì)要求傳入一個(gè) Request 對(duì)象乔煞。此時(shí) DefaultFuture 可從 Request 對(duì)象中獲取請(qǐng)求編號(hào)吁朦,并將 <請(qǐng)求編號(hào), DefaultFuture 對(duì)象> 映射關(guān)系存入到靜態(tài) HashMap 中,即 FUTURES渡贾。消費(fèi)者端線程池中的線程收到 Response 對(duì)象后逗宜,會(huì)根據(jù) Response 對(duì)象中的請(qǐng)求編號(hào)到 FUTURES 集合中取出相應(yīng)的 DefaultFuture 對(duì)象,然后再將 Response 對(duì)象設(shè)置到 DefaultFuture 對(duì)象中空骚。最后再喚醒用戶線程纺讲,這樣用戶線程即可從 DefaultFuture 對(duì)象中獲取調(diào)用結(jié)果了,整個(gè)過(guò)程大致如下圖:

request-id-application.jpg

3 Dubbo 序列化協(xié)議

Dubbo序列化支持 java囤屹、compactedjava熬甚、nativejava、fastjson肋坚、dubbo乡括、fst、hessian2智厌、kryo诲泌,其中 dubbo 協(xié)議缺省為 hessian2 序列化,rmi 協(xié)議缺省為 java 序列化铣鹏,http 協(xié)議缺省為 json 序列化

以下是 dubbo:2.7.7 擴(kuò)展的序列化方式敷扫,可在 <dubbo:protocol> 標(biāo)簽的 serialization 屬性進(jìn)行配置

fastjson=org.apache.dubbo.common.serialize.fastjson.FastJsonSerialization
fst=org.apache.dubbo.common.serialize.fst.FstSerialization
hessian2=org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization
native-hessian=org.apache.dubbo.serialize.hessian.Hessian2Serialization
java=org.apache.dubbo.common.serialize.java.JavaSerialization
compactedjava=org.apache.dubbo.common.serialize.java.CompactedJavaSerialization
nativejava=org.apache.dubbo.common.serialize.nativejava.NativeJavaSerialization
kryo=org.apache.dubbo.common.serialize.kryo.KryoSerialization
kryo2=org.apache.dubbo.common.serialize.kryo.optimized.KryoSerialization2
avro=org.apache.dubbo.common.serialize.avro.AvroSerialization
protostuff=org.apache.dubbo.common.serialize.protostuff.ProtostuffSerialization

gson=org.apache.dubbo.common.serialize.gson.GsonSerialization

protobuf-json=org.apache.dubbo.common.serialize.protobuf.support.GenericProtobufJsonSerialization
protobuf=org.apache.dubbo.common.serialize.protobuf.support.GenericProtobufSerialization

Dubbo 序列化主要由 Serialization(序列化策略)、DataInput(反序列化诚卸,二進(jìn)制->對(duì)象)呻澜、DataOutput(序列化递礼,對(duì)象->二進(jìn)制流) 來(lái)進(jìn)行數(shù)據(jù)的序列化與反序列化


3.1 hessian 序列化

Hessian 數(shù)據(jù)結(jié)構(gòu)

8 種原始類型 1. raw binary data
2. boolean
3. 64-bit millisecond date
4. 64-bit double
5. 32-bit int
6. 64-bit long
7. null
8. UTF8-encoded string
3 種遞歸類型 1. list for lists and arrays
2. map for maps and dictionaries
3. object for objects
1 中特殊類型 1. ref for shared and circular object references.

hessian 序列化 VS Java 原生序列化

Java 序列化會(huì)把要序列化的對(duì)象類的元數(shù)據(jù)和業(yè)務(wù)數(shù)據(jù)全部序列化為字節(jié)流,而且是把整個(gè)繼承關(guān)系上的東西全部序列化了羹幸。它序列化出來(lái)的字節(jié)流是一個(gè)對(duì)象從結(jié)構(gòu)到內(nèi)容的完整描述脊髓,包含所有的信息,因此效率較低而且字節(jié)流比較大栅受。但是由于序列化了所有內(nèi)容将硝,因此也更可用和可靠

hession 序列化實(shí)現(xiàn)機(jī)制是著重于數(shù)據(jù),附帶簡(jiǎn)單的類型信息的方法屏镊。就像 Integer a = 1依疼,hessian會(huì)序列化成 I 1 這樣的流,I 表示 int or Integer而芥,1 就是數(shù)據(jù)內(nèi)容律罢。對(duì)于復(fù)雜對(duì)象,通過(guò) Java 的反射機(jī)制棍丐,hessian 把對(duì)象所有的屬性當(dāng)成一個(gè) Map 來(lái)序列化误辑,產(chǎn)生類似 M className propertyName1 I 1 propertyName S stringValue 這樣的流,包含了基本的類型描述和數(shù)據(jù)內(nèi)容

hessian 的序列化在內(nèi)容的序列化上做了一些優(yōu)化歌逢,hessian 將需要序列化的多個(gè)相同的對(duì)象只會(huì)寫入一次巾钉,其他用到該對(duì)象的只使用對(duì)象的引用,而不重新寫入對(duì)象的描述信息和值信息

3.2 protobuf 序列化

Protocol Buffer 是 Google 出品的一種輕量并且高效的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式秘案,性能比 JSON砰苍、XML 要高很多,原因如下:

  1. 它使用 proto 編譯器阱高,自動(dòng)進(jìn)行序列化和反序列化赚导,速度非常快赤惊,比 XML 和 JSON 快大概 20~100 倍
  2. 它的數(shù)據(jù)壓縮效果好辟癌,就是說(shuō)它序列化后的數(shù)據(jù)量體積小。因?yàn)轶w積小荐捻,傳輸起來(lái)帶寬和速度上會(huì)有優(yōu)化,詳見(jiàn)下一節(jié)
    • 使用 Varint 數(shù)字表示方法寡夹,減少用來(lái)表示數(shù)字的字節(jié)數(shù)
    • 一個(gè)負(fù)數(shù)一般會(huì)被表示為一個(gè)很大的整數(shù)处面,因?yàn)橛?jì)算機(jī)定義負(fù)數(shù)的符號(hào)位為數(shù)字的最高位。因此 Protocol Buffer 定義了 sint32 這種類型菩掏,采用 zigzag 編碼魂角,用無(wú)符號(hào)數(shù)來(lái)表示有符號(hào)數(shù)字,實(shí)現(xiàn)了用較少的 Bytes 來(lái)表示負(fù)數(shù)
    • Protocol Buffer 的二進(jìn)制流采用 Key-Value 結(jié)構(gòu)存儲(chǔ)信息智绸,無(wú)需使用分隔符來(lái)分割不同的 Field野揪。對(duì)于可選的 Field访忿,如果消息中不存在該 field,那么在最終的 Message Buffer 中就沒(méi)有該 field斯稳,這個(gè)特性也有助于節(jié)約消息本身的大小

數(shù)據(jù)壓縮方法

Protobuf 序列化后所生成的二進(jìn)制消息非常緊湊海铆,這得益于 Protobuf 采用的非常巧妙的 Encoding 方法

考察消息結(jié)構(gòu)之前,讓我首先要介紹一個(gè)叫做 Varint 的術(shù)語(yǔ)

Varint 是一種緊湊的表示數(shù)字的方法挣惰。它用一個(gè)或多個(gè)字節(jié)來(lái)表示一個(gè)數(shù)字卧斟,值越小的數(shù)字使用越少的字節(jié)數(shù)。這能減少用來(lái)表示數(shù)字的字節(jié)數(shù)

比如對(duì)于 int32 類型的數(shù)字憎茂,一般需要 4 個(gè) Bytes 來(lái)表示珍语。但是采用 Varint,對(duì)于很小的 int32 類型的數(shù)字竖幔,則可以用 1 個(gè) Byte 來(lái)表示板乙。當(dāng)然凡事都有好的也有不好的一面,采用 Varint 表示法拳氢,大的數(shù)字則需要 5 個(gè) Byte 來(lái)表示募逞。從統(tǒng)計(jì)的角度來(lái)說(shuō),一般不會(huì)所有的消息中的數(shù)字都是大數(shù)饿幅,因此大多數(shù)情況下凡辱,采用 Varint 后,可以用更少的字節(jié)數(shù)來(lái)表示數(shù)字信息栗恩。下面就詳細(xì)介紹一下 Varint

Varint 中的每個(gè) Byte 的最高位 bit 有特殊的含義透乾,如果該位為 1,表示后續(xù)的 Byte 也是該數(shù)字的一部分磕秤,如果該位為 0乳乌,則結(jié)束。其他的 7 個(gè) bit 都用來(lái)表示數(shù)字市咆。因此小于 128 的數(shù)字都可以用一個(gè) Byte 表示汉操。大于 128 的數(shù)字,比如 300蒙兰,會(huì)用兩個(gè)字節(jié)來(lái)表示:1010 1100 0000 0010

下圖演示了 Google Protocol Buffer 如何解析兩個(gè) Bytes磷瘤。注意到最終計(jì)算前將兩個(gè) Bytes 的位置相互交換過(guò)一次,這是因?yàn)?Google Protocol Buffer 字節(jié)序采用 little-endian 的方式

image006.jpg

消息經(jīng)過(guò)序列化后會(huì)成為一個(gè)二進(jìn)制數(shù)據(jù)流搜变,該流中的數(shù)據(jù)為一系列的 Key-Value 對(duì)采缚。如下圖所示:

image007.jpg

采用這種 Key-Pair 結(jié)構(gòu)無(wú)需使用分隔符來(lái)分割不同的 Field。對(duì)于可選的 Field挠他,如果消息中不存在該 field扳抽,那么在最終的 Message Buffer 中就沒(méi)有該 field,這些特性都有助于節(jié)約消息本身的大小

以代碼清單 1 中的消息為例。假設(shè)我們生成如下的一個(gè)消息 Test1:

Test1.id = 10; Test1.str = "hello";

則最終的 Message Buffer 中有兩個(gè) Key-Value 對(duì)贸呢,一個(gè)對(duì)應(yīng)消息中的 id镰烧;另一個(gè)對(duì)應(yīng) str

Key 用來(lái)標(biāo)識(shí)具體的 field,在解包的時(shí)候楞陷,Protocol Buffer 根據(jù) Key 就可以知道相應(yīng)的 Value 應(yīng)該對(duì)應(yīng)于消息中的哪一個(gè) field

Key 的定義如下:

(field_number << 3) | wire_type

可以看到 Key 由兩部分組成怔鳖。第一部分是 field_number,比如消息 Test1 中 field id 的 field_number 為 1猜谚。第二部分為 wire_type败砂。表示 Value 的傳輸類型

Wire Type 可能的類型如下表所示:

Type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimi string, bytes, embedded messages, packed repeated fields
3 Start group Groups (deprecated)
4 End group Groups (deprecated)
5 32-bit fixed32, sfixed32, float

在我們的例子當(dāng)中,field id 所采用的數(shù)據(jù)類型為 int32魏铅,因此對(duì)應(yīng)的 wire type 為 0昌犹。細(xì)心的讀者或許會(huì)看到在 Type 0 所能表示的數(shù)據(jù)類型中有 int32 和 sint32 這兩個(gè)非常類似的數(shù)據(jù)類型。Google Protocol Buffer 區(qū)別它們的主要意圖也是為了減少 encoding 后的字節(jié)數(shù)

在計(jì)算機(jī)內(nèi)览芳,一個(gè)負(fù)數(shù)一般會(huì)被表示為一個(gè)很大的整數(shù)斜姥,因?yàn)橛?jì)算機(jī)定義負(fù)數(shù)的符號(hào)位為數(shù)字的最高位。如果采用 Varint 表示一個(gè)負(fù)數(shù)沧竟,那么一定需要 5 個(gè) Bytes(為何是 5 bytes 呢铸敏?)。為此 Google Protocol Buffer 定義了 sint32 這種類型悟泵,采用 zigzag 編碼

Zigzag 編碼用無(wú)符號(hào)數(shù)來(lái)表示有符號(hào)數(shù)字杈笔,正數(shù)和負(fù)數(shù)交錯(cuò),這就是 zigzag 這個(gè)詞的含義了

如圖所示:

使用 zigzag 編碼糕非,絕對(duì)值小的數(shù)字蒙具,無(wú)論正負(fù)都可以采用較少的 Bytes 來(lái)表示

其他的數(shù)據(jù)類型,比如字符串等則采用類似數(shù)據(jù)庫(kù)中的 varchar 的表示方法朽肥,即用一個(gè) varint 表示長(zhǎng)度禁筏,然后將其余部分緊跟在這個(gè)長(zhǎng)度部分之后即可

通過(guò)以上對(duì) protobuf Encoding 方法的介紹,想必您也已經(jīng)發(fā)現(xiàn) protobuf 消息的內(nèi)容小衡招,適于網(wǎng)絡(luò)傳輸篱昔。假如您對(duì)那些有關(guān)技術(shù)細(xì)節(jié)的描述缺乏耐心和興趣,那么下面這個(gè)簡(jiǎn)單而直觀的比較應(yīng)該能給您更加深刻的印象

假設(shè)有一條 helloworld 消息 id=101 str="hello"始腾,用 Protobuf 序列化后的字節(jié)序列為:

08 65 12 06 48 65 6C 6C 6F 77

而如果用 XML州刽,則類似這樣:

31 30 31 3C 2F 69 64 3E 3C 6E 61 6D 65 3E 68 65 
6C 6C 6F 3C 2F 6E 61 6D 65 3E 3C 2F 68 65 6C 6C 
6F 77 6F 72 6C 64 3E 
 
一共 55 個(gè)字節(jié),這些奇怪的數(shù)字需要稍微解釋一下浪箭,其含義用 ASCII 表示如下:
<helloworld> 
    <id>101</id> 
    <name>hello</name> 
</helloworld>

4 線程模型

dubbo-protocol.jpg

如果事件處理的邏輯能迅速完成穗椅,并且不會(huì)發(fā)起新的 IO 請(qǐng)求,比如只是在內(nèi)存中記個(gè)標(biāo)識(shí)山林,則直接在 IO 線程上處理更快,因?yàn)闇p少了線程池調(diào)度

但如果事件處理邏輯較慢,或者需要發(fā)起新的 IO 請(qǐng)求驼抹,比如需要查詢數(shù)據(jù)庫(kù)桑孩,則必須派發(fā)到線程池,否則 IO 線程阻塞框冀,將導(dǎo)致不能接收其它請(qǐng)求

因此流椒,需要通過(guò)不同的派發(fā)策略和不同的線程池配置的組合來(lái)應(yīng)對(duì)不同的場(chǎng)景:

<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />

Dispatcher

  • all 所有消息都派發(fā)到線程池,包括請(qǐng)求明也,響應(yīng)宣虾,連接事件,斷開(kāi)事件温数,心跳等(默認(rèn)設(shè)置)
  • direct 所有消息都不派發(fā)到線程池绣硝,全部在 IO 線程上直接執(zhí)行
  • message 只有請(qǐng)求響應(yīng)消息派發(fā)到線程池,其它連接斷開(kāi)事件撑刺,心跳等消息鹉胖,直接在 IO 線程上執(zhí)行
  • execution 只有請(qǐng)求消息派發(fā)到線程池,不含響應(yīng)够傍,響應(yīng)和其它連接斷開(kāi)事件甫菠,心跳等消息,直接在 IO 線程上執(zhí)行
  • connection 在 IO 線程上冕屯,將連接斷開(kāi)事件放入隊(duì)列寂诱,有序逐個(gè)執(zhí)行,其它消息派發(fā)到線程池

ThreadPool

  • fixed 固定大小線程池安聘,啟動(dòng)時(shí)建立線程痰洒,不關(guān)閉,一直持有(默認(rèn)設(shè)置)
  • cached 緩存線程池搞挣,空閑一分鐘自動(dòng)刪除带迟,需要時(shí)重建
  • limited 可伸縮線程池,但池中的線程數(shù)只會(huì)增長(zhǎng)不會(huì)收縮囱桨。只增長(zhǎng)不收縮的目的是為了避免收縮時(shí)突然來(lái)了大流量引起的性能問(wèn)題
  • eager 優(yōu)先創(chuàng)建Worker線程池仓犬。在任務(wù)數(shù)量大于corePoolSize但是小于maximumPoolSize時(shí),優(yōu)先創(chuàng)建Worker來(lái)處理任務(wù)舍肠。當(dāng)任務(wù)數(shù)量大于maximumPoolSize時(shí)搀继,將任務(wù)放入阻塞隊(duì)列中。阻塞隊(duì)列充滿時(shí)拋出RejectedExecutionException翠语。(相比于cached:cached在任務(wù)數(shù)量超過(guò)maximumPoolSize時(shí)直接拋出異常而不是將任務(wù)放入阻塞隊(duì)列)

Dubbo 集群容錯(cuò)

1 集群容錯(cuò)概述

為了避免單點(diǎn)故障叽躯,現(xiàn)在的應(yīng)用通常至少會(huì)部署在兩臺(tái)服務(wù)器上。對(duì)于一些負(fù)載比較高的服務(wù)肌括,會(huì)部署更多的服務(wù)器点骑。這樣,在同一環(huán)境下的服務(wù)提供者數(shù)量會(huì)大于 1。對(duì)于服務(wù)消費(fèi)者來(lái)說(shuō)黑滴,同一環(huán)境下出現(xiàn)了多個(gè)服務(wù)提供者憨募,這時(shí)會(huì)出現(xiàn)一個(gè)問(wèn)題,服務(wù)消費(fèi)者需要決定選擇哪個(gè)服務(wù)提供者進(jìn)行調(diào)用袁辈。另外服務(wù)調(diào)用失敗時(shí)的處理措施也是需要考慮的菜谣,是重試呢,還是拋出異常晚缩,亦或是只打印異常尾膊。為了處理這些問(wèn)題,Dubbo 定義了集群容錯(cuò)接口 ClusterClusterInvoker

集群 Cluster 用途是將多個(gè)服務(wù)提供者合并為一個(gè) Cluster Invoker荞彼,并將這個(gè) Invoker 暴露給服務(wù)消費(fèi)者冈敛。這樣一來(lái),服務(wù)消費(fèi)者只需通過(guò)這個(gè) Invoker 進(jìn)行遠(yuǎn)程調(diào)用即可卿泽,至于具體調(diào)用哪個(gè)服務(wù)提供者莺债,以及調(diào)用失敗后如何處理等問(wèn)題,現(xiàn)在都交給集群模塊去處理

集群模塊是服務(wù)提供者和服務(wù)消費(fèi)者的中間層签夭,為服務(wù)消費(fèi)者屏蔽了服務(wù)提供者的情況齐邦,這樣服務(wù)消費(fèi)者就可以專心處理遠(yuǎn)程調(diào)用相關(guān)事宜。比如發(fā)請(qǐng)求第租,接受服務(wù)提供者返回的數(shù)據(jù)等措拇。這就是集群的作用

總體來(lái)說(shuō),Cluster 作為一個(gè)集群容錯(cuò)層慎宾,其核心接口為 ClusterClusterInvoker丐吓。每一個(gè)不同的容錯(cuò)機(jī)制都繼承 Cluster 接口進(jìn)行不同的實(shí)現(xiàn),每個(gè)實(shí)現(xiàn)類中都需要調(diào)用 join 方法創(chuàng)建一個(gè)對(duì)應(yīng)的 ClusterInvoker 實(shí)現(xiàn)類趟据。然后調(diào)用 ClusterInvokerinvoke 方法進(jìn)行具體的調(diào)用流程券犁。以 FailoverCluster 為例,其實(shí)現(xiàn)代碼如下:

public class FailoverCluster implements Cluster {
    public static final String NAME = "failover";

    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker(directory);
    }
}

整體過(guò)程如下圖所示:

我們可以看到集群工作過(guò)程可分為兩個(gè)階段:

第一個(gè)階段是在服務(wù)消費(fèi)者初始化期間汹碱,集群 Cluster 實(shí)現(xiàn)類為服務(wù)消費(fèi)者創(chuàng)建 Cluster Invoker 實(shí)例粘衬,即上圖中的 merge 操作。

第二個(gè)階段是在服務(wù)消費(fèi)者進(jìn)行遠(yuǎn)程調(diào)用時(shí)咳促。以 FailoverClusterInvoker 為例:

  1. FailoverClusterInvoker 首先會(huì)調(diào)用 Directory#list 方法列舉 Invoker 列表(可將 Invoker 簡(jiǎn)單理解為服務(wù)提供者)稚新。Directory 的用途是保存 Invoker,可簡(jiǎn)單類比為 List<Invoker>跪腹。其實(shí)現(xiàn)類 RegistryDirectory 是一個(gè)動(dòng)態(tài)服務(wù)目錄褂删,可感知注冊(cè)中心配置的變化,它所持有的 Invoker 列表會(huì)隨著注冊(cè)中心內(nèi)容的變化而變化冲茸。每次變化后屯阀,RegistryDirectory 會(huì)動(dòng)態(tài)增刪 Invoker
  2. FailoverClusterInvoker 調(diào)用 Routerroute 方法進(jìn)行路由诵盼,過(guò)濾掉不符合路由規(guī)則的 Invoker
  3. FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后惜浅,它會(huì)通過(guò) LoadBalanceInvoker 列表中選擇一個(gè) Invoker
  4. FailoverClusterInvoker 將調(diào)用參數(shù)傳給 LoadBalance 選擇出的 Invoker 實(shí)例的 invoke 方法俄讹,進(jìn)行真正的遠(yuǎn)程調(diào)用

2 容錯(cuò)機(jī)制實(shí)現(xiàn)

Dubbo 中的容錯(cuò)機(jī)制有以下幾種忍些,在 dubbo:service堡僻、dubbo:reference嫉晶、dubbo:provider天吓、dubbo:consumercluster 屬性中配置

機(jī)制 機(jī)制簡(jiǎn)介
Failover 當(dāng)調(diào)用失敗時(shí)會(huì)重試其他服務(wù)器蘸际。用戶可以通過(guò) retries="2" 設(shè)置重試次數(shù)祭陷。這是 Dubbo 的默認(rèn)容錯(cuò)機(jī)制苍凛,會(huì)做負(fù)載均衡通常使用在讀操作或冪等操作上兵志,但會(huì)導(dǎo)致接口延遲增大醇蝴,容易加重下游服務(wù)的負(fù)載
Failfast 當(dāng)請(qǐng)求失敗后,快速返回異常結(jié)果想罕,不作任何重試悠栓。會(huì)對(duì)請(qǐng)求做負(fù)載均衡通常使用在非冪等操作上
Failsafe 請(qǐng)求出現(xiàn)異常時(shí)按价,直接忽略惭适。會(huì)對(duì)請(qǐng)求做負(fù)載均衡,不關(guān)心調(diào)用是否成功楼镐,不拋出異常影響外層調(diào)用癞志。適用于不重要的服務(wù)場(chǎng)景(如日志)
Failback 請(qǐng)求失敗后,會(huì)將失敗請(qǐng)求記錄在失敗隊(duì)列中框产,并有一個(gè)定時(shí)線程池定時(shí)重試凄杯,適用于異步或最終一致性的服務(wù)場(chǎng)景,會(huì)做負(fù)載均衡
Forking 同時(shí)調(diào)用多個(gè)相同的服務(wù)秉宿,只要其中一個(gè)返回就立即返回結(jié)果戒突。用戶可以設(shè)置 fork 屬性確定最大并行調(diào)用數(shù)量。通常使用在對(duì)接口實(shí)時(shí)性要求極高的調(diào)用上描睦,但會(huì)浪費(fèi)更多資源
Broadcast 廣播調(diào)用所有服務(wù)膊存,任意一個(gè)節(jié)點(diǎn)失敗則報(bào)錯(cuò),不用負(fù)載均衡
Available 遍歷所有服務(wù)列表酌摇,找到第一個(gè)可用節(jié)點(diǎn)膝舅,直接請(qǐng)求并返回結(jié)果,不做負(fù)載均衡
Mergeable 自動(dòng)將多個(gè)節(jié)點(diǎn)請(qǐng)求得到的結(jié)果進(jìn)行合并
Mock

2.1 Failover 策略

該策略的邏輯如下:

  1. 校驗(yàn)從 AbstractClusterInvoker 傳入的 Invoker 列表是否為空
  2. 獲取配置參數(shù)窑多,如 retries
  3. 初始化一些集合和對(duì)象仍稀,用于保存過(guò)程中出現(xiàn)的異常、記錄調(diào)用哪些節(jié)點(diǎn)
  4. 使用 for 循環(huán)實(shí)現(xiàn)充實(shí)埂息,循環(huán)次數(shù)就是重試次數(shù)技潘。成功則返回遥巴,否則繼續(xù)循環(huán)。如果循環(huán)結(jié)束仍沒(méi)有一個(gè)成功的返回享幽,則將(3)中的記錄拋出
    • 校驗(yàn) Invoker 列表是否為空
    • 負(fù)載均衡
    • 遠(yuǎn)程調(diào)用

2.2 Failfast 策略

該策略的邏輯如下:

  1. 校驗(yàn)從 AbstractClusterInvoker 傳入的 Invoker 列表是否為空
  2. 負(fù)載均衡
  3. 遠(yuǎn)程調(diào)用铲掐,在 try 中調(diào)用 Invoker#invoke 方法,若捕獲到異常則封裝成 RpcException 拋出
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    this.checkInvokers(invokers, invocation);
    Invoker invoker = this.select(loadbalance, invocation, invokers, (List)null);
    try {
        return invoker.invoke(invocation);
    } catch (Throwable var6) {
        if (var6 instanceof RpcException && ((RpcException)var6).isBiz()) {
            throw (RpcException)var6;
        } else {
            throw new RpcException...;
        }
    }
}

2.3 Failsafe 策略

該策略的邏輯如下:

  1. 校驗(yàn)從 AbstractClusterInvoker 傳入的 Invoker 列表是否為空
  2. 負(fù)載均衡
  3. 遠(yuǎn)程調(diào)用值桩,在 try 中調(diào)用 Invoker#invoke 方法摆霉,若捕獲到異常則直接吞掉,返回一個(gè)結(jié)果集
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    try {
        this.checkInvokers(invokers, invocation);
        Invoker<T> invoker = this.select(loadbalance, invocation, invokers, (List)null);
        return invoker.invoke(invocation);
    } catch (Throwable var5) {
        logger.error("Failsafe ignore exception: " + var5.getMessage(), var5);
        return AsyncRpcResult.newDefaultAsyncResult((Object)null, (Throwable)null, invocation);
    }
}

2.4 Failback 策略

若調(diào)用失敗奔坟,則定期重試携栋。FailbackClusterInvoker 中定義了一個(gè) ConcurrentHashMap 用來(lái)保存失敗調(diào)用請(qǐng)求。另外定義了一個(gè)定時(shí)線程池咳秉,默認(rèn)每 5s 把所有的失敗調(diào)用拿出來(lái)重試婉支。若調(diào)用成功則將請(qǐng)求從 ConcurrentHashMap 中移除

該策略的邏輯如下:

  1. 校驗(yàn)從 AbstractClusterInvoker 傳入的 Invoker 列表是否為空
  2. 負(fù)載均衡
  3. 遠(yuǎn)程調(diào)用,在 try 中調(diào)用 Invoker#invoke 方法澜建,若捕獲到異常則將調(diào)用請(qǐng)求保存到 ConcurrentHashMap向挖,并返回一個(gè)空結(jié)果集
  4. 定時(shí)線程池每 5s 將 ConcurrentHashMap 中的失敗請(qǐng)求進(jìn)行重新請(qǐng)求,請(qǐng)求成功則從 ConcurrentHashMap 中移除炕舵。若還是請(qǐng)求失敗則異常也會(huì)被捕獲何之,防止中斷 ConcurrentHashMap 后面的重試

2.5 Available 策略

該策略的邏輯如下:

  1. 遍歷 AbstractClusterInvoker 傳入的 Invoker 列表,找到第一個(gè)可用的服務(wù)直接調(diào)用并返回
  2. 如果遍歷后沒(méi)有找到可用的 Invoker咽筋,則拋出異常

2.6 Broadcast 策略

該策略的邏輯如下:

  1. 校驗(yàn)從 AbstractClusterInvoker 傳入的 Invoker 列表是否為空
  2. 初始化一些集合和對(duì)象帝美,用于保存過(guò)程中出現(xiàn)的異常和結(jié)果信息
  3. 循環(huán)遍歷 Invoker 列表,直接 RPC 調(diào)用晤硕。任何一個(gè)節(jié)點(diǎn)出錯(cuò)不中整個(gè)廣播過(guò)程悼潭,會(huì)先記錄異常,在最后廣播完成后再拋出舞箍。最后一個(gè)節(jié)點(diǎn)的異常會(huì)覆蓋前面節(jié)點(diǎn)的異常信息

2.7 Forking 策略

該策略的邏輯如下:

  1. 校驗(yàn)從 AbstractClusterInvoker 傳入的 Invoker 列表是否為空舰褪;初始化一個(gè) Invoker 集合,用于保存真正要調(diào)用的 Invoker 列表疏橄;從 URL 中獲取最大并行數(shù)占拍、超時(shí)時(shí)間

  2. 獲取最終要調(diào)用的 Invoker 列表。假設(shè)用戶設(shè)置的最大并行數(shù)是 n捎迫,實(shí)際可調(diào)用最大服務(wù)數(shù)為 v晃酒。若 n < 0 or n > v,說(shuō)明可用服務(wù)少于用戶設(shè)置窄绒,則最終調(diào)用的服務(wù)只能有 v 個(gè)贝次;若 n < v,則循環(huán)調(diào)用 n 次負(fù)載均衡方法彰导,獲取 Invoker 加入 Invoker 列表中

    注意:Invoker 加入 Invoker 列表中會(huì)進(jìn)行去重操作

  3. 初始化一個(gè)阻塞隊(duì)列和一個(gè)異常計(jì)數(shù)器

  4. 執(zhí)行調(diào)用蛔翅。循環(huán)使用線程池并行調(diào)用敲茄,調(diào)用成功則將成功結(jié)果放入阻塞隊(duì)列中;調(diào)用失敗山析,則異常計(jì)數(shù)器 + 1堰燎。若所有線程的調(diào)動(dòng)都失敗了,即異常計(jì)數(shù) ≥ 可調(diào)用 Invoker 數(shù)時(shí)笋轨,講異常信息放入阻塞隊(duì)列

    并行調(diào)用如何保證個(gè)別調(diào)用的失敗不返回異常信息秆剪,只有全部調(diào)用失敗財(cái)才返回異常信息呢?答案在于只有當(dāng)異常計(jì)數(shù) ≥ 可調(diào)用 Invoker 數(shù)時(shí)爵政,才會(huì)將異常信息放入阻塞隊(duì)列

  5. 主線程同步等待結(jié)果鸟款。主線程使用阻塞隊(duì)列的 poll(timeout) 方法,同步等待阻塞隊(duì)列中的一個(gè)結(jié)果茂卦,若是正常結(jié)果則返回,否則拋出異常

    從這點(diǎn)可知组哩,F(xiàn)orking 的超時(shí)是通過(guò)在阻塞隊(duì)列的 poll 方法中傳入超時(shí)時(shí)間實(shí)現(xiàn)的

3 Directory 實(shí)現(xiàn)

todo

4 路由機(jī)制實(shí)現(xiàn)

todo

5 負(fù)載均衡實(shí)現(xiàn)

Dubbo 提供了4種負(fù)載均衡實(shí)現(xiàn)等龙,分別是:

  • 基于權(quán)重隨機(jī)算法的 RandomLoadBalance
  • 基于最少活躍調(diào)用數(shù)算法的 LeastActiveLoadBalance
  • 基于 hash 一致性的 ConsistentHashLoadBalance
  • 基于加權(quán)輪詢算法的 RoundRobinLoadBalance

5.1 RandomLoadBalance

RandomLoadBalance 是加權(quán)隨機(jī)算法的具體實(shí)現(xiàn),它的算法思想很簡(jiǎn)單伶贰。假設(shè)我們有一組服務(wù)器 servers = [A, B, C]蛛砰,他們對(duì)應(yīng)的權(quán)重為 weights = [5, 3, 2],權(quán)重總和為10∈蜓茫現(xiàn)在把這些權(quán)重值平鋪在一維坐標(biāo)值上泥畅,[0, 5) 區(qū)間屬于服務(wù)器 A,[5, 8) 區(qū)間屬于服務(wù)器 B琅翻,[8, 10) 區(qū)間屬于服務(wù)器 C位仁。接下來(lái)通過(guò)隨機(jī)數(shù)生成器生成一個(gè)范圍在 [0, 10) 之間的隨機(jī)數(shù),然后計(jì)算這個(gè)隨機(jī)數(shù)會(huì)落到哪個(gè)區(qū)間上方椎。比如數(shù)字3會(huì)落到服務(wù)器 A 對(duì)應(yīng)的區(qū)間上聂抢,此時(shí)返回服務(wù)器 A 即可。權(quán)重越大的機(jī)器棠众,在坐標(biāo)軸上對(duì)應(yīng)的區(qū)間范圍就越大琳疏,因此隨機(jī)數(shù)生成器生成的數(shù)字就會(huì)有更大的概率落到此區(qū)間內(nèi)。只要隨機(jī)數(shù)生成器產(chǎn)生的隨機(jī)數(shù)分布性很好闸拿,在經(jīng)過(guò)多次選擇后空盼,每個(gè)服務(wù)器被選中的次數(shù)比例接近其權(quán)重比例。

生成隨機(jī)數(shù)使用了 ThreadLocalRandom新荤,它的性能比 Random 好一點(diǎn)揽趾。因?yàn)?Random 生成隨機(jī)數(shù)時(shí),為了保證線程安全性苛骨,使用了 CAS 操作保證每次只有一個(gè)線程可以獲取并更新 seed但骨。獲取失敗的線程將自旋重試励七,因此多線程下會(huì)因競(jìng)爭(zhēng)同一個(gè) seed 導(dǎo)致性能下降

ThreadLocalRandom 則為每個(gè)線程維護(hù)了一個(gè) seed,這樣就可以有效避免 seed 競(jìng)爭(zhēng)

5.2 LeastActiveLoadBalance

LeastActiveLoadBalance 翻譯過(guò)來(lái)是<font color='red'>最小活躍數(shù)負(fù)載均衡 </font>奔缠÷犹В活躍調(diào)用數(shù)越小,表明該服務(wù)提供者效率越高校哎,單位時(shí)間內(nèi)可處理更多的請(qǐng)求两波。此時(shí)應(yīng)優(yōu)先將請(qǐng)求分配給該服務(wù)提供者。在具體實(shí)現(xiàn)中闷哆,每個(gè)服務(wù)提供者對(duì)應(yīng)一個(gè)活躍數(shù) active腰奋。初始情況下,所有服務(wù)提供者活躍數(shù)均為0抱怔。每收到一個(gè)請(qǐng)求劣坊,活躍數(shù)加1,完成請(qǐng)求后則將活躍數(shù)減1屈留。在服務(wù)運(yùn)行一段時(shí)間后局冰,性能好的服務(wù)提供者處理請(qǐng)求的速度更快,因此活躍數(shù)下降的也越快灌危,此時(shí)這樣的服務(wù)提供者能夠優(yōu)先獲取到新的服務(wù)請(qǐng)求康二、這就是最小活躍數(shù)負(fù)載均衡算法的基本思想

5.3 ConsistentHashLoadBalance

一致性 hash 算法由麻省理工學(xué)院的 Karger 及其合作者于1997年提出的,算法提出之初是用于大規(guī)模緩存系統(tǒng)的負(fù)載均衡勇蝙。它的工作過(guò)程是這樣的沫勿,首先根據(jù) ip 或者其他的信息為緩存節(jié)點(diǎn)生成一個(gè) hash,并將這個(gè) hash 投射到 [0, 232 - 1] 的圓環(huán)上味混。當(dāng)有查詢或?qū)懭胝?qǐng)求時(shí)产雹,則為緩存項(xiàng)的 key 生成一個(gè) hash 值。然后查找第一個(gè)大于或等于該 hash 值的緩存節(jié)點(diǎn)翁锡,并到這個(gè)節(jié)點(diǎn)中查詢或?qū)懭刖彺骓?xiàng)洽故。如果當(dāng)前節(jié)點(diǎn)掛了,則在下一次查詢或?qū)懭刖彺鏁r(shí)盗誊,為緩存項(xiàng)查找另一個(gè)大于其 hash 值的緩存節(jié)點(diǎn)即可时甚。

比如下面綠色點(diǎn)對(duì)應(yīng)的緩存項(xiàng)將會(huì)被存儲(chǔ)到 cache-2 節(jié)點(diǎn)中。由于 cache-3 掛了哈踱,原本應(yīng)該存到該節(jié)點(diǎn)中的緩存項(xiàng)最終會(huì)存儲(chǔ)到 cache-4 節(jié)點(diǎn)中

下面來(lái)看看一致性 hash 在 Dubbo 中的應(yīng)用荒适。我們把上圖的緩存節(jié)點(diǎn)替換成 Dubbo 的服務(wù)提供者,于是得到了下圖:

這里相同顏色的節(jié)點(diǎn)均屬于同一個(gè)服務(wù)提供者开镣,比如 Invoker1-1刀诬,Invoker1-2,……, Invoker1-160邪财。這樣做的目的是通過(guò)引入虛擬節(jié)點(diǎn)陕壹,讓 Invoker 在圓環(huán)上分散開(kāi)來(lái)质欲,避免數(shù)據(jù)傾斜問(wèn)題。所謂數(shù)據(jù)傾斜是指糠馆,由于節(jié)點(diǎn)不夠分散嘶伟,導(dǎo)致大量請(qǐng)求落到了同一個(gè)節(jié)點(diǎn)上,而其他節(jié)點(diǎn)只會(huì)接收到了少量請(qǐng)求的情況又碌。比如:

如上九昧,由于 Invoker-1 和 Invoker-2 在圓環(huán)上分布不均,導(dǎo)致系統(tǒng)中75%的請(qǐng)求都會(huì)落到 Invoker-1 上毕匀,只有 25% 的請(qǐng)求會(huì)落到 Invoker-2 上铸鹰。解決這個(gè)問(wèn)題辦法是引入虛擬節(jié)點(diǎn),通過(guò)虛擬節(jié)點(diǎn)均衡各個(gè)節(jié)點(diǎn)的請(qǐng)求量

5.4 RoundRobinLoadBalance

我們來(lái)看一下 Dubbo 中加權(quán)輪詢負(fù)載均衡的實(shí)現(xiàn) RoundRobinLoadBalance皂岔。在詳細(xì)分析源碼前蹋笼,我們先來(lái)了解一下什么是加權(quán)輪詢。這里從最簡(jiǎn)單的輪詢開(kāi)始講起

所謂輪詢是指將請(qǐng)求輪流分配給每臺(tái)服務(wù)器躁垛。舉個(gè)例子剖毯,我們有三臺(tái)服務(wù)器 A、B缤苫、C。我們將第一個(gè)請(qǐng)求分配給服務(wù)器 A墅拭,第二個(gè)請(qǐng)求分配給服務(wù)器 B活玲,第三個(gè)請(qǐng)求分配給服務(wù)器 C,第四個(gè)請(qǐng)求再次分配給服務(wù)器 A谍婉。這個(gè)過(guò)程就叫做輪詢舒憾。輪詢是一種無(wú)狀態(tài)負(fù)載均衡算法,實(shí)現(xiàn)簡(jiǎn)單穗熬,適用于每臺(tái)服務(wù)器性能相近的場(chǎng)景下镀迂。

現(xiàn)實(shí)情況下,我們并不能保證每臺(tái)服務(wù)器性能均相近唤蔗。如果我們將等量的請(qǐng)求分配給性能較差的服務(wù)器探遵,這顯然是不合理的。因此妓柜,這個(gè)時(shí)候我們需要對(duì)輪詢過(guò)程進(jìn)行加權(quán)箱季,以調(diào)控每臺(tái)服務(wù)器的負(fù)載。經(jīng)過(guò)加權(quán)后棍掐,每臺(tái)服務(wù)器能夠得到的請(qǐng)求數(shù)比例藏雏,接近或等于他們的權(quán)重比。比如服務(wù)器 A作煌、B掘殴、C 權(quán)重比為 5:2:1赚瘦。那么在8次請(qǐng)求中,服務(wù)器 A 將收到其中的5次請(qǐng)求奏寨,服務(wù)器 B 會(huì)收到其中的2次請(qǐng)求起意,服務(wù)器 C 則收到其中的1次請(qǐng)求

涉及到平滑輪詢算法,有興趣研究一下

Dubbo 擴(kuò)展點(diǎn)加載機(jī)制

1 Java SPI

SPI 全稱 service Provider Interface

Java SPI 使用了策略模式服爷,一個(gè)接口多種實(shí)現(xiàn)杜恰。具體步驟如下:

  1. 定義一個(gè)接口和對(duì)應(yīng)的方法
  2. 編寫接口的實(shí)現(xiàn)類
  3. 在 META-INF/services/ 目錄下創(chuàng)建一個(gè)以接口全路徑名命名的文件,如 com.test.interface.Service
  4. 在文件中寫入具體實(shí)現(xiàn)類的全路徑名仍源,若有多個(gè)則用分行符分離心褐,如 com.test.interface.impl.ServiceImpl
  5. 在代碼中通過(guò) ServiceLoader 來(lái)加載具體的實(shí)現(xiàn)類

其缺點(diǎn)是:

  • JDK 自帶的 SPI 會(huì)一次性實(shí)例化所有擴(kuò)展點(diǎn)實(shí)現(xiàn),如果擴(kuò)展點(diǎn)不使用笼踩,那么會(huì)浪費(fèi)資源
  • 在擴(kuò)展點(diǎn)加載失敗的情況下逗爹,JDK 擴(kuò)展點(diǎn)加載機(jī)制無(wú)法提供擴(kuò)展點(diǎn)加載失敗的真正原因
  • JDK 自帶 SPI 不支持 IOC 和 AOP 的功能

2 Dubbo SPI

相比 Java SPIDubbo SPI 做了一些改進(jìn)和優(yōu)化:

  • 相對(duì)于 Java SPI 一次性加載所有實(shí)現(xiàn)嚎于,Dubbo SPI 是按需加載掘而,只加載配置文件中的類,并分成不同的種類緩存在內(nèi)存中于购,不會(huì)立即初始化袍睡,在性能上有更好的表現(xiàn)
  • 更為詳細(xì)的擴(kuò)展加載失敗信息
  • 增加了對(duì)擴(kuò)展 IOC 和 AOP的支持。一個(gè)擴(kuò)展類可以直接 setter 注入其他擴(kuò)展

Dubbo SPI 還兼容了 Java SPI 的配置路徑和內(nèi)容配置方式肋僧。在Dubbo啟動(dòng)的時(shí)候斑胜,會(huì)默認(rèn)掃描這三個(gè)目錄下的配置文件:META-INF/servicesMETA-INF/dubbo/嫌吠、META-INF/dubbo/internal

Dubbo SPI 配置規(guī)范如下表:

規(guī)范名 規(guī)范說(shuō)明
SPI配置文件路徑 META-INF/services止潘、META-INF/dubbo/、META-INF/dubbo/internal
SPI配置文件名稱 全路徑類名
文件內(nèi)容格式 key=value辫诅,多個(gè)用換行符分隔
2.1 分類與緩存

Dubbo SPI 可分為 Class 緩存實(shí)例緩存凭戴。這兩種緩存又根據(jù)擴(kuò)展類的分類分為 普通擴(kuò)展類包裝擴(kuò)展類自適應(yīng)擴(kuò)展類

  • Class 緩存:Dubbo SPI 獲取擴(kuò)展類時(shí)炕矮,先從緩存中讀取么夫,若緩存中不存在則加載配置文件,根據(jù)配置將 Class 緩存到內(nèi)存中肤视,并不會(huì)全部初始化
  • 實(shí)例緩存:基于性能考慮魏割,Dubbo SPI 也將 Class 初始化后的實(shí)例進(jìn)行緩存。每次獲取實(shí)例會(huì)先在緩存中查找钢颂,若緩存中不存在則重新加載并進(jìn)行緩存钞它。這也是 Dubbo SPI 性能優(yōu)于 Java SPI 的原因,即 Dubbo SPI 緩存的 Class 是按需實(shí)例化的
2.2 擴(kuò)展點(diǎn)注解

@SPI

@Adaptive

3 ExtensionLoader 的工作原理

ExtensionLoader 的邏輯入口可以分為 getExtensiongetAdptiveExtension遭垛、getActivateExtension 三個(gè)尼桶,分別是獲取普通擴(kuò)展類、獲取自適應(yīng)擴(kuò)展類锯仪、獲取自動(dòng)激活的擴(kuò)展類泵督。總體邏輯都是從調(diào)用這三個(gè)方法開(kāi)始的庶喜,每個(gè)方法可能會(huì)有不同的重載方法小腊,根據(jù)不同的傳入?yún)?shù)進(jìn)行調(diào)整:

3.1 getExtension
3.2 getAdaptiveExtension

getAdaptiveExtension 方法中,會(huì)為擴(kuò)展點(diǎn)接口自動(dòng)生成實(shí)現(xiàn)類字符串久窟,實(shí)現(xiàn)類主要包含以下邏輯:

  1. 為接口中每個(gè)有 @Adaptive 注解的方法生成默認(rèn)實(shí)現(xiàn)(沒(méi)有注解的方法則生成空實(shí)現(xiàn))
  2. 每個(gè)默認(rèn)實(shí)現(xiàn)都會(huì)從 URL 中提取 Adaptive 參數(shù)值秩冈,并以此為依據(jù)動(dòng)態(tài)加載擴(kuò)展點(diǎn)
  3. 使用不同的編譯器,把實(shí)現(xiàn)類字符串編譯為自適應(yīng)類并返回斥扛,詳見(jiàn)第 4 節(jié)

4 擴(kuò)展點(diǎn)動(dòng)態(tài)編譯實(shí)現(xiàn)

Dubbo SPI 使用了生成字符串代碼再編譯為 Class 的方式入问,支持字符串代碼編譯的有 JDK 編譯器 和 Javassist 編譯器

Dubbo 中有三種代碼編譯器,分別是 JDK 編譯器稀颁、Javassist 編譯器AdaptiveCompiler 編譯器芬失。這幾種編譯器都實(shí)現(xiàn)了 Compiler 接口,編譯器類之間的關(guān)系如下圖:

Compiler 接口上含有一個(gè) @SPI 注解匾灶,注解的默認(rèn)值是 @SPI("javassist")棱烂,很明顯,Javassist 編譯器將作為默認(rèn)編譯器阶女。如果用戶想改變默認(rèn)編譯器颊糜,則可以通過(guò) <dubbo:application compiler="jdk" /> 標(biāo)簽進(jìn)行設(shè)置

AdaptiveCompiler上面有 @Adaptive 注解,說(shuō)明 AdaptiveCompiler 會(huì)固定為默認(rèn)實(shí)現(xiàn)张肾,這個(gè) Compiler 的主要作用和 AdaptiveExtensionFactory 相似芭析,就是為了管理其他 compiler

@Adaptive
public class AdaptiveCompiler implements Compiler {
    ... 
    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }
    public Class<?> compile(String code, ClassLoader classLoader) {
        ...
        return compiler.compile(code, classLoader);
    }
}

AdaptiveCompiler#setDefaultCompiler 方法會(huì)在 ApplicationConfig 中被調(diào)用锚扎,也就是 Dubbo 在啟動(dòng)時(shí)吞瞪,會(huì)解析 <dubbo:application compiper="jdk" /> 標(biāo)簽,獲取設(shè)置的值驾孔,初始化對(duì)應(yīng)的編譯器芍秆。如果沒(méi)有標(biāo)簽設(shè)置,則使用 @SPI("javassist") 中的設(shè)置翠勉,即 javassistCompiler

4.1 Javassist 動(dòng)態(tài)代碼編譯

Javassist 動(dòng)態(tài)代碼編譯示例:

@Test
public void test_javassist() throws CannotCompileException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    //初始化Javassist類池
    ClassPool classPool = ClassPool.getDefault();
    //創(chuàng)建一個(gè)Hello World類
    CtClass ctClass = classPool.makeClass("Hello World");
    //添加一個(gè)test方法妖啥,會(huì)打印Hello World,直接傳入方法的字符串
    CtMethod method = CtMethod.make("" +
            "public static void test(){" +
            "System.out.println(\"Hello World\");" +
            "}", ctClass);
    ctClass.addMethod(method);
    //生成類
    Class aClass = ctClass.toClass();
    //通過(guò)反射調(diào)用這個(gè)類實(shí)例
    Object object = aClass.newInstance();
    Method m = aClass.getDeclaredMethod("test", null);
    m.invoke(object, null);
}

由于之前已經(jīng)生成了代碼字符串对碌,因此在 JavassistCompiler 中荆虱,就是不斷通過(guò)正則表達(dá)式匹配不同部位的代碼,然后調(diào)用 Javassist 庫(kù)中的 API 生成不同部位的代碼,最后得到一個(gè)完整的 Class 對(duì)象

具體步驟如下:

  1. 初始化 Javassist怀读,設(shè)置默認(rèn)參數(shù)诉位,如設(shè)置當(dāng)前的classpath
  2. 通過(guò)正則匹配出所有 import 的包,并使用 Javassist 添加 import
  3. 通過(guò)正則匹配出所有 extends 的包菜枷,創(chuàng)建 Class 對(duì)象苍糠,并使用 Javassist 添加 extends
  4. 通過(guò)正則匹配出所有 implements 包,并使用 Javassist 添加 implements
  5. 通過(guò)正則匹配出類里面所有內(nèi)容啤誊,即得到 {} 中的內(nèi)容岳瞭,再通過(guò)正則匹配出所有方法,并使用 Javassist 添加類方法
  6. 生成 Class 對(duì)象
4.2 JDK 動(dòng)態(tài)代碼編譯

JDKCompiler 是 Dubbo 編譯器的另一種實(shí)現(xiàn)蚊锹,使用了 JDK 自帶的編譯器瞳筏,原生JDK編譯器包位于 java.tools 下。主要使用了三個(gè)東西:JavaFileObject 接口枫耳、ForwardingJavaFileManager 接口乏矾、JavaCompiler.CompilationTask 方法

整個(gè)動(dòng)態(tài)編譯過(guò)程可以簡(jiǎn)單地總結(jié)為:首先初始化一個(gè) JavaFileObject 對(duì)象,并把字符串作為參數(shù)傳入構(gòu)造方法迁杨,然后調(diào)用 JavaCompiler.CompilationTask 方法編譯出具體的類钻心。JavaFileManager 負(fù)責(zé)管理類文件輸入/輸出的位置

  1. JavaFileObject接口:字符串代碼會(huì)被包裝成一個(gè)文件對(duì)象,并提供獲取二進(jìn)制流的接口铅协。Dubbo框架中的JavaFileObjectImpl類可以看做該接口的一種擴(kuò)展實(shí)現(xiàn)捷沸,構(gòu)造方法中需要傳入生成好的字符串代碼,此文件對(duì)象的輸入和輸入都是ByteArray流狐史。
  2. JavaFileManager接口: 主要管理文件的讀取和輸出位置痒给。JDK中沒(méi)有可以直接使用的實(shí)現(xiàn)類,唯一的實(shí)現(xiàn)鱷梨ForwardingJavaFileManager構(gòu)造器又是protect類型骏全。因此Dubbo中定制化實(shí)現(xiàn)了一個(gè)JavaFileManagerImpl類苍柏,并通過(guò)一個(gè)自定義類加載器ClassLoaderImpl完成資源加載。
  3. JavaCompiler.CompilationTask:把JavaFileObject對(duì)象編譯層具體的類

Dubbo 配置覆蓋優(yōu)先級(jí)

以 timeout 為例姜贡,下圖顯示了配置的查找順序试吁,其它 retries, loadbalance, actives 等類似:

  • 方法級(jí)優(yōu)先,接口級(jí)次之楼咳,全局配置再次之
  • 如果級(jí)別一樣熄捍,則消費(fèi)方優(yōu)先,提供方次之

其中母怜,服務(wù)提供方配置余耽,通過(guò) URL 經(jīng)由注冊(cè)中心傳遞給消費(fèi)方


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市苹熏,隨后出現(xiàn)的幾起案子碟贾,更是在濱河造成了極大的恐慌币喧,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袱耽,死亡現(xiàn)場(chǎng)離奇詭異粱锐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)扛邑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門怜浅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蔬崩,你說(shuō)我怎么就攤上這事恶座。” “怎么了沥阳?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵跨琳,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我桐罕,道長(zhǎng)脉让,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任功炮,我火速辦了婚禮溅潜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘薪伏。我一直安慰自己滚澜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布嫁怀。 她就那樣靜靜地躺著设捐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪塘淑。 梳的紋絲不亂的頭發(fā)上萝招,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音存捺,去河邊找鬼槐沼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛召噩,可吹牛的內(nèi)容都是我干的母赵。 我是一名探鬼主播逸爵,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼具滴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了师倔?” 一聲冷哼從身側(cè)響起构韵,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后疲恢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凶朗,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年显拳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棚愤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡杂数,死狀恐怖宛畦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情揍移,我是刑警寧澤次和,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站那伐,受9級(jí)特大地震影響踏施,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罕邀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一畅形、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诉探,春花似錦束亏、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至阳液,卻和暖如春怕敬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帘皿。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工东跪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鹰溜。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓虽填,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親曹动。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斋日,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345