Dubbo
Dubbo架構圖

Dubbo層次結構圖
第一層:service層,接口層,給服務提供者和消費者來實現(xiàn)的
第二層:config層,配置層,主要是對dubbo進行各種配置的
第三層:proxy層,服務代理層,透明生成客戶端的stub和服務單的skeleton
第四層:registry層,服務注冊層,負責服務的注冊與發(fā)現(xiàn)
第五層:cluster層,集群層,封裝多個服務提供者的路由以及負載均衡,將多個實例組合成一個服務
第六層:monitor層,監(jiān)控層,對rpc接口的調用次數(shù)和調用時間進行監(jiān)控
第七層:protocol層,遠程調用層,封裝rpc調用
第八層:exchange層,信息交換層,封裝請求響應模式,同步轉異步
第九層:transport層,網(wǎng)絡傳輸層,抽象mina和netty為統(tǒng)一接口
第十層:serialize層,數(shù)據(jù)序列化層
Dubbo支持的通信協(xié)議
| 協(xié)議名 | 序列化 | 連接 | 適用場景 |
|---|---|---|---|
| Dubbo協(xié)議 | hessian2 | 單一長連接,NIO異步通信 | 傳輸數(shù)據(jù)量很?。看握埱笤?00kb以內),但是并發(fā)量很高,消費者遠大于提供者 |
| rmi協(xié)議 | java二進制序列化 | 多個短連接 | 消費者和提供者數(shù)量差不多,文件的傳輸 |
| hessian協(xié)議 | hessian序列化協(xié)議 | 多個短連接 | 傳輸數(shù)據(jù)量較大,提供者數(shù)量比消費者數(shù)量還多,文件的傳輸 |
| http協(xié)議 | json序列化 | 多個短連接 | 需同時給應用程序和瀏覽器 JS 使用的服務 |
| webservice | SOAP文本序列化 | 多個短連接 | 系統(tǒng)集成,跨語言調用 |
Dubbo不好用的地方
| 不好用的地方 | 說明 | 原因 | 解決方案 |
|---|---|---|---|
| 參數(shù)及返回值需要實現(xiàn)Serializable接口 | 默認協(xié)議是Dubbo協(xié)議,通過netty傳輸,需要轉換成二機制數(shù)據(jù) | 實現(xiàn)Serializable接口 | |
| 參數(shù)及返回值不能自定義實現(xiàn)List, Map, Number, Date, Calendar等接口 | - | 默認協(xié)議是Dubbo協(xié)議,采用的是hessian序列化&反序列化方式,遇上以上接口時會做特殊處理,自定義實現(xiàn)類中的屬性值都會丟失 | 使用java原生接口 |
| 父子類有相同屬性時值丟失 | - | 默認采用hessian序列化,獲取屬性時,采用了map去重,但是讀值時,根據(jù)序列化順序,對于同名字段,子類的該字段值會被賦值兩次,總是被父類的值覆蓋,導致子類的字段值丟失 | 不要重名 |
| 自定義異常被包裝成RuntimeException | - | dubbo提供端對異常進行了統(tǒng)一封裝導致的 | 將自定義異常和接口類放到一個包中/方法簽名上申明自定義異常 |
| IP暴露問題 | Docker、雙網(wǎng)卡、虛擬機等環(huán)境下,Dubbo默認綁定的IP可能并不是我們期望的正確IP | 跟Dubbo綁定IP默認行為有關 | 通過添加啟動參數(shù)和配置解決 |
| Data length too large | 請求或者響應的報文體長度超過了8k | dubbo協(xié)議適合小數(shù)據(jù)傳輸,建議更改協(xié)議 | |
| 線程耗盡 | Dubbo提供者線程池為fix類型,默認線程數(shù)為200,隊列為0 | 解決業(yè)務耗時原因/擴容 |
Dubbo服務提供端異常統(tǒng)一處理邏輯
目的是為了保證拋出的異常,消費端都能識別,具體邏輯如下:
- 如果是checked異常(不是RuntimeException但是是Exception.java類型的異常),直接拋出;
- 在方法簽名上有聲明(例如String saySomething()throws MyException ),直接拋出;
- 異常類和接口類在同一jar包里,直接拋出;
- 是JDK自帶的異常(全類名以java或者javax開頭,例如java.lang.IllegalStateException),直接拋出;
- 是Dubbo本身的異常RpcException,直接拋出;
- 否則,Dubbo通過如下代碼將異常包裝成RuntimeException拋給客戶端
Dubbo常用性能調優(yōu)屬性

