Dubbo 面試 17 問

1 Dubbo 是什麼?RPC 又是什麼?

Dubbo 是一個分佈式服務框架,致力於提供高性能和透明化的 RPC 遠程服務調用方案,以及 SOA 服務治理方案。

RPC(Remote Procedure Call)—遠程過程調用,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。RPC 協議假定某些傳輸協議的存在,如 TCP 或 UDP,爲通信程序之間攜帶信息數據。在 OSI 網絡通信模型中,RPC 跨越了傳輸層和應用層。RPC 使得開發包括網絡分佈式多程序在內的應用程序更加容易。RPC 採用客戶機 / 服務器模式。請求程序就是一個客戶機,而服務提供程序就是一個服務器。首先,客戶機調用進程發送一個有進程參數的調用信息到服務進程,然後等待應答信息。在服務器端,進程保持睡眠狀態直到調用信息到達爲止。當一個調用信息到達,服務器獲得進程參數,計算結果,發送答覆信息,然後等待下一個調用信息,最後,客戶端調用進程接收答覆信息,獲得進程結果,然後調用執行繼續進行。有多種 RPC 模式和執行。

我們用一種通俗易懂的語言解釋它,遠程調用就是本地機器調用遠程機器的一個方法,遠程機器返回結果的過程

爲什麼要這麼做?

主要原因是由於單臺服務的性能已經無法滿足我們了,在這個流量劇增的時代,只有多臺服務器才能支撐起來現有的用戶體系。

而在這種體系下,服務越來越多,逐漸演化出了現在這種微服務化的 RPC 框架。

2 Dubbo 能做什麼?

Dubbo 包含以下核心功能:

3 能說一下 Dubbo 的總體調用過程嗎?

調用過程

  1. Proxy 持有一個 Invoker 對象,使用 Invoker 調用;

  2. 之後通過 Cluster 進行負載容錯,失敗重試;

  3. 調用 Directory 獲取遠程服務的 Invoker 列表;

  4. 負載均衡

  5. 用戶配置了路由規則,則根據路由規則過濾獲取到的 Invoker 列表;

  6. 用戶沒有配置路由規則,或配置路由後還有很多節點時,則使用 LoadBalance 方法做負載均衡,選用一個可以調用的 Invoker。

  7. 經過一個一個過濾器鏈,通常是處理上下文、限流、計數等;

  8. 會使用 Client 做數據傳輸;

  9. 私有化協議的構造(Codec);

  10. 進行序列化;

  11. 服務端收到這個 Request 請求,將其分配到 ThreadPool 中處理;

  12. Server 來處理這些 Request;

  13. 根據請求查找對應的 Exporter;

  14. 之後經過一個服務提供者端的過濾器鏈;

  15. 然後找到接口實現並真正的調用,將請求結果返回。

4 說說 Dubbo 支持哪些協議,每種協議的應用場景和優缺點

  1. dubbo 單一長連接和 NIO 異步通訊:適合大併發小數據量的服務調用,以及消費者遠大於提供者。傳輸協議 TCP、異步、Hessian 序列化;

  2. rmi  採用 JDK 標準的 rmi 協議實現,傳輸參數和返回參數對象需要實現 Serializable 接口。使用 Java 標準序列化機制,使用阻塞式短連接,傳輸數據包大小混合。消費者和提供者個數差不多,可傳文件、傳輸協議 TCP。多個短連接,TCP 協議傳輸、同步傳輸,適用常規的遠程服務調用和 rmi 互 操作。缺點:依賴低版本的 Common-Collections 包,Java 序列化存在安全漏洞;

  3. webservice 基於 WebService 的遠程調用協議,集成 CXF 實現,提供和原生 WebService 的互操作。多個短連接,基於 HTTP 傳輸、同步傳輸,適用系統集成和跨語言調用;

  4. http  基於 HTTP 表單提交的遠程調用協議,使用 Spring 的 HttpInvoke 實 現。多個短連接,傳輸協議 HTTP,傳入參數大小混合,提供者個數多於消費者,需要給應用程序和瀏覽器 JS 調用;

  5. hessian  集成 Hessian 服務,基於 HTTP 通訊,採用 Servlet 暴露服務。Dubbo 內嵌 Jetty 作爲服務器默認實現,提供與 Hession 服務互操作。多個短連接、同步 HTTP 傳輸、Hessian 序列化,傳入參數較大,提供者大於消費者,提供者壓力較大,可傳文件;

  6. memcache 基於 memcached 實現的 RPC 協議;

  7. redis 基於 Redis 實現的 RPC 協議。

