原文見:http://blog.csdn.net/hzzhoushaoyu/article/details/43273099
一、前言
部門去年年中開始各種改造,第一步是模塊服務化,這邊初選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中定義接口
- 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.服務端
配置應用名
- <dubbo:application name="test"/>
配置dubbo注解識別處理器,不指定包名的話會在spring bean中查找對應實例的類配置了dubbo注解的
- <dubbo:annotation/>
配置注冊中心,通過group指定注冊中心分組,可通過register配置是否注冊到該注冊中心以及subscribe配置是否從該注冊中心訂閱
- <dubbo:registry address="zookeeper://127.0.0.1:2181/" group="test"/>
- <dubbo:protocol name="dubbo" port="20880" accesslog="d:/access.log"></dubbo:protocol>
- <dubbo:service interface="com.web.foo.service.FirstDubboService" ref="firstDubboServiceImpl" version="1.0"></dubbo:service>
- @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
- <dubbo:reference interface="com.web.foo.service.FirstDubboService"
- version="1.0" id="firstDubboService" ></dubbo:reference>
另外開發、測試環境可通過指定Url方式繞過注冊中心直連指定的服務地址,避免注冊中心中服務過多,啟動建立連接時間過長,如
- <dubbo:reference interface="com.web.foo.service.FirstDubboService"
- version="1.0" id="firstDubboService" url="dubbo://127.0.0.1:20880/"></dubbo:reference>
- @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);
- }
- }
這個地方看了下源碼,本應該支持當前類和父類中的public set方法,但是看起來是個BUG,Dubbo處理reference處部分源碼如下
- 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:monitor protocol="registry" /> <!--通過注冊中心獲取monitor地址后建立連接-->
- <dubbo:monitor address="dubbo://127.0.0.1:7070/com.alibaba.dubbo.monitor.MonitorService" /> <!--繞過注冊中心直連monitor,同consumer直連-->
7.服務路由


8.負載均衡
- Random,隨機,按權重配置隨機概率,調用量越大分布越均勻,默認是這種方式
- RoundRobin,輪詢,按權重設置輪詢比例,如果存在比較慢的機器容易在這臺機器的請求阻塞較多
- LeastActive,最少活躍調用數,不支持權重,只能根據自動識別的活躍數分配,不能靈活調配
- ConsistentHash,一致性hash,對相同參數的請求路由到一個服務提供者上,如果有類似灰度發布需求可采用
9.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中包含該參數名且參數值不為空才被加載
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問題
為了在測試環境提供一個內網訪問的地址和一個辦公區訪問的地址。
- 服務不配置ip,綁定到0.0.0.0,自動獲取保證獲取到是內網IP注冊到注冊中心即可,如果不是想要的IP,可以在/etc/hosts中通過綁定Hostname指定IP
- 內網訪問方式通過注冊中心或者直連指定內網IP和端口
- 外網訪問方式通過直連指定外網IP和端口
3.dubbo reference注解問題
4.服務超時問題
- 客戶端耗時大,也就是超時異常時的client elapsed xxx,這個是從創建Future對象開始到使用channel發出請求的這段時間,中間沒有復雜操作,只要CPU沒問題基本不會出現大耗時,頂多1ms屬于正常
- IOThread繁忙,默認情況下,dubbo協議一個客戶端與一個服務提供者會建立一個共享長連接,如果某個客戶端處于特別繁忙而且一直往一個服務提供者塞請求,可能造成IOThread阻塞,一般非常特殊的情況才會出現
- 服務端工作線程池中線程全部繁忙,接收消息后塞入隊列等待,如果等待時間比預想長會引起超時
- 網絡抖動,如果上述情況都排除了,還出現在請求發出后,服務接收請求前超過預想時間,只能歸類到網絡抖動了,需要SA一起查看問題
- 服務自身耗時大,這個需要應用自身做好耗時統計,當出現這種情況的時候需要用數據來說明問題及規劃優化方案,建議采用緩存埋點的方式統計服務中各個執行階段的耗時情況,最終如果超過預想時間則把緩存統計的耗時情況打日志,減少日志量,且能夠得到更明確的信息
5.服務保護
- 考慮服務的dubbo線程池類型(fix線程池的話考慮線程池大小)、數據庫連接池、dubbo連接數限制是否都合適
- 考慮服務超時時間和重試的關系,設置合適的值
- 一定時間內服務異常數較大,則可考慮使用failfast讓客戶端請求直接返回或者讓客戶端不再請求
6.zkclient的問題
7.注冊中心的分組group和服務的不同實現group
五、dubbo如何工作的

1.如何跟進源碼
2.服務提供者

- ServiceBean
- ProtocolFilterWrapper
- RegistryProtocol
- DubboProtocol
- DubboProtocol$ExchangeHandler
3.客戶端

- ReferenceBean
- InvokerInvocationHandler
- ProtocolFIlterWrapper
- RegistryProtocol
- DubboProtocol
- ClusterInvoker
- DubboInvoker