C90DBB86-152E-4D54-A388-106EB095A321.jpeg

image.png
注意表中參數(shù)與圖中的對應關系:
1、當consumer發(fā)起一個請求時,首先經(jīng)過active limit(參數(shù)actives)進行方法級別的限制,其實現(xiàn)方式為CHM中存放計數(shù)器(AtomicInteger),請求時加1,請求完成(包括異常)減1,如果超過actives則等待有其他請求完成后重試或者超時后失??;
2、從多個連接(connections)中選擇一個連接發(fā)送數(shù)據(jù),對于默認的netty實現(xiàn)來說,由于可以復用連接,默認一個連接就可以。不過如果你在壓測,且只有一個consumer,一個provider,此時適當?shù)募哟骳onnections確實能夠增強網(wǎng)絡傳輸能力。但線上業(yè)務由于有多個consumer多個provider,因此不建議增加connections參數(shù);
3、連接到達provider時(如dubbo的初次連接),首先會判斷總連接數(shù)是否超限(acceps),超過限制連接將被拒絕;
4、連接成功后,具體的請求交給io thread處理。io threads雖然是處理數(shù)據(jù)的讀寫,但io部分為異步,更多的消耗的是cpu,因此iothreads默認cpu個數(shù)+1是比較合理的設置,不建議調整此參數(shù);
5、數(shù)據(jù)讀取并反序列化以后,交給業(yè)務線程池處理,默認情況下線程池為fixed,且排隊隊列為0(queues),這種情況下,最大并發(fā)等于業(yè)務線程池大小(threads),如果希望有請求的堆積能力,可以調整queues參數(shù)。如果希望快速失敗由其他節(jié)點處理(官方推薦方式),則不修改queues,只調整threads;
6、execute limit(參數(shù)executes)是方法級別的并發(fā)限制,原理與actives類似,只是少了等待的過程,即受限后立即失?。?
Dubbo負載均衡策略
| 策略名 | 實現(xiàn)邏輯 |
|---|---|
| 隨機 | 生成隨機值,判斷落在哪個權重范圍內 |
| 輪詢 | 維護一個總調用次數(shù),對總權重取模,判斷落在哪個權重范圍內 |
| 最少活躍數(shù) | 維護每個服務器的活躍數(shù),取最小值 |
| 一致性Hash |
Dubbo的集群及容錯策略
| 容錯策略名 | 說明 | 適用場景 |
|---|---|---|
| failover | 失敗自動切換 | 讀操作 |
| failfast | 失敗立即返回 | 寫操作 |
| failsafe | 失敗則忽略 | 允許丟失的操作,例如日志 |
| failback | 失敗定時重發(fā) | 消息隊列 |
| forking | 并行調用多個provider,某個成功就返回 | |
| broadcast | 廣播,逐個調用所有的provider |
Dubbo的動態(tài)代理
- 默認使用javassist動態(tài)字節(jié)碼生成,創(chuàng)建代理類
java的spi機制的原理(ServiceLoader)
- 獲取classloader
- 讀取META-INF/services/+全限定接口名的文件內容
- 循環(huán)創(chuàng)建實現(xiàn)類對象并加入緩存
- 返回實現(xiàn)類對象集合
springboot的spi機制
- spring.factories文件
Dubbo的spi機制的使用
- 三個路徑都可以放以接口全限定類為名的文件, META-INF/services/ 、META-INF/dubbo/和META-INF/dubbo/internal/
- 文件內容是鍵值對格式
- 接口需要用@SPI注解修飾
- 通過接口類找到一個 ExtensionLoader
- 通過ExtensionLoader.getExtension(key) 得到指定key的實現(xiàn)類實例
Dubbo的spi機制的原理