5 Dubbo 中都用到哪些設計模式?

責任鏈模式

責任鏈模式在 Dubbo 中發揮的作用舉足輕重,就像是 Dubbo 框架的骨架。

Dubbo 的調用鏈組織是用責任鏈模式串連起來的。責任鏈中的每個節點實現 Filter 接口,然後由 ProtocolFilterWrapper,將所有 Filter 串連起來。

Dubbo 的 許多功能都是通過 Filter 擴展實現的,比如監控、日誌、緩存、安全、telne t 以及 RPC 本身都是如此。

觀察者模式

Dubbo 中 使用觀察者模式最典型的例子是 RegistryService。

消費者在初始化的時候會調用 subscribe 方法,註冊一個觀察者。如果觀察者引用的服務地址列表發生改變,就會通過 NotifyListener 通知消費者。

此外,Dubbo 的 InvokerListener、ExporterListener 也實現了觀察者模式。只要實現該接口並註冊,就可以接收到 consumer 端調用 refer 和 provider 端調用 export 的通知。

修飾器模式

Dubbo 中還大量用到了修飾器模式。比如 ProtocolFilterWrapper 類是對 Protocol 類的修飾。

在 export 和 refer 方法中,配合責任鏈模式把 Filter 組裝成責任鏈,實現對 Protocol 功能的修飾。其他的還有 ProtocolListenerWrapper、 ListenerInvokerWrapper、InvokerWrapper 等。

工廠方法模式

CacheFactory 的實現採用工廠方法模式。

CacheFactory 接口定義 getCache 方法,然後定義一個 AbstractCacheFactory 抽象類實現 CacheFactory,並將實際創建 cache 的 createCache 方法分離出來,並設置爲抽象方法。這樣具體 cache 的創建工作就留給具體的子類去完成。

抽象工廠模式

ProxyFactory 及其子類是 Dubbo 中使用抽象工廠模式的典型例子。

ProxyFactory 提供兩個方法,分別用來生產 Proxy 和 Invoker(這兩個方法簽名看起來有些矛盾,因爲 getProxy 方法需要傳入一個 Invoker 對象,而 getInvoker 方法需要傳入一個 Proxy 對象,看起來會形成循環依賴,但其實兩個方式使用的場景不一樣)。

AbstractProxyFactory 實現了 ProxyFactory 接口,作爲具體實現類的抽象父類。然後定義了 JdkProxyFactory 和 JavassistProxyFactory 兩個具體類,分別用來生產基於 JDK 代理機制和基於 Javassist 代理機制的 Proxy 和 Invoker。

適配器模式

爲了讓用戶根據自己的需求選擇日誌組件,Dubbo 自定義了自己的 Logger 接口,併爲常見的日誌組件(包括 jcl, jdk, log4j, slf4j)提供相應的適配器。

利用簡單工廠模式提供一個 LoggerFactory。客戶可以創建抽象的 Dubbo 自定義 Logger,而無需關心實際使用的日誌組件類型。

在 LoggerFactory 初始化時,客戶通過設置系統變量的方式選擇自己所用的日誌組件,這樣提供了很大的靈活性。

代理模式

Dubbo consumer 使用 Proxy 類創建遠程服務的本地代理。本地代理實現和遠程服務一樣的接口,並且屏蔽了網絡通信的細節,使得用戶在使用本地代理的時候,感覺和使用本地服務一樣。

