大白話帶你梳理一下Dubbo的那些事兒
首先宣告,本文並不是什麼程式碼實戰型別的文章,適合於想對dubbo有更加全面認識的讀者閱讀,文章不會過於深奧,只是將一系列的知識點串通起來,幫助讀者溫故而知新。
RPC服務的介紹
相信有過一些分散式開發經歷的讀者都有用過一些RPC框架,通過框架包裝好之後提供的API介面呼叫遠端服務,體驗感覺起來就和呼叫本地服務一樣輕鬆。這麼方便好用的技術框架,在實際的開發過程中是如何包裝的呢?
很早的時候,國外的工程師設計了一種能夠通過A計算機呼叫B計算機上邊應用程式的技術,這種技術不需要開發人員對於網路通訊瞭解過多,並且呼叫其他機器上邊程式的時候和呼叫本地的程式一樣方便好用。
A機器發起請求去呼叫B機器程式的時候會被掛起,B機器接收到A機器發起的請求引數之後會做一定的引數轉換,最後將對應的程式結果返回給A,這就是最原始的RPC服務呼叫了。
RPC呼叫的優勢
簡單
不需要開發者對於網路通訊做過多的設定,例如我們在使用http協議進行遠端介面呼叫的時候,總是會需要編寫較多的http協議引數(header,context,Accept-Language,Accept-Encode等等),這些處理對於開發人員來說,實際上都並不是特別友好。但是RPC服務呼叫框架通常都將這類解析進行了對應的封裝,大大降低了開發人員的使用難度。
高效
在網路傳輸方面,RPC更多是處於應用層和傳輸層之間。這裡我們需要先理清楚一個問題,網路分層。RPC是處於會話層的部分,相比處於應用層的HTTP而言,RPC要比Rest服務呼叫更加輕便。
常見的遠端呼叫技術
rmi
利用java.rmi包實現,基於Java遠端方法協議(Java Remote Method Protocol) 和java的原生序列化。
Hessian
是一個輕量級的remoting onhttp工具,使用簡單的方法提供了RMI的功能。基於HTTP協議,採用二進位制編解碼。
protobuf-rpc-pro
是一個Java類庫,提供了基於 Google 的 Protocol Buffers 協議的遠端方法呼叫的框架。基於 Netty 底層的 NIO 技術。支援 TCP 重用/ keep-alive、SSL加密、RPC 呼叫取消操作、嵌入式日誌等功能。
Thrift
是一種可伸縮的跨語言服務的軟體框架。它擁有功能強大的程式碼生成引擎,無縫地支援C + +,C#,Java,Python和PHP和Ruby。thrift允許你定義一個描述檔案,描述資料型別和服務介面。依據該檔案,編譯器方便地生成RPC客戶端和伺服器通訊程式碼。
最初由facebook開發用做系統內部語言之間的RPC通訊,2007年由facebook貢獻到apache基金 ,現在是apache下的opensource之一 。支援多種語言之間的RPC方式的通訊:php語言client可以構造一個物件,呼叫相應的服務方法來呼叫java語言的服務,跨越語言的C/S RPC呼叫。底層通訊基於SOCKET。
Avro
出自Hadoop之父Doug Cutting, 在Thrift已經相當流行的情況下推出Avro的目標不僅是提供一套類似Thrift的通訊中介軟體,更是要建立一個新的,標準性的雲端計算的資料交換和儲存的Protocol。支援HTTP,TCP兩種協議。
Dubbo
Dubbo是 阿里巴巴公司開源的一個高效能優秀的服務框架,使得應用可通過高效能的 RPC 實現服務的輸出和輸入功能,可以和 Spring框架無縫整合。
上邊我們說到了RPC的遠端呼叫發展歷史,那麼下邊我們一起來深入探討一下RPC的服務。
首先我們來看看OSI的網路協議內容。
OSI的七層網路模型
對於OSI的七層網路模型我繪製了下邊的這麼一張圖:
下邊是我個人對於這七層協議的理解:
-
應用層 主要是對於服務介面的格式多定義,例如提供一定的終端介面暴露給外部應用呼叫。
-
表示層 處理一些資料傳輸的格式轉換,例如說編碼的統一,加密和解密處理。
-
會話層 管理使用者的會話和對話,建立不同機器之間的會話連線。
-
傳輸層 向網路層提供可靠有序的資料包資訊。
-
網路層 真正傳送資料包資訊的層面,提供流和擁塞控制,從而降低網路的資源損耗。
-
資料鏈路層 封裝對應的資料包,檢測和糾正資料包傳輸資訊。
-
物理層 通過網路通訊裝置傳送資料
HTTP & RPC
HTTP主要是位於TCP/IP協議棧的應用層部分,首先需要構建三次握手的連結,接著才能進行資料資訊的請求傳送,最後進行四次揮手斷開連結。
RPC在請求的過程中跨越了傳輸層和應用層,這是因為它本身是依賴於Socket的原因。(再深入的原因我也不知道)。減少了上邊幾層的封裝,RPC的請求效率自然是要比HTTP高效很多。
那麼一個完整的RPC呼叫應該包含哪些部分呢?
通常我們將一個完整的RPC架構分為了以下幾個核心元件:
-
Server
-
Client
-
Server Stub
-
Client Stub
這四個模組中我稍微說下stub吧。這個單詞翻譯過來稱之為存根。
Client Stub 就是將客戶端請求的引數,服務名稱,服務地址進行打包,統一發送給server方。
Server Stub 我用通俗易懂的語言來解釋就是服務端接收到Client傳送的資料之後進行訊息解包,呼叫本地方法。(看過netty拆包機制應該會對這塊比較瞭解)。
Dubbo的核心屬性
其實Dubbo配置裡面的核心內容就是 服務暴露,服務發現,服務治理。
什麼是服務暴露,服務發現,服務治理?
下邊我們用一段xml的配置來進行講解:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <dubbo:application name="dubbo-invoker-provider"> <dubbo:parameter key="qos.port" value="22222"/> </dubbo:application> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <dubbo:protocol name="dubbo" port="20880"/> <bean id="userService" class="com.sise.user.service.UserServiceImpl" /> <dubbo:service interface="com.sise.user.service.UserService" ref="userService" /> </beans>
在dubbo的配置檔案裡面,通常我們所說的dubbo:service 可以理解為服務暴露,dubbo:refernce 為服務發現,mock是服務治理,timeout屬於服務治理的一種(效能調優).
假設dubbo裡面希望將一些公共的配置抽取出來,我們可以通過properties檔案進行配置,dubbo在載入配置檔案的優先順序如下:
-
優先會讀取JVM -D啟動引數後邊的內容
-
讀取xml配置檔案
-
讀取properties配置檔案內容
dubbo預設會讀取dubbo.properties配置檔案的資訊,例如下邊這種配置:
dubbo.application.name=dubbo-user-service dubbo.registry.address=zookeeper://127.0.0.1:2181
假設我們的dubbo配置檔案不命名為dubbo.properties(假設命名為了my-dubbo.properties)的時候,可以在啟動引數的後邊加上這麼一段指令:
-Ddubbo.properties.file=my-dubbo.properties
那麼在應用程式啟動之後,對應的工程就會讀取指定的配置檔案,這樣就可以將一些共用的dubbo配置給抽取了出來。
XML和配置類的對映
在工作中,我們通常都會通過配置xml的方式來設定一個服務端暴露的服務介面和消費端需要呼叫的服務資訊,這些配置的xml實際上在dubbo的原始碼中都會被解析為對應的實體類物件。
例如說我們常用到的reference配置類,下邊我貼出一段程式碼:
package com.sise.user.config; import com.sise.user.service.UserService; import com.sise.user.service.UserServiceImpl; import org.apache.dubbo.config.*; import java.io.IOException; import java.util.concurrent.CountDownLatch; /** * dubbo裡面的自定義配置類 * * @author idea * @data 2019/12/29 */ public class DubboSelfDefConfig { /** * dubbo的服務暴露 */ public void server() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("dubbo-server-config"); RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://127.0.0.1:2181"); ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); protocolConfig.setThreads(200); UserService userService = new UserServiceImpl(); ServiceConfig<UserService> serviceConfig = new ServiceConfig<>(); serviceConfig.setApplication(applicationConfig); serviceConfig.setRegistry(registryConfig); serviceConfig.setProtocol(protocolConfig); serviceConfig.setInterface(UserService.class); serviceConfig.setRef(userService); serviceConfig.export(); } public void consumer() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("dubbo-client-config"); RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://127.0.0.1:2181"); ReferenceConfig<UserService> referenceConfig = new ReferenceConfig<>(); referenceConfig.setApplication(applicationConfig); referenceConfig.setRegistry(registryConfig); referenceConfig.setInterface(UserService.class); UserService localRef = referenceConfig.get(); localRef.echo("idea"); } public static void main(String[] args) throws InterruptedException, IOException { DubboSelfDefConfig d = new DubboSelfDefConfig(); d.consumer(); CountDownLatch countDownLatch = new CountDownLatch(1); countDownLatch.await(); } }
在這段程式碼裡面,通過案例可以發現有這些資訊內容:
UserService localRef = referenceConfig.get(); localRef.echo("idea");
這兩行語句是獲取具體服務的核心之處,由於我在別處定義了一個叫做UserService 的公共服務介面,因此在服務引用的過程中可以進行轉換。
Dubbo2.7的三大新特新
Dubbo的github官方地址為 https://github.com/apache/dubbo
在這裡插入圖片描述
Dubbo 目前有如圖所示的 5 個分支,其中 2.7.1-release 只是一個臨時分支,忽略不計,對其他 4 個分支而言,我歸納了一下,分別有如下資訊:
-
2.5.x 近期已經通過投票,Dubbo 社群即將停止對其的維護。
-
2.6.x 為長期支援的版本,也是 Dubbo 貢獻給 Apache 之前的版本,其包名字首為:com.alibaba,JDK 版本對應 1.6。
-
3.x-dev 是前瞻性的版本,對 Dubbo 進行一些高階特性的補充,如支援 rx 特性。
-
master 為長期支援的版本,版本號為 2.7.x,也是 Dubbo 貢獻給 Apache 的開發版本,其包名字首為:org.apache,JDK 版本對應 1.8。
Dubbo 2.7 新特性
Dubbo 2.7.x 作為 Apache 的孵化版本,除了程式碼優化之外,還新增了許多重磅的新特性,本文將會介紹其中最典型的2個新特性:
-
非同步化改造
-
三大中心改造
非同步化改造
1.非同步化呼叫的方式,在Dubbo2.7版本里面提供了非同步化呼叫的功能,相關案例程式碼如下所示:
@RestController @RequestMapping(value = "/test") public class TestController { @Reference(async = true) private UserService userService; @GetMapping("/testStr") public String testStr(String param){ return userService.testEcho(param); } }
但是通過這種非同步傳送的方式我們通常都是獲取不到響應值的,所以這裡的return為null。
如果在低於2.7版本的dubbo框架中希望獲取到非同步返回的響應值還是需要通過RPC上下文來提取資訊。
程式碼案例如下所示:
@GetMapping("/futureGet") public String futureGet(String param) throws ExecutionException, InterruptedException { userService.testEcho(param); Future<String> future= RpcContext.getContext().getFuture(); String result = future.get(); System.out.println("this is :"+result); return result; }
通過RPC上下文的方式可以取到對應的響應值,但是這種方式需要有所等待,因此此時的效率會有所降低。假設我們將dubbo的版本提升到了2.7.1之後,通過使用CompletableFuture來進行介面優化的話,這部分的程式碼實現就會有所變化:
/** * @author idea * @date 2019/12/31 * @Version V1.0 */ public interface DemoService { String sayHello(String name) ; default CompletableFuture<String> sayAsyncHello(String name){ return CompletableFuture.completedFuture(sayHello(name)); } }
呼叫方程式碼:
package com.sise.consumer.controller; import com.sise.dubbo.service.DemoService; import org.apache.dubbo.config.annotation.Reference; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; /** * @author idea * @date 2019/12/31 * @Version V1.0 */ @RestController @RequestMapping(value = "/demo") public class DemoController { @Reference private DemoService demoService; @RequestMapping(value = "/testDemo") public String testDemo(String name){ System.out.println("【testDemo】 this is :"+name); return demoService.sayHello(name); }. @RequestMapping(value = "/testAsyncDemo") public String testAsyncDemo(String name){ System.out.println("【testAsyncDemo】 this is :"+name); CompletableFuture<String> future = demoService.sayAsyncHello(name); AtomicReference<String> result = null; //通過一條callback執行緒來處理響應的資料資訊 future.whenComplete((retValue,exception)->{ if(exception==null){ System.out.println(retValue); result.set(retValue); } else { exception.printStackTrace(); } }); return "通過一條callback執行緒來處理響應的資料資訊,所以這個時候獲取不到資訊響應"; } }
這樣的呼叫是藉助了callback執行緒來幫我們處理原先的資料內容,關於dubbo裡面的非同步化呼叫,我借用了官方的一張圖來進行展示:
我們上邊講解的眾多方法都只是針對於dubbo的客戶端非同步化,並沒有講解關於服務端的非同步化處理,這是因為結合dubbo的業務執行緒池模型來思考,服務端的非同步化處理比較雞肋(因為dubbo內部服務端的執行緒池本身就是非同步化呼叫的了)。
當然dubbo 2.6 裡面對於介面非同步化呼叫的配置到了2.7版本依舊有效。
三大中心的改造
註冊中心
在dubbo2.7之前,dubbo主要還是由consumer,provider ,register組成,然而在2.7版本之後,dubbo的註冊中心被拆解為了三個中心,分別是原先的註冊中心和元資料中心以及配置中心。
元資料配置
在dubbo2.7版本中,將原先註冊在zk上邊的過多資料進行了註冊拆分,這樣能夠保證減少對於zk端的壓力。具體配置如下:
<dubbo:registry address=“zookeeper://127.0.0.1:2181” simplified="true"/>
簡化了相應配置之後,dubbo也只會上傳一些必要的服務治理資料了,簡化版本的服務資料只剩下下邊這些資訊:
dubbo://30.5.120.185:20880/com.sise.TestService? application=test-provider& dubbo=2.0.2& release=2.7.0& timestamp=1554982201973
對於其他的元資料資訊將會被儲存到一些元資料中心裡面,例如說redis,nacos,zk等
元資料配置改造主要解決的問題是:推送量大 -> 儲存資料量大 -> 網路傳輸量大 -> 延遲嚴重
配置中心
dubbo2.7開始支援多種分散式配置中心的元件。例如說:zk,Spring Cloud Config, Apollo, Nacos,關於這部分的配置網上的資料也比較多,我就不在這裡細說