一、前言
部門去年年中開始各種改造,第一步是子產品服務化,這邊初選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

如上圖所示,一個抽象出來的基本架構,consumer和provider是架構中必然存在的,Registry做為全局配置資訊管理子產品,推薦生産環境使用Registry,可實時推送現存活的服務提供者,Monitor一般用于監控和統計RPC調用情況、成功率、失敗率等情況,讓開發及運維了解線上運作情況。
應用執行過程大緻如下:
服務提供者啟動,根據協定資訊綁定到配置的IP和端口上,如果已有服務綁定過相同IP和端口的則跳過
注冊服務資訊至注冊中心
用戶端啟動,根據接口和協定資訊訂閱注冊中心中注冊的服務,注冊中心将存活的服務位址通知到用戶端,當有服務資訊變更時用戶端可以通過定時通知得到變更資訊
在用戶端需要調用服務時,從記憶體中拿到上次通知的所有存活服務位址,根據路由資訊和負載均衡機制選擇最終調用的服務位址,發起調用
通過filter分别在用戶端發送請求前和服務端接收請求後,通過異步記錄一些需要的資訊傳遞到monitor做監控或者統計
一般單獨有一個jar包,維護服務接口定義、RPC參數類型、RPC傳回類型、接口異常、接口用到的常量,該jar包中不處理任何業務邏輯。
比如命名api-0.1.jar,在api-0.1.jar中定義接口
并在api-0.1.jar中定義RpcResponseDto,RpcAccountRequestDto,RpcBusinessException,RpcSystemException。
服務端通過引用該jar包實作接口并暴露服務,用戶端引用該jar包引用接口的代理執行個體。
開源的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試下,還沒正式投入。
配置應用名
配置dubbo注解識别處理器,不指定包名的話會在spring bean中查找對應執行個體的類配置了dubbo注解的
配置注冊中心,通過group指定注冊中心分組,可通過register配置是否注冊到該注冊中心以及subscribe配置是否從該注冊中心訂閱
配置服務協定,多網卡可通過IP指定綁定的IP位址,不指定或者指定非法IP的情況下會綁定在0.0.0.0,使用Dubbo協定的服務會在初始化時建立長連接配接
通過xml配置檔案配置服務暴露,首先要有個spring bean執行個體(無論是注解配置的還是配置檔案配置的),在下面ref中指定bean執行個體ID,作為服務實作類
通過注解方式配置服務暴露,Component是Spring bean注解,Service是dubbo的注解(不要和spring bean的service注解弄混),如前文所述,dubbo注解隻會在spring bean中被識别
同服務端配置應用名、注解識别處理器和注冊中心。
配置用戶端reference
bean。用戶端跟服務端不同的是用戶端這邊沒有實際的實作類的,是以配置的dubbo:reference實際會生成一個spring
bean執行個體,作為代理處理Dubbo請求,然後其他要調用處直接使用spring bean的方式使用這個執行個體即可。
xml配置檔案配置方式,id即為spring bean的id,之後無論是在spring配置中使用ref="firstDubboService"還是通過@Autowired注解都OK
另外開發、測試環境可通過指定Url方式繞過注冊中心直連指定的服務位址,避免注冊中心中服務過多,啟動建立連接配接時間過長,如
注解配置方式引用,
Reference被識别的條件是spring bean執行個體對應的目前類中的field,如上是直接修飾spring bean目前類中的屬性
這個地方看了下源碼,本應該支援目前類和父類中的public set方法,但是看起來是個BUG,Dubbo處理reference處部分源碼如下
如果使用Dubbo自帶的監控中心,可通過簡單配置即可,先通過github獲得dubbo-monitor的源碼,部署啟動後在應用配置如下
最重要輔助功能之一,可随時配置路由規則調整用戶端調用政策,目前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.*等。
不比對的配置規則和比對的配置規則是一緻的。
配置完成後可在消費者标簽頁檢視路由結果
dubbo提供4種負載均衡方式:
Random,随機,按權重配置随機機率,調用量越大分布越均勻,預設是這種方式
RoundRobin,輪詢,按權重設定輪詢比例,如果存在比較慢的機器容易在這台機器的請求阻塞較多
LeastActive,最少活躍調用數,不支援權重,隻能根據自動識别的活躍數配置設定,不能靈活調配
ConsistentHash,一緻性hash,對相同參數的請求路由到一個服務提供者上,如果有類似灰階釋出需求可采用
dubbo的負載均衡機制是在用戶端調用時通過記憶體中的服務方資訊及配置的負責均衡政策選擇,如果對自己系統沒有一個全面認知,建議先采用random方式。
有需要自己實作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中包含該參數名且參數值不為空才被加載
如下是dubbo rpc access log的過濾器,僅對服務提供方有效,且參數中需要帶accesslog,也就是配置protocol或者serivce時配置的accesslog="d:/rpc_access.log"
http://alibaba.github.io/dubbo-doc-static/User+Guide-zh.htm#UserGuide-zh-%3Cdubbo%3Amonitor%2F%3E
可關注以上連結内容,dubbo提供較多的輔助功能特性,大多目前我們暫時未使用到,後續我們這邊關注到的兩個特性可能會再引進來使用:
結果緩存,省得自己再去寫一個緩存,對緩存沒有特殊要求的話直接使用dubbo的好了
分組合并,對RPC接口不同的實作方式分别調用然後合并結果的一種調用模式,比如我們要查使用者是否合法,一種我們要查是否在黑名單,同時我們還要關注登入資訊是否異常,然後合并結果
四、前車之鑒
這個主要是在整個學習及使用過程中記錄的,以及一些同僚在初識過程問過我的,這邊做了整理然後直接列舉在下面:
引用隻會找相應版本的服務
為了今後更換接口定義釋出線上時,可不停機釋出,使用版本号
為了在測試環境提供一個内網通路的位址和一個辦公區通路的位址。
•增加一個指定IP為内網位址的服務協定
•增加一個不指定IP的服務協定,但是在/etc/hosts中hostname對應的IP要為外網IP
上面這種方案是一開始使用的方案,後面發現dubbo在啟動過程無論是否配路由還是會一個個去連接配接,雖然不影響啟動,但是由于存在逾時是以會影響啟動時間,而且每台機器還得特别配置指定IP,後面使用另外一套方案:
服務不配置ip,綁定到0.0.0.0,自動擷取保證擷取到是内網IP注冊到注冊中心即可,如果不是想要的IP,可以在/etc/hosts中通過綁定Hostname指定IP
内網通路方式通過注冊中心或者直連指定内網IP和端口
外網通路方式通過直連指定外網IP和端口
使用這種方式需要注意做好防火牆控制等,比如線上預設也是不指定IP,會綁定在0.0.0.0,如果非法人員知道調用的外網IP和端口,而且可以直接通路就麻煩了(如果在應用中做IP攔截也成,需要注意有防範措施)。
前文介紹使用時已經提到過,@Reference隻能在spring bean執行個體對應的目前類中使用,暫時無法在父類使用;如果确實要在父類聲明一個引用,可通過配置檔案配置dubbo:reference,然後在需要引用的地方跟引用spring bean一樣就行
目前如果存在逾時,情況基本都在如下幾點:
用戶端耗時大,也就是逾時異常時的client elapsed xxx,這個是從建立Future對象開始到使用channel送出請求的這段時間,中間沒有複雜操作,隻要CPU沒問題基本不會出現大耗時,頂多1ms屬于正常
IOThread繁忙,預設情況下,dubbo協定一個用戶端與一個服務提供者會建立一個共享長連接配接,如果某個用戶端處于特别繁忙而且一直往一個服務提供者塞請求,可能造成IOThread阻塞,一般非常特殊的情況才會出現
服務端工作線程池中線程全部繁忙,接收消息後塞入隊列等待,如果等待時間比預想長會引起逾時
網絡抖動,如果上述情況都排除了,還出現在請求發出後,服務接收請求前超過預想時間,隻能歸類到網絡抖動了,需要SA一起檢視問題
服務自身耗時大,這個需要應用自身做好耗時統計,當出現這種情況的時候需要用資料來說明問題及規劃優化方案,建議采用緩存埋點的方式統計服務中各個執行階段的耗時情況,最終如果超過預想時間則把緩存統計的耗時情況打日志,減少日志量,且能夠得到更明确的資訊
現在我們應用使用過程中發現兩種類型的耗時,一種我們目前隻能歸類到網絡抖動,後續需要找運維一起關注這個問題,另外一種是由于一些曆史原因,資料庫查詢容易發生抖動,總有一個時間點會突然多出很多逾時。
服務保護的原則上是避免發生類似雪崩效應,盡量将異常控制在服務周圍,不要擴散開。
說到雪崩效應,還得提下dubbo自身的重試機制,預設3次,當失敗時會進行重試,這樣在某個時間點出現性能問題,然後調用方再連續重複調用,很容易引起雪崩,建議的話還是很據業務情況規劃好如何進行異常處理,何時進行重試。
服務保護的話,目前我們主要從以下幾個方面來實施,也不成熟,還在摸索:
考慮服務的dubbo線程池類型(fix線程池的話考慮線程池大小)、資料庫連接配接池、dubbo連接配接數限制是否都合适
考慮服務逾時時間和重試的關系,設定合适的值
一定時間内服務異常數較大,則可考慮使用failfast讓用戶端請求直接傳回或者讓用戶端不再請求
經上司推薦,還在學習Release it,後續有其他想法,再回頭來編輯。
前文已經提到過zkclient有兩個問題,修改伺服器時間會導緻容器挂掉;dubbo使用zkclient沒有傳逾時時間導緻zookeeper無法連接配接的時候,直接阻塞Integer.MAX_VALUE。
正在調研curator,目前隻能說curator不會在無法連接配接的時候直接阻塞。
另外zkclient和curator的jar包應該都是jdk1.6編譯的,是以系統還在jdk1.5以下的話無法使用。
這兩個東西完全不同的概念,使用的時候不要弄混了。
registry上可以配置group,用于區分不同分組的注冊中心,比如在同一個注冊中心下,有一部分注冊資訊是要給開發環境用的,有一部分注冊資訊時要給測試環境用的,可以分别用不同的group區分開,目前對這個了解還不透徹,大緻就是用于區分不同環境。
service和reference上也可以配置group,這個用于區分同一個接口的不同實作,隻有在reference上指定與service相同的group才會被發現,還有前文提到的分組合并結果也是用的這個。
五、dubbo如何工作的
其實dubbo整個架構内容并不算大,仔細看的話可能最多兩天看完一遍,但是目前還是沒領悟到怎麼做到的擴充性,學習深度還不夠~
要學習dubbo源碼的話,必須要拿出官方高清大圖才行。
這張圖看起來挺複雜的樣子,真正拆分之後對照源碼來看會發現非常清晰、簡單直覺。
入口就是各種dubbo配置項的解析,<dubbo:xxx />都是spring namespace,可以看到dubbo jar包下META-INF裡面的spring.handlers,自定義的spring namespace處理器。
對于spring不太熟的同學可以先了解下這個功能,入口都在這裡,解析成功後每個<dubbo:xxx />配置項都對應一個spring執行個體。
首先把這張圖拆分成三塊,首先是服務端剖去網絡傳輸子產品,也就是大圖中的右上角。
這裡主要抽幾個主要的類,從服務初始化到接收消息的流程簡單說明下,有興趣的再對照源碼看下會比較清晰。
ServiceBean
繼承ServiceConfig,做為服務配置管理和配置資訊校驗,每一個dubbo:service配置或者注解都會對應生成一個ServiceBean的執行個體,維護目前服務的配置資訊,并把一些全局配置塞入到該服務配置中。
另外ServiceBean本身是一個InitializingBean,在afterPropertiesSet時通過配置資訊引導服務綁定和注冊。
可以留意到ServiceBean還實作了ApplicationListener,在全部spring bean加載完成後判斷是否延遲加載的邏輯。
ProtocolFilterWrapper
經過serviceBean引導後進入該類,這個地方注意下,Protocol使用的裝飾模式,葉子隻有DubboProtocol和RegistryProtocol,在中間調用中會繞來繞去,而且registry會走一遍這個流程,然後在RegistryProtocol中暴露服務再走一遍,注意每個類的作用,不要被繞昏了就行,第一次跟進代碼的時候沒留意就暈頭轉向的。
在這之前其實還有個ProtocolListenerWrapper,封裝監聽器,在服務暴露後通知到監聽器,沒有複雜邏輯,如果沒特殊需求可以先繞過。
再來說ProtocolFIlterWrapper,這個類的作用就是串聯filter調用鍊,如果有看過struts或者spring mvc攔截器源碼的應該不會陌生。
RegistryProtocol
注冊中心協定,如果配置了注冊中心位址,每次服務暴露肯定首先引導進入這個類中,如果沒有注冊中心連接配接則會先建立連接配接,然後再引導真正的服務協定暴露流程,會再走一次ProtocolFilterWrapper的流程(這次引導到的葉子是DubboProtocol)。
在服務暴露傳回後,會再執行服務資訊的注冊和訂閱操作。
DubboProtocol
這個類的export相對較簡單,就是引導服務bind server socket。
另外該類還提供了一個内部類,用于處理接收請求,就是下面要提到的ExchangeHandler。
DubboProtocol$ExchangeHandler
接收反序列化好的請求消息,然後根據請求資訊找到執行鍊,将請求再丢入執行鍊,讓其最終執行到實作類再将執行結果傳回即整個過程完成。
用戶端子產品與服務端子產品比較類似,隻是剛好反過來,一個是暴露服務,一個是引用服務,然後用戶端多出路由和負載均衡。
ReferenceBean
繼承ReferenceConfig,維護配置資訊和配置資訊的校驗,該功能與ServiceBean類似
其本身還實作了FactoryBean,作為執行個體工廠,建立遠端調用代理類;而且如果不指定為init的reference都是在首次getBean的時候調用到該factoryBean的getObject才進行初始化
另外實作了InitializingBean,在初始化過程中引導配置資訊初始化和建構init的代理執行個體
InvokerInvocationHandler
看到這個類名應該就知道是動态代理的handler,這裡作為遠端調用代理類的處理器在用戶端調用接口時引導進入invoker調用鍊
ProtocolFIlterWrapper
與Service那邊的功能類似,建構調用鍊
與service那邊類似,如果與注冊中心還沒有連接配接則建立連接配接,之後注冊和訂閱,再根據配置的政策傳回相應的clusterInvoker
比service那邊有個隐藏較深的邏輯需要留意的,就是訂閱過程,RegistryDirectory作為訂閱監聽器,在訂閱完成後會通知到RegistryDirectory,然後會重新整理invoker,進入引導至DubboProtocol的流程,與變更的service建立長連接配接,第一次發生訂閱時就會同步接收到通知并将已存在的service存到字典
在訂閱過程中發現有service變更則會引導至這裡,與服務建立長連接配接,整個過程為了得到串聯執行鍊Invoker
ClusterInvoker
ClusterInvoker由RegistryProtocol建構完成後,内部封裝了Directory,在調用時會從Directory列舉存活的service對應的Invoker,Directory作為被通知對象,在service有變更時也會及時得到通知
調用時在叢集中發現存在多節點的話都會通過clusterInvoker來根據配置抉擇最終調用的節點,包括路由方式、負載均衡等
dubbo本身支援的節點調用政策包括比如failoverClusterInvoker在失敗時進行重試其他節點,failfastClusterInvoker在失敗時傳回異常,mergeableClusterInvoker則是對多個實作結果進行合并的等等很多
DubboInvoker
承接上層的調用資訊,作為調用結構的葉子,将資訊傳遞到exchange層,主要用來和echange互動的功能子產品
從exchange往下都是算網絡傳輸,包括做序列化、反序列化,使用Netty等IO架構發送接收消息等邏輯,先前看的時候沒有做統一梳理,後續有機會再來編輯吧。
轉自:https://blog.csdn.net/hzzhoushaoyu/article/details/43273099