6 如果 Dubbo 中 provider 提供的服務有多個版本怎麼辦?

可以直接通過 Dubbo 配置中的 version 版本來控制多個版本即可。比如:

<dubbo:service interface="com.xxxx.rent.service.IDemoService" ref="iDemoServiceFirst" version="1.0.0"/>
<dubbo:service interface="com.xxxx.rent.service.IDemoService" ref="iDemoServiceSecond" version="1.0.1"/>

老版本 version=1.0.0,新版本 version=1.0.1。

7 服務暴露的流程是怎麼樣的?

  1. 通過 ServiceConfig 解析標籤,創建 dubbo 標籤解析器來解析 dubbo 的標籤。容器創建完成之後,觸發 ContextRefreshEvent 事件回調開始暴露服務;

  2. 通過 ProxyFactory.getInvoker 方法,並利用 Javassist 或 JdkProxyFactory 來進行動態代理,將服務暴露接口封裝成 Invoker 對象,裏面包含了需要執行的方法的對象信息和具體的 URL 地址;

  3. 再通過 DubboProtocol 的實現把包裝後的 Invoker 轉換成 Exporter;

  4. 然後啓動服務器 server,監聽端口;

  5. 最後 RegistryProtocol 保存 URL 地址和 Invoker 映射關係,同時註冊到服務中心。

8 服務引用的流程是怎麼樣的?

  1. 首先,客戶端根據 config 文件信息從註冊中心訂閱服務。首次會全量緩存到本地,後續的更新會監聽動態更新到本地;

  2. 接着,DubboProtocol 根據 provider 的地址和接口信息連接到服務端 server。開啓客戶端 client,然後創建 invoker;

  3. 然後,通過 invoker 爲服務接口生成代理對象,這個代理對象用於遠程調用 provider,至此完成了服務引用。

9 Dubbo 的註冊中心有哪些?

Zookeeper、Redis、Multicast、Simple 等都可以作爲 Dubbo 的註冊中心。

10 聊聊 Dubbo SPI 機制?

SPI(Service Provider Interface)是一種服務發現機制。其實就是將結構的實現類寫入配置當中,在服務加載的時候讀取配置文件,加載實現類。這樣就可以在運行的時候,動態幫助接口替換實現類。

Dubbo 的 SPI 其實是對 Java 的 SPI 進行了一種增強, 可以按需加載實現類之外,增加了 IOC 和 AOP 的特性,還有自適應擴展機制。

SPI 在 dubbo 應用很多,包括協議擴展、集羣擴展、路由擴展、序列化擴展等。

Dubbo 對於文件目錄的配置分爲了三類:

  1. META-INF/services/ 目錄:該目錄下的 SPI 配置文件是爲了用來兼容 Java SPI;

  2. META-INF/dubbo/ 目錄:該目錄存放用戶自定義的 SPI 配置文件:key=com.xxx.xxx;

  3. META-INF/dubbo/internal/ 目錄:該目錄存放 Dubbo 內部使用的 SPI 配置文件。

11 Dubbo 的 SPI 和 Java 的 SPI 有什麼區別?

Java SPI

Java SPI 在查找擴展實現類的時候遍歷 SPI 的配置文件,並且將實現類全部實例化。

Dubbo SPI

  1. 對 Dubbo 進行擴展不需要改動 Dubbo 源碼;

  2. 延遲加載,可以一次只加載自己想要加載的擴展實現;

  3. 增加了對擴展點 IOC 和 AOP 的支持,一個擴展點可以直接 setter 注入其它擴展點;

  4. Dubbo 的擴展機制能很好的支持第三方 IoC 容器,默認支持 Spring Bean。

12 有哪些負載均衡策略?

加權隨機

比如我們有三臺服務器 [A, B, C],給他們設置權重爲 [4, 5, 6],然後將這三個數平鋪在水平線上,和爲 15。然後在 15 以內生成一個隨機數,0~4 是服務器 A,4~9 是服務器 B,9~15 是服務器 C。

