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中定義接口

 

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"/>

配置服務協議,多網卡可通過IP指定綁定的IP地址,不指定或者指定非法IP的情況下會綁定在0.0.0.0,使用Dubbo協議的服務會在初始化時建立長連接

<dubbo:protocol name="dubbo" port="20880" accesslog="d:/access.log"></dubbo:protocol>

通過xml配置文件配置服務暴露,首先要有個spring bean實例(無論是注解配置的還是配置文件配置的),在下面ref中指定bean實例ID,作為服務實現類

<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中被識別

@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);

}

}

Reference被識別的條件是spring bean實例對應的當前類中的field,如上是直接修飾spring bean當前類中的屬性

這個地方看了下源碼,本應該支持當前類和父類中的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自帶的監控中心,可通過簡單配置即可,先通過github獲得dubbo-monitor的源碼,部署啟動后在應用配置如下

<dubbo:monitor protocol="registry" /> <!--通過注冊中心獲取monitor地址后建立連接-->

 

<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,對相同參數的請求路由到一個服務提供者上,如果有類似灰度發布需求可采用

dubbo的負載均衡機制是在客戶端調用時通過內存中的服務方信息及配置的負責均衡策略選擇,如果對自己系統沒有一個全面認知,建議先采用random方式。

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中包含該參數名且參數值不為空才被加載

如下是dubbo rpc access log的過濾器,僅對服務提供方有效,且參數中需要帶accesslog,也就是配置protocol或者serivce時配置的accesslog="d:/rpc_access.log"

@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問題

為了在測試環境提供一個內網訪問的地址和一個辦公區訪問的地址。

 

•增加一個指定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攔截也成,需要注意有防范措施)。

 

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讓客戶端請求直接返回或者讓客戶端不再請求

 

經領導推薦,還在學習Release it,后續有其他想法,再回頭來編輯。

 

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

繼承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

接收反序列化好的請求消息,然后根據請求信息找到執行鏈,將請求再丟入執行鏈,讓其最終執行到實現類再將執行結果返回即整個過程完成。

 

3.客戶端

客戶端模塊與服務端模塊比較類似,只是剛好反過來,一個是暴露服務,一個是引用服務,然后客戶端多出路由和負載均衡。

 

 

ReferenceBean

繼承ReferenceConfig,維護配置信息和配置信息的校驗,該功能與ServiceBean類似

其本身還實現了FactoryBean,作為實例工廠,創建遠程調用代理類;而且如果不指定為init的reference都是在首次getBean的時候調用到該factoryBean的getObject才進行初始化

另外實現了InitializingBean,在初始化過程中引導配置信息初始化和構建init的代理實例

InvokerInvocationHandler

看到這個類名應該就知道是動態代理的handler,這里作為遠程調用代理類的處理器在客戶端調用接口時引導進入invoker調用鏈

ProtocolFIlterWrapper

與Service那邊的功能類似,構建調用鏈

RegistryProtocol

與service那邊類似,如果與注冊中心還沒有連接則建立連接,之后注冊和訂閱,再根據配置的策略返回相應的clusterInvoker

比service那邊有個隱藏較深的邏輯需要留意的,就是訂閱過程,RegistryDirectory作為訂閱監聽器,在訂閱完成后會通知到RegistryDirectory,然后會刷新invoker,進入引導至DubboProtocol的流程,與變更的service建立長連接,第一次發生訂閱時就會同步接收到通知並將已存在的service存到字典

DubboProtocol

在訂閱過程中發現有service變更則會引導至這里,與服務建立長連接,整個過程為了得到串聯執行鏈Invoker

ClusterInvoker

ClusterInvoker由RegistryProtocol構建完成后,內部封裝了Directory,在調用時會從Directory列舉存活的service對應的Invoker,Directory作為被通知對象,在service有變更時也會及時得到通知

調用時在集群中發現存在多節點的話都會通過clusterInvoker來根據配置抉擇最終調用的節點,包括路由方式、負載均衡等

dubbo本身支持的節點調用策略包括比如failoverClusterInvoker在失敗時進行重試其他節點,failfastClusterInvoker在失敗時返回異常,mergeableClusterInvoker則是對多個實現結果進行合並的等等很多

DubboInvoker

承接上層的調用信息,作為調用結構的葉子,將信息傳遞到exchange層,主要用來和echange交互的功能模塊

4.網絡傳輸層

從exchange往下都是算網絡傳輸,包括做序列化、反序列化,使用Netty等IO框架發送接收消息等邏輯,先前看的時候沒有做統一梳理,后續有機會再來編輯吧。

 

一、前言部門去年年中開始各種改造,第一步是模塊服務化,這邊初選dubbo試用在一些非重要模塊上,慢慢引入到一些稍微重要的功能上,半年時間,學習過程及線上使用遇到的些問題在此總結下。
整理這篇文章差不多花了兩天半時間,請尊重勞動成果,如轉載請注明出處http://blog.csdn.net/hzzhoushaoyu/article/details/43273099
二、什么是dubboDubbo是阿里巴巴提供的開源的SOA服務化治理的技術框架,據說只是剖出來的一部分開源的,但一些基本的需求已經可以滿足的,而且擴展性也非常好(至今沒領悟到擴展性怎么做到的),通過spring bean的方式管理配置及實例,較容易上手且對應用無侵入。更多介紹可戳http://alibaba.github.io/dubbo-doc-static/Home-zh.htm。
三、如何使用dubbo1.服務化應用基本框架