image.png
- 通過接口類找到一個 ExtensionLoader
- 通過定義key到實例緩存中查找是否存在該實例,如果存在則返回
- 實現(xiàn)類緩存中查找是否存在該類,沒有則掃描約定的三個目錄并加載
- 放射創(chuàng)建指定實現(xiàn)類對象
- 經(jīng)過注入和包裝后返回
Dubbo的服務降級
通過mock屬性實現(xiàn)服務降級
Dubbo的服務熔斷
無熔斷機制,可通過hystrix實現(xiàn)。
Dubbo的服務限流
| 限流方式 | 使用客戶端 | 標簽 | 含義 |
|---|---|---|---|
| executes | 提供端 | service、method | 并發(fā)執(zhí)行不能超過指定數(shù) |
| accepts | 提供端 | protocol | 指定協(xié)議的連接數(shù)不能超過指定數(shù) |
| actives | 提供端/消費端 | service、reference、method | 長鏈接時:最多可以處理的請求個數(shù);短鏈接時:可以同時處理的短連接數(shù)量 |
| connections | 提供端/消費端 | service、reference、method | 最大連接數(shù) |
spring與Dubbo搭配使用時,在什么時候開始服務注冊
- spring啟動過程中刷新上下文操作時
- bean實例化結束后會發(fā)送上下文刷新完成事件
- Dubbo的ServiceBean類實現(xiàn)了ApplicationListener接口監(jiān)聽了該事件
- 故此時會去執(zhí)行onApplicationEvent方法,也是在此時進行了服務注冊
Dubbo 服務暴露的流程

服務暴露

服務暴露的大致流程
- 根據(jù)配置得到 URL
- 通過 javassist 動態(tài)封裝服務實現(xiàn)類,統(tǒng)一暴露出 Invoker 使得調用方便,屏蔽底層實現(xiàn)細節(jié)(ProxyFactory)
- 利用 Dubbo SPI 機制根據(jù) URL 的參數(shù)選擇對應的插件實現(xiàn)類(例如protocol)
- 然后將invoker封裝成 exporter 存儲起來,等待消費者的調用
- 最后將URL 注冊到注冊中心,使得消費者可以獲取服務提供者的信息(ExporterListener)
Dubbo服務引用的過程(注冊中心方式舉例)

服務引用詳細流程

服務引用大致流程
- 通過配置組成URL
- 向注冊中心注冊消費者,訂閱節(jié)點(Directory)
- 監(jiān)聽注冊中心獲取提供者信息(Directory實現(xiàn)了NotifyListener接口)
- 通過SPI機制獲取對應協(xié)議,生成Invoker(Directory.refer)
- Invoker中加入負載,容錯等處理 (RegistryProtocol.merge)
- 最后通過動態(tài)代理封裝得到代理類(ProxyFactory)
如何實現(xiàn)鏈路追蹤
- 通過brave生成和傳遞traceId
- zipkin匯總traceId
- Logstash采集日志
- ElasticSearch+Kibana 可視化分析日志
dubbo的線程模型
- all 所有消息都派發(fā)到線程池
- direct 所有消息都不派發(fā)到線程池,全部在 IO 線程上直接執(zhí)行
- message 只有請求、響應消息派發(fā)到業(yè)務線程池,其他連接斷開事件/心跳等消息,直接在IO線程上執(zhí)行
- execution 只請求消息派發(fā)到業(yè)務線程池,響應和連接、斷開事件、心跳等消息,直接在 IO 線程上執(zhí)行
- connection 在 IO 線程上,將連接、斷開事件放入隊列,有序逐個執(zhí)行,其它消息派發(fā)到線程池
dubbo的線程模型圖

dubbo的線程模型圖
dubbo停機的過程

dubbo停機的過程
- 收到 kill PID 進程退出信號,Spring 容器會觸發(fā)容器銷毀事件。
- provider 端會注銷服務元數(shù)據(jù)信息(刪除ZK節(jié)點)。
- consumer 會拉取最新服務提供者列表。
- provider 會發(fā)送 readonly 事件報文通知 consumer 服務不可用。
- 服務端等待已經(jīng)執(zhí)行的任務結束并拒絕新任務執(zhí)行。
- TCP連接斷開