最小活躍數

每個服務提供者對應一個活躍數 active。初始情況下,所有服務提供者活躍數均爲 0。每收到一個請求,活躍數加 1,完成請求後則將活躍數減 1。

在服務運行一段時間後,性能好的服務提供者處理請求的速度更快,因此活躍數下降的也越快,此時這樣的服務提供者能夠優先獲取到新的服務請求。

一致性哈希

加權輪詢

比如有三臺服務器 [A, B, C],給它們設置權重爲 [4, 5, 6]。那麼,假如總共有 15 次請求,那麼會有 4 次落在 A 服務器,5 次落在 B 服務器,6 次落在 C 服務器。

13 集羣容錯方式有哪些?

  1. Failover Cluster 失敗自動切換:dubbo 的默認容錯方案,當調用失敗時自動切換到其他可用的節點。具體的重試次數和間隔時間可用通過引用服務的時候配置。默認重試次數爲 1 即只調用一次;

  2. Failback Cluster 失敗自動恢復:在調用失敗,記錄日誌和調用信息,然後返回空結果給 consumer,並且通過定時任務每隔 5 秒對失敗的調用進行重試;

  3. Failfast Cluster 快速失敗:只會調用一次,失敗後立刻拋出異常;

  4. Failsafe Cluster 失敗安全:調用出現異常,記錄日誌不拋出,返回空結果;

  5. Forking Cluster 並行調用多個服務提供者:通過線程池創建多個線程,併發調用多個 provider,結果保存到阻塞隊列。只要有一個 provider 成功返回了結果,就會立刻返回結果;

  6. Broadcast Cluster 廣播模式:逐個調用每個 provider。如果其中一臺報錯,在循環調用結束後,拋出異常。

14 說說 Dubbo 的分層?

分層圖

從大的範圍來說,dubbo 分爲三層:

Service 和 Config 兩層可以認爲是 API 層。主要提供給 API 使用者,使用者只需要配置和完成業務代碼就可以了。後面所有的層級是 SPI 層,提供給擴展者使用。主要用來做 Dubbo 的二次開發功能擴展。再劃分到更細的層面,就是圖中的 10 層模式。

15 服務提供者能實現失效踢出是什麼原理?

服務失效踢出基於 Zookeeper 臨時節點原理。

Zookeeper 中的節點是有生命週期的,具體的生命週期取決於節點的類型。節點主要分爲持久(Persistent)節點和臨時(Ephemeral)節點 。

16 爲什麼要通過代理對象通信?

其實,主要就是爲了將調用細節封裝起來,將調用遠程方法變得和調用本地方法一樣簡單。還可以做一些其他方面的增強,比如負載均衡,容錯機制,過濾操作,調用數據的統計。

17 怎麼設計一個 RPC 框架?

關於這個問題,核心考察點就是你對於 RPC 框架的理解:一個成熟的 RPC 框架可以完成哪些功能。

其實,當我們看過一兩個 RPC 框架後,就可以對這個問題回答個七七八八了。我們來舉個例子。

  1. 首先,我們需要一個註冊中心。管理消費者和提供者的節點信息,這樣纔會有消費者和提供纔可以去訂閱服務、註冊服務;

  2. 有了註冊中心後,可能會有很多個 provider 節點。那麼,肯定會有一個負載均衡模塊來負責節點的調用。至於用戶指定路由規則,可以看作額外的優化點;

  3. 具體調用肯定會需要牽扯到通信協議,所以需要一個模塊來對通信協議進行封裝,網絡傳輸還要考慮序列化;

  4. 調用失敗後怎麼去處理?所以還需要一個容錯模塊,負責處理的失敗情況;

  5. 做完這些,一個基礎的模型就已經搭建好了。我們還可以有更多的優化點,比如一些請求數據的監控,配置信息的處理,日誌信息的處理等等。

以上就是一個比較基本的 RPC 框架思路,大家有沒有 get 到?

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/S2ckynSFDolyweNo5qTm-g