大白话带你梳理一下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.xsdhttp://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?{@Referenceprivate?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,關于這部分的配置網上的資料也比較多,我就不在這里細說了。
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的大白话带你梳理一下Dubbo的那些事儿的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NYOJ 71 独木舟上的旅行 贪
- 下一篇: NYOJ 14 会场安排问题