如上圖所示,一個抽象出來的基本框架,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台后集群不可用redismulticast,廣播受到網絡結構的影響,一般本地不想搭注冊中心的話使用這種調用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"/>配置服務協議,多網卡可通過IP指定綁定的IP地址,不指定或者指定非法IP的情況下會綁定在0.0.0.0,使用Dubbo協議的服務會在初始化時建立長連接<dubbo:protocol name="dubbo" port="20880" accesslog="d:/access.log"></dubbo:protocol>通過xml配置文件配置服務暴露,首先要有個spring bean實例(無論是注解配置的還是配置文件配置的),在下面ref中指定bean實例ID,作為服務實現類<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中被識別@Component@Service(version="1.0")public class FirstDubboServiceImpl implements FirstDubboService{ @Overridepublic 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>注解配置方式引用,@Componentpublic 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處部分源碼如下
        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的源碼,部署啟動后在應用配置如下<dubbo:monitor protocol="registry" /> <!--通過注冊中心獲取monitor地址后建立連接-->
<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.42.消費者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,對相同參數的請求路由到一個服務提供者上,如果有類似灰度發布需求可采用dubbo的負載均衡機制是在客戶端調用時通過內存中的服務方信息及配置的負責均衡策略選擇,如果對自己系統沒有一個全面認知,建議先采用random方式。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中包含該參數名且參數值不為空才被加載如下是dubbo rpc access log的過濾器,僅對服務提供方有效,且參數中需要帶accesslog,也就是配置protocol或者serivce時配置的accesslog="d:/rpc_access.log"@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問題為了在測試環境提供一個內網訪問的地址和一個辦公區訪問的地址。
•增加一個指定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攔截也成,需要注意有防范措施)。
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讓客戶端請求直接返回或者讓客戶端不再請求
經領導推薦,還在學習Release it,后續有其他想法,再回頭來編輯。
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繼承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接收反序列化好的請求消息,然后根據請求信息找到執行鏈,將請求再丟入執行鏈,讓其最終執行到實現類再將執行結果返回即整個過程完成。
3.客戶端客戶端模塊與服務端模塊比較類似,只是剛好反過來,一個是暴露服務,一個是引用服務,然后客戶端多出路由和負載均衡。
ReferenceBean繼承ReferenceConfig,維護配置信息和配置信息的校驗,該功能與ServiceBean類似其本身還實現了FactoryBean,作為實例工廠,創建遠程調用代理類;而且如果不指定為init的reference都是在首次getBean的時候調用到該factoryBean的getObject才進行初始化另外實現了InitializingBean,在初始化過程中引導配置信息初始化和構建init的代理實例InvokerInvocationHandler看到這個類名應該就知道是動態代理的handler,這里作為遠程調用代理類的處理器在客戶端調用接口時引導進入invoker調用鏈ProtocolFIlterWrapper與Service那邊的功能類似,構建調用鏈RegistryProtocol與service那邊類似,如果與注冊中心還沒有連接則建立連接,之后注冊和訂閱,再根據配置的策略返回相應的clusterInvoker比service那邊有個隱藏較深的邏輯需要留意的,就是訂閱過程,RegistryDirectory作為訂閱監聽器,在訂閱完成后會通知到RegistryDirectory,然后會刷新invoker,進入引導至DubboProtocol的流程,與變更的service建立長連接,第一次發生訂閱時就會同步接收到通知並將已存在的service存到字典DubboProtocol在訂閱過程中發現有service變更則會引導至這里,與服務建立長連接,整個過程為了得到串聯執行鏈InvokerClusterInvokerClusterInvoker由RegistryProtocol構建完成后,內部封裝了Directory,在調用時會從Directory列舉存活的service對應的Invoker,Directory作為被通知對象,在service有變更時也會及時得到通知調用時在集群中發現存在多節點的話都會通過clusterInvoker來根據配置抉擇最終調用的節點,包括路由方式、負載均衡等dubbo本身支持的節點調用策略包括比如failoverClusterInvoker在失敗時進行重試其他節點,failfastClusterInvoker在失敗時返回異常,mergeableClusterInvoker則是對多個實現結果進行合並的等等很多DubboInvoker承接上層的調用信息,作為調用結構的葉子,將信息傳遞到exchange層,主要用來和echange交互的功能模塊4.網絡傳輸層從exchange往下都是算網絡傳輸,包括做序列化、反序列化,使用Netty等IO框架發送接收消息等邏輯,先前看的時候沒有做統一梳理,后續有機會再來編輯吧。————————————————版權聲明:本文為CSDN博主「傑克和仙豆」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。原文鏈接:https://blog.csdn.net/hzzhoushaoyu/article/details/43273099


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM