dubbo是什么
dubbo是一個分布式服務中間件,是高性能和透明化的RPC遠程服務調用解決方案,主要通過資源調度和服務治理來解決分布式架構下服務資源浪費以提高集群的使用率。核心部分包含:
- 遠程通訊:提供多種基于長連接的NIO的抽象封裝,包括多種線程模型,序列化方式,以及請求-響應模式的信息交互
- 集群容錯:提供基于接口方法的透明化遠程調用,包括多協議支持,軟負載均衡,失敗容錯,地址路由,動態配置的集群策略
- 服務發現:基于注冊中心目錄服務,使服務消費者能動態查找服務提供者,使地址透明,服務提供者可以根據流量平滑增加節點
1.dubbo架構-組件角色
在深入架構前,先了解一下dubbo的組件角色,如下圖:
- Provider:暴露服務的服務提供方
- Container:服務運行容器
- Registry:服務注冊與發現的注冊中心
- Consumer:調用遠程服務的服務消費方
- Monitor:統計服務調用次數和耗時的監控中心
調用關系分析:
- 服務容器Container負責啟動,加載,運行服務提供者。
- 服務提供者Provider在啟動時,向注冊中心注冊自己提供的服務。
- 服務消費者Consumer在啟動時,向注冊中心訂閱自己所需的服務。
- 注冊中心Registry返回服務提供者地址列表給消費者,如果有變更,注冊中心將基于長連接推送變更數據給消費者。
- 服務消費者Consumer,從提供者地址列表中,基于軟負載均衡算法,選一臺提供者進行調用,如果調用失敗,再選另一臺調用。
- 服務消費者Consumer和提供者Provider,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心Monitor。
問:各組件角色之間是采用長連接還是短連接交互的?為什么? 答:服務提供者->消費者,服務提供者->注冊中心,服務消費者->注冊中心。以上三個交互環節采用的是長連接。至于為什么?筆者分析是因為基于注冊中心的動態服務發現與注冊機制,所以需要通過心跳的方式實時檢測節點的狀態
2.dubbo架構-模型分層
先來看一下dubbo這個中間件的領域模型分層結構,如下圖:
中間件架構分層設計:
- 服務接口層(Service):該層是與實際業務邏輯相關的,根據服務提供方和服務消費方的業務設計對應的接口和實現。
- 配置層(Config):對外配置接口,以ServiceConfig和ReferenceConfig為中心,可以直接new配置類,也可以通過spring解析配置生成配置類。
- 服務代理層(Proxy):服務接口透明代理,生成服務的客戶端Stub和服務器端Skeleton,以ServiceProxy為中心,擴展接口為ProxyFactory。
- 服務注冊層(Registry):封裝服務地址的注冊與發現,以服務URL為中心,擴展接口為RegistryFactory、Registry和RegistryService。可能沒有服務注冊中心,此時服務提供方直接暴露服務。
- 集群層(Cluster):封裝多個提供者的路由及負載均衡,并橋接注冊中心,以Invoker為中心,擴展接口為Cluster、 Directory、Router和LoadBalance。將多個服務提供方組合為一個服務提供方,實現對服務消費方來透明,只需要與一個服務提供方進 行交互。
- 監控層(Monitor):RPC調用次數和調用時間監控,以Statistics為中心,擴展接口為MonitorFactory、Monitor和MonitorService。
- 遠程調用層(Protocol):封將RPC調用,以Invocation和Result為中心,擴展接口為Protocol、Invoker和 Exporter。Protocol是服務域,它是Invoker暴露和引用的主功能入口,它負責Invoker的生命周期管理。Invoker是實體 域,它是Dubbo的核心模型,其它模型都向它靠擾,或轉換成它,它代表一個可執行體,可向它發起invoke調用,它有可能是一個本地的實現,也可能是 一個遠程的實現,也可能一個集群實現。
- 信息交換層(Exchange):封裝請求響應模式,同步轉異步,以Request和Response為中心,擴展接口為Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
- 網絡傳輸層(Transport):抽象mina和netty為統一接口,以Message為中心,擴展接口為Channel、Transporter、Client、Server和Codec。
- 數據序列化層(Serialize):可復用的一些工具,擴展接口為Serialization、 ObjectInput、ObjectOutput和ThreadPool。
各層之間關系總結:
- Protocol是核心層,簡單組合只需要Protocol,Invoker,Exporter就能完成遠程方法調用。Filter是在Invoker過程中增加的攔截層
- Registry和Monitor屬于獨立角色,Registry屬于注冊中心層
- Consumer和Provider屬于抽象概念,因為一個節點在提供服務的同時調用了別的服務,那么他既屬于Consumer又屬于Provider
- Proxy層封裝過了所有接口了透明代理,但是其他層都以Invoker為中心,只有暴露接口時才用Proxy將Invoker轉換為接口(接口轉換為Invoker)
- Remoting是dubbo協議的實現,內部分為Transport傳輸層和Exchange信息交換層,Transport只負責單向數據傳輸(Netty,Mina的抽象,也可以擴展使用UDP協議傳輸),Exchange是在Transport上定義了 Request-Response的模型
3.調用鏈路
在看源碼之前,先看一下dubbo服務調用鏈路過程。如下圖:
首先服務消費者通過代理對象 Proxy 發起遠程調用,接著通過網絡客戶端 Client 將編碼后的請求發送給服務提供方的網絡層上,也就是 Server。Server 在收到請求后,首先要做的事情是對數據包進行解碼。然后將解碼后的請求發送至分發器 Dispatcher,再由分發器將請求派發到指定的線程池上,最后由線程池調用具體的服務。這就是一個遠程調用請求的發送與接收過程。
3.1.調用方式
dubbo支持同步和異步兩種調用方式,其中異步調用分為“有返回值”的異步調用和“無返回值”的異步調用。所謂“無返回值”異步調用是指服務消費方只管調用,但不關心調用結果,此時 dubbo 會直接返回一個空的 RpcResult。若要使用異步特性,需要服務消費方手動進行配置。默認情況下,Dubbo 使用同步調用方式。
異步調用方式如下:
/* 異步方法配置 */ <dubbo:reference id="asyncService" check="false" interface="com.alibaba.dubbo.demo.AsyncService" url="localhost:20880"> <dubbo:method name="sayHello" async="true" /> </dubbo:reference>
Consumer端調用方式如下:
- 調用直接返回null
String result = asyncService.sayHello("world");
- 通過RpcContext獲取Future對象,調用get()阻塞直到返回結果
asyncService.sayHello("world"); Future<String> future = RpcContext.getContext().getFuture(); String result = future.get();
- 通過ResponseFuture設置回調,執行完成調用done(),異常調用caught()
asyncService.sayHello("world"); ResponseFuture responseFuture = ((FutureAdapter)RpcContext.getContext().getFuture()).getFuture(); responseFuture.setCallback(new ResponseCallback() { @Override public void done(Object response) { System.out.println("done"); } @Override public void caught(Throwable exception) { System.out.println("caught"); } }); try { System.out.println("result = " + responseFuture.get()); } catch (RemotingException e) { e.printStackTrace(); }
4.Dubbo VS SpringCloud VS Other
Dubbo實現了服務治理的基礎,但是要完成一個完備的微服務架構,還需要在各環節去擴展和完善以保證集群的健康,以減輕開發、測試以及運維各個環節上增加出來的壓力,這樣才能讓各環節人員真正的專注于業務邏輯。而Spring Cloud依然發揚了Spring Source整合一切的作風,以標準化的姿態將一些微服務架構的成熟產品與框架揉為一體,并繼承了Spring Boot簡單配置、快速開發、輕松部署的特點,讓原本復雜的架構工作變得相對容易上手一些。所以,如果選擇Dubbo請務必在各個環節做好整套解決方案的準備,不然很可能隨著服務數量的增長,整個團隊都將疲于應付各種架構上不足引起的困難。而如果選擇Spring Cloud,相對來說每個環節都已經有了對應的組件支持,可能有些也不一定能滿足你所有的需求。
5.踩坑實例
5.1.Reference注解
使用@Reference注解在消費者端注入提供者依賴的時候,MVC容器獲取對象時可能出現Null的情況。Reference注解生成的實例是不會交給Spring容器托管的,只是作為Spring管理bean的一個屬性賦值去操作,所以無法通過BeanFactory.getBean()獲得。如果容器初始化時先加載了MVC容器,隨后加載Dubbo實例,這時在Controller層引用的dubbo對象會造成空指針的異常,所以加載時需要進行編排,先加載dubbo實例,再加載MVC容器。另外一種方案是設計一個組件層,將dubbo和業務層在組件層進行組裝,提供Controller層使用。
5.2.Dubbo超時和重連機制
Dubbo默認有超時和重連機制,在異常場景下如果不注意會引起異常或者冪等性的異常情況。超時機制是在指定時間內Provider沒有返回,則認定本次調用失敗。默認重連2次,超時1000ms。另外Dubbo可以以服務,接口,方法3個緯度配置參數,值得注意的是配置以消費者端為準,如果消費者端未配置則以提供者端為準。
5.3.Dubbo序列化協議
Dubbo適合高并發量小數據傳輸的RPC場景,默認dubbo協議交互,但是出于性能考慮很多公司會采用kryo序列化方式來提升性能,kryo確實性能要高出很多,但是有一個弊端,就是序列化對象修改后不能向上向下兼容,換句話來說就是DTO對象增加了一個字段,服務提供者和消費者兩端需要同時升級版本,否則接口調用就會報錯。這里可以自己擴展Dubbo預留的Serialization接口在kryo的基礎上完善序列化協議。