dubbo學習過程、使用經驗分享及實現原理簡單介紹
一、前言
部門去年年中開始各種改造,第一步是模塊服務化,這邊初選dubbo試用在一些非重要模塊上,慢慢引入到一些稍微重要的功能上,半年時間,學習過程及線上使用遇到的些問題在此總結下。
整理這篇文章差不多花了兩天半時間,請尊重勞動成果,如轉載請註明出處http://blog.csdn.net/hzzhoushaoyu/article/details/43273099
二、什麽是dubbo
Dubbo是阿裏巴巴提供的開源的SOA服務化治理的技術框架,據說只是剖出來的一部分開源的,但一些基本的需求已經可以滿足的,而且擴展性也非常好(至今沒領悟到擴展性怎麽做到的),通過spring bean的方式管理配置及實例,較容易上手且對應用無侵入。更多介紹可戳http://alibaba.github.io/dubbo-doc-static/Home-zh.htm。
三、如何使用dubbo
1.服務化應用基本框架
如上圖所示,一個抽象出來的基本框架,consumer和provider是框架中必然存在的,Registry做為全局配置信息管理模塊,推薦生產環境使用Registry,可實時推送現存活的服務提供者,Monitor一般用於監控和統計RPC調用情況、成功率、失敗率等情況,讓開發及運維了解線上運行情況。
應用執行過程大致如下:
- 服務提供者啟動,根據協議信息綁定到配置的IP和端口上,如果已有服務綁定過相同IP和端口的則跳過
- 註冊服務信息至註冊中心
- 客戶端啟動,根據接口和協議信息訂閱註冊中心中註冊的服務,註冊中心將存活的服務地址通知到客戶端,當有服務信息變更時客戶端可以通過定時通知得到變更信息
- 在客戶端需要調用服務時,從內存中拿到上次通知的所有存活服務地址,根據路由信息和負載均衡機制選擇最終調用的服務地址,發起調用
- 通過filter分別在客戶端發送請求前和服務端接收請求後,通過異步記錄一些需要的信息傳遞到monitor做監控或者統計
2.服務接口定義
一般單獨有一個jar包,維護服務接口定義、RPC參數類型、RPC返回類型、接口異常、接口用到的常量,該jar包中不處理任何業務邏輯。
比如命名api-0.1.jar,在api-0.1.jar中定義接口
[java] view plain copy- public interface UserService
- {
- public RpcResponseDto isValidUser(RpcAccountRequestDto requestDto) throws new RpcBusinessException, RpcSystemException;
- }
並在api-0.1.jar中定義RpcResponseDto,RpcAccountRequestDto,RpcBusinessException,RpcSystemException。
服務端通過引用該jar包實現接口並暴露服務,客戶端引用該jar包引用接口的代理實例。
3.註冊中心
開源的dubbo已支持4種組件作為註冊中心,我們部門使用推薦的zookeeper做為註冊中心,由於就瓶頸來說不會出現在註冊中心,風險較低,未做特別的研究或比較。
- zookeeper,推薦集群中部署奇數個節點,由於zookeeper掛掉一半的機器集群就不可用,所以部署4臺和3臺的集群都是在掛掉2臺後集群不可用
- redis
- multicast,廣播受到網絡結構的影響,一般本地不想搭註冊中心的話使用這種調用
- dubbo簡易註冊中心
對於zookeeper客戶端,dubbo在2.2.0之後默認使用zkclient,2.3.0之後提供可選配置Curator,提到這個點的原因主要是因為zkclient發現一些問題:①服務器在修改服務器時間後zkClient會拋出日誌錯誤之類的異常然後容器(我們使用resin)掛掉了,也不能確定就是zkClient的問題,接入dubbo之前無該問題②dubbo使用zkclient不傳入連接zookeeper等待超時時間,使用默認的Integer.MAX_VALUE,這樣在zookeeper連不上的情況下不報錯也無法啟動;目前我們準備尋找其他解決方案,比如使用curator試下,還沒正式投入。
4.服務端
配置應用名
[html] view plain copy- <dubbo:application name="test"/>
配置dubbo註解識別處理器,不指定包名的話會在spring bean中查找對應實例的類配置了dubbo註解的
- <dubbo:annotation/>
配置註冊中心,通過group指定註冊中心分組,可通過register配置是否註冊到該註冊中心以及subscribe配置是否從該註冊中心訂閱
[html] view plain copy- <dubbo:registry address="zookeeper://127.0.0.1:2181/" group="test"/>
配置服務協議,多網卡可通過IP指定綁定的IP地址,不指定或者指定非法IP的情況下會綁定在0.0.0.0,使用Dubbo協議的服務會在初始化時建立長連接
[html] view plain copy- <dubbo:protocol name="dubbo" port="20880" accesslog="d:/access.log"></dubbo:protocol>
通過xml配置文件配置服務暴露,首先要有個spring bean實例(無論是註解配置的還是配置文件配置的),在下面ref中指定bean實例ID,作為服務實現類
[html] view plain copy- <dubbo:service interface="com.web.foo.service.FirstDubboService" ref="firstDubboServiceImpl" version="1.0"></dubbo:service>
通過註解方式配置服務暴露,Component是Spring bean註解,Service是dubbo的註解(不要和spring bean的service註解弄混),如前文所述,dubbo註解只會在spring bean中被識別
[java] view plain copy- @Component
- @Service(version="1.0")
- public class FirstDubboServiceImpl implements FirstDubboService
- {
- @Override
- public void sayHello(TestDto test)
- {
- System.out.println("Hello World!");
- }
- }
5.客戶端
同服務端配置應用名、註解識別處理器和註冊中心。
配置客戶端reference
bean。客戶端跟服務端不同的是客戶端這邊沒有實際的實現類的,所以配置的dubbo:reference實際會生成一個spring
bean實例,作為代理處理Dubbo請求,然後其他要調用處直接使用spring bean的方式使用這個實例即可。
xml配置文件配置方式,id即為spring bean的id,之後無論是在spring配置中使用ref="firstDubboService"還是通過@Autowired註解都OK
[html] view plain copy- <dubbo:reference interface="com.web.foo.service.FirstDubboService"
- version="1.0" id="firstDubboService" ></dubbo:reference>
另外開發、測試環境可通過指定Url方式繞過註冊中心直連指定的服務地址,避免註冊中心中服務過多,啟動建立連接時間過長,如
[html] view plain copy- <dubbo:reference interface="com.web.foo.service.FirstDubboService"
- version="1.0" id="firstDubboService" url="dubbo://127.0.0.1:20880/"></dubbo:reference>
註解配置方式引用,
[java] view plain copy- @Component
- public class Consumer
- {
- @Reference(version="1.0")
- private FirstDubboService service;
- public void test()
- {
- TestDto test = new TestDto();
- test.setList(Arrays.asList(new String[]{"a", "b"}));
- test.setTest("t");
- service.sayHello(test);
- }
- }
Reference被識別的條件是spring bean實例對應的當前類中的field,如上是直接修飾spring bean當前類中的屬性
這個地方看了下源碼,本應該支持當前類和父類中的public set方法,但是看起來是個BUG,Dubbo處理reference處部分源碼如下
[java] view plain copy- Method[] methods = bean.getClass().getMethods();
- for (Method method : methods) {
- String name = method.getName();
- if (name.length() > 3 && name.startsWith("set")
- && method.getParameterTypes().length == 1
- && Modifier.isPublic(method.getModifiers())
- && ! Modifier.isStatic(method.getModifiers())) {
- try {
- Reference reference = method.getAnnotation(Reference.class);
- if (reference != null) {
- Object value = refer(reference, method.getParameterTypes()[0]);
- if (value != null) {
- method.invoke(bean, new Object[] { });//??這裏不是應該把value作為參數調用麽,而且為什麽上面if條件判斷參數為1這裏不傳參數
- }
- }
- } catch (Throwable e) {
- logger.error("Failed to init remote service reference at method " + name + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
- }
- }
- }
6.監控中心
如果使用Dubbo自帶的監控中心,可通過簡單配置即可,先通過github獲得dubbo-monitor的源碼,部署啟動後在應用配置如下 [html] view plain copy- <dubbo:monitor protocol="registry" /> <!--通過註冊中心獲取monitor地址後建立連接-->
[html] view plain copy
- <dubbo:monitor address="dubbo://127.0.0.1:7070/com.alibaba.dubbo.monitor.MonitorService" /> <!--繞過註冊中心直連monitor,同consumer直連-->
7.服務路由
最重要輔助功能之一,可隨時配置路由規則調整客戶端調用策略,目前dubbo-admin中已提供基本路由規則的配置UI,到github下載源碼部署後很容易找到地方,這裏簡單介紹下怎麽用路由。 下面是dubbo-admin的新建路由界面,可配置信息都在圖片中有, 比如現在我們有10.0.0.1~3三臺消費者和10.0.0.4~6三臺服務提供者,想讓1和2調用4,3調用5和6的話,則可以配置兩個規則, 1.消費者IP:10.0.0.1,10.0.0.2 ;提供者IP:10.0.0.4 2.消費者IP:10.0.0.3;提供者IP:10.0.0.5,10.0.0.6 另外,IP地址支持結尾為*匹配所有,如10.0.0.*或者10.0.*等。 不匹配的配置規則和匹配的配置規則是一致的。 配置完成後可在消費者標簽頁查看路由結果8.負載均衡
dubbo提供4種負載均衡方式:- Random,隨機,按權重配置隨機概率,調用量越大分布越均勻,默認是這種方式
- RoundRobin,輪詢,按權重設置輪詢比例,如果存在比較慢的機器容易在這臺機器的請求阻塞較多
- LeastActive,最少活躍調用數,不支持權重,只能根據自動識別的活躍數分配,不能靈活調配
- ConsistentHash,一致性hash,對相同參數的請求路由到一個服務提供者上,如果有類似灰度發布需求可采用
9.dubbo過濾器
有需要自己實現dubbo過濾器的,可關註如下步驟:- dubbo初始化過程加載META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/三個路徑(classloaderresource)下面的com.alibaba.dubbo.rpc.Filter文件
- 文件配置每行Name=FullClassName,必須是實現Filter接口
- @Activate標註擴展能被自動激活
- @Activate如果group(provider|consumer)匹配才被加載
- @Activate的value字段標明過濾條件,不寫則所有條件下都會被加載,寫了則只有dubbo URL中包含該參數名且參數值不為空才被加載
- @Activate(group = Constants.PROVIDER, value = Constants.ACCESS_LOG_KEY)
- public class AccessLogFilter implements Filter {
- }
10.其他特性
http://alibaba.github.io/dubbo-doc-static/User+Guide-zh.htm#UserGuide-zh-%3Cdubbo%3Amonitor%2F%3E
可關註以上鏈接內容,dubbo提供較多的輔助功能特性,大多目前我們暫時未使用到,後續我們這邊關註到的兩個特性可能會再引進來使用:- 結果緩存,省得自己再去寫一個緩存,對緩存沒有特殊要求的話直接使用dubbo的好了
- 分組合並,對RPC接口不同的實現方式分別調用然後合並結果的一種調用模式,比如我們要查用戶是否合法,一種我們要查是否在黑名單,同時我們還要關註登錄信息是否異常,然後合並結果
四、前車之鑒
這個主要是在整個學習及使用過程中記錄的,以及一些同事在初識過程問過我的,這邊做了整理然後直接列舉在下面:1.服務版本號
- 引用只會找相應版本的服務
- <dubbo:serviceinterface=“com.xxx.XxxService” ref=“xxxService” version=“1.0” />
- <dubbo:referenceid=“xxxService” interface=“com.xxx.XxxService” version=“1.0”/>
- 為了今後更換接口定義發布在線時,可不停機發布,使用版本號
2.暴露一個內網一個外網IP問題
為了在測試環境提供一個內網訪問的地址和一個辦公區訪問的地址。
上面這種方案是一開始使用的方案,後面發現dubbo在啟動過程無論是否配路由還是會一個個去連接,雖然不影響啟動,但是由於存在超時所以會影響啟動時間,而且每臺機器還得特別配置指定IP,後面使用另外一套方案:
- 服務不配置ip,綁定到0.0.0.0,自動獲取保證獲取到是內網IP註冊到註冊中心即可,如果不是想要的IP,可以在/etc/hosts中通過綁定Hostname指定IP
- 內網訪問方式通過註冊中心或者直連指定內網IP和端口
- 外網訪問方式通過直連指定外網IP和端口
3.dubbo reference註解問題
前文介紹使用時已經提到過,@Reference只能在spring bean實例對應的當前類中使用,暫時無法在父類使用;如果確實要在父類聲明一個引用,可通過配置文件配置dubbo:reference,然後在需要引用的地方跟引用spring bean一樣就行4.服務超時問題
目前如果存在超時,情況基本都在如下幾點:- 客戶端耗時大,也就是超時異常時的client elapsed xxx,這個是從創建Future對象開始到使用channel發出請求的這段時間,中間沒有復雜操作,只要CPU沒問題基本不會出現大耗時,頂多1ms屬於正常
- IOThread繁忙,默認情況下,dubbo協議一個客戶端與一個服務提供者會建立一個共享長連接,如果某個客戶端處於特別繁忙而且一直往一個服務提供者塞請求,可能造成IOThread阻塞,一般非常特殊的情況才會出現
- 服務端工作線程池中線程全部繁忙,接收消息後塞入隊列等待,如果等待時間比預想長會引起超時
- 網絡抖動,如果上述情況都排除了,還出現在請求發出後,服務接收請求前超過預想時間,只能歸類到網絡抖動了,需要SA一起查看問題
- 服務自身耗時大,這個需要應用自身做好耗時統計,當出現這種情況的時候需要用數據來說明問題及規劃優化方案,建議采用緩存埋點的方式統計服務中各個執行階段的耗時情況,最終如果超過預想時間則把緩存統計的耗時情況打日誌,減少日誌量,且能夠得到更明確的信息
5.服務保護
服務保護的原則上是避免發生類似雪崩效應,盡量將異常控制在服務周圍,不要擴散開。 說到雪崩效應,還得提下dubbo自身的重試機制,默認3次,當失敗時會進行重試,這樣在某個時間點出現性能問題,然後調用方再連續重復調用,很容易引起雪崩,建議的話還是很據業務情況規劃好如何進行異常處理,何時進行重試。 服務保護的話,目前我們主要從以下幾個方面來實施,也不成熟,還在摸索:- 考慮服務的dubbo線程池類型(fix線程池的話考慮線程池大小)、數據庫連接池、dubbo連接數限制是否都合適
- 考慮服務超時時間和重試的關系,設置合適的值
- 一定時間內服務異常數較大,則可考慮使用failfast讓客戶端請求直接返回或者讓客戶端不再請求
6.zkclient的問題
前文已經提到過zkclient有兩個問題,修改服務器時間會導致容器掛掉;dubbo使用zkclient沒有傳超時時間導致zookeeper無法連接的時候,直接阻塞Integer.MAX_VALUE。 正在調研curator,目前只能說curator不會在無法連接的時候直接阻塞。 另外zkclient和curator的jar包應該都是jdk1.6編譯的,所以系統還在jdk1.5以下的話無法使用。7.註冊中心的分組group和服務的不同實現group
這兩個東西完全不同的概念,使用的時候不要弄混了。 registry上可以配置group,用於區分不同分組的註冊中心,比如在同一個註冊中心下,有一部分註冊信息是要給開發環境用的,有一部分註冊信息時要給測試環境用的,可以分別用不同的group區分開,目前對這個理解還不透徹,大致就是用於區分不同環境。 service和reference上也可以配置group,這個用於區分同一個接口的不同實現,只有在reference上指定與service相同的group才會被發現,還有前文提到的分組合並結果也是用的這個。五、dubbo如何工作的
其實dubbo整個框架內容並不算大,仔細看的話可能最多兩天看完一遍,但是目前還是沒領悟到怎麽做到的擴展性,學習深度還不夠~ 要學習dubbo源碼的話,必須要拿出官方高清大圖才行。 這張圖看起來挺復雜的樣子,真正拆分之後對照源碼來看會發現非常清晰、簡單直觀。1.如何跟進源碼
入口就是各種dubbo配置項的解析,<dubbo:xxx />都是spring namespace,可以看到dubbo jar包下META-INF裏面的spring.handlers,自定義的spring namespace處理器。 對於spring不太熟的同學可以先了解下這個功能,入口都在這裏,解析成功後每個<dubbo:xxx />配置項都對應一個spring實例。2.服務提供者
首先把這張圖拆分成三塊,首先是服務端剖去網絡傳輸模塊,也就是大圖中的右上角。 這裏主要抽幾個主要的類,從服務初始化到接收消息的流程簡單說明下,有興趣的再對照源碼看下會比較清晰。- ServiceBean
- ProtocolFilterWrapper
- RegistryProtocol
- DubboProtocol
- DubboProtocol$ExchangeHandler
3.客戶端
客戶端模塊與服務端模塊比較類似,只是剛好反過來,一個是暴露服務,一個是引用服務,然後客戶端多出路由和負載均衡。- ReferenceBean
- InvokerInvocationHandler
- ProtocolFIlterWrapper
- RegistryProtocol
- DubboProtocol
- ClusterInvoker
- DubboInvoker
4.網絡傳輸層
從exchange往下都是算網絡傳輸,包括做序列化、反序列化,使用Netty等IO框架發送接收消息等邏輯,先前看的時候沒有做統一梳理,後續有機會再來編輯吧。
dubbo學習過程、使用經驗分享及實現原理簡單介紹