文章目錄
註解配置
服務提供方
@Slf4j
@Service //屬於Dubbo的@Service註解,非Spring 作用:暴露服務
@Component
public class UserServiceImpl implements UserService {
@Override
public String userAddress(String userId) {
log.info("請求提供者服者參數:[{}]", userId);
return userId;
}
}
消費方
@Slf4j
@Component
public class Uservice {
@Reference
private UserService userService;
public void getUserAddress(String userId) {
String result = userService.userAddress(userId);
log.info("消費端請求結果:[{}]", result);
}
}
啓動時檢查
Dubbo 缺省會在啓動時檢查依賴的服務是否可用,不可用時會拋出異常,阻止 Spring 初始化完成,以便上線時,能及早發現問題,默認 check=“true” 。
可以通過 check=“false” 關閉檢查,比如,測試時,有些服務不關心,或者出現了循環依賴,必須有一方先啓動。
另外,如果你的 Spring 容器是懶加載的,或者通過 API 編程延遲引用服務,請關閉 check,否則服務臨時不可用時,會拋出異常,拿到 null 引用,如果 check=“false” ,總是會返回引用,當服務恢復時,能自動連上。
關閉某個服務的啓動時檢查 (沒有提供者時報錯):
<dubbo:reference interface="com.foo.BarService" check="false" />
關閉所有服務的啓動時檢查 (沒有提供者時報錯):
<dubbo:consumer check="false" />
集羣容錯
在集羣調用失敗時,Dubbo 提供了多種容錯方案,缺省爲 failover 重試。
[DUBBO] Notify urls for subscribe url
consumer://169.254.75.192/com.myke.api.UserService?
application=user-service-consumer
&category=providers,configurators,routers
&default.check=false&dubbo=2.6.2
&interface=com.myke.api.UserService&methods=userAddress
&pid=21208&side=consumer
×tamp=1583561061678,
urls: [dubbo://169.254.75.192:30880/com.myke.api.UserService?
anyhost=true
&application=user-service-provider&dubbo=2.6.2
&generic=false&interface=com.myke.api.UserService
&methods=userAddress&pid=17392
&side=provider×tamp=1583561004341,
empty://169.254.75.192/com.myke.api.UserService?
application=user-service-consumer
&category=configurators&default.check=false&dubbo=2.6.2
&interface=com.myke.api.UserService&methods=userAddress&pid=21208
&side=consumer×tamp=1583561061678,
empty://169.254.75.192/com.myke.api.UserService?
application=user-service-consumer&category=routers
&default.check=false&dubbo=2.6.2
&interface=com.myke.api.UserService&methods=userAddress
&pid=21208&side=consumer×tamp=1583561061678],
dubbo version: 2.6.2, current host: 169.254.75.192
消費者調用分析
-
爲接口常見代理
-
獲取集羣容錯對象,默認
FailoverClusterInvoker
//-- 創建代理 com.alibaba.dubbo.config.spring.ReferenceBean#getObject com.alibaba.dubbo.config.ReferenceConfig#init // dubbo 的頂層接口 interfaceClass = GenericService.class; // 創建代理 ref = createProxy(map); // create service proxy return (T) proxyFactory.getProxy(invoker); com.alibaba.dubbo.rpc.proxy.AbstractProxyFactory#getProxy(com.alibaba.dubbo.rpc.Invoker<T>, java.lang.Class<?>[]) public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) { return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker)); } com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler#invoke return invoker.invoke(new RpcInvocation(method, args)).recreate(); // invoker 獲取的是一個集羣容錯對象 // invoker 默認的第一個對象是就是 MockClusterInvoker com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker // invoker 的默認集羣容錯對象是 FailoverClusterInvoker com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke //獲取默認重試次數,即 len 爲 3,重試次數小於0時,則默認爲1次 // getUrl就是上面的url int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1; if (len <= 0) { len = 1; } // 獲取負載均衡器 Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked); // 調用返回值 Result result = invoker.invoke(invocation);
-
Failover Cluster:
FailoverClusterInvoker
失敗自動切換,當出現失敗,重試其它服務器 。
通常用於讀操作,但重試會帶來更長延遲。
可通過 retries=“2” 來設置重試次數(不含第一次),默認共計調用三次服務方
。
重試次數配置如下:<dubbo:service retries=“2” /> 或 <dubbo:reference retries=“2” /> 或 <dubbo:reference> <dubbo:method name="findFoo" retries="2" /> </dubbo:reference>
-
Failfast Cluster :
FailfastClusterInvoker
快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。 -
Failsafe Cluster:
FailsafeClusterInvoker
失敗安全,出現異常時,直接忽略。通常用於寫入審計日誌等操作。 -
Failback Cluster
失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於消息通知操作。 -
Forking Cluster
並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪
費更多服務資源。可通過 forks=“2” 來設置最大並行數。 -
Broadcast Cluster
廣播調用所有提供者,逐個調用,任意一臺報錯則報錯 。通常用於通知所有提供者更新緩存
或日誌等本地資源信息。
<dubbo:service cluster=“failsafe” />
負載均衡
- Random LoadBalance
隨機
,按權重設置隨機概率。
在一個截面上碰撞的概率高,但調用量越大分佈越均勻
,而且按概率使用權重後也比較均勻,有利於動態調整提供者權重。 - RoundRobin LoadBalance
輪循
,按公約後的權重設置輪循比率。
存在慢的提供者累積請求的問題
,比如:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,久而久之,所有請求都卡在調到第二臺上。 - ConsistentHash LoadBalance
一致性 Hash
,相同參數的請求總是發到同一提供者
。
當某一臺提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動。
缺省只對第一個參數 Hash,如果要修改,請配置
<dubbo:parameter key="hash.arguments"value="0,1" />
缺省用 160 份虛擬節點,如果要修改,請配置
<dubbo:parameter key="hash.nodes" value="320" />
hash.arguments : 當進行調用時候根據調用方法的哪幾個參數生成key,並根據key來通過一致性hash算法來選擇調用結點。
例如調用方法invoke(String s1,String s2); 若hash.arguments爲1(默認值),則僅取invoke的參數1(s1)來生成hashCode。
hash.nodes: 爲結點的副本數 - LeastActive LoadBalance
最少活躍調用數
,相同活躍數的隨機,活躍數指調用前後計數差。
使慢的提供者收到更少請求
,因爲越慢的提供者的調用前後計數差會越大。
服務分組
當一個接口有多種實現時,可以用 group 區分。
服務方
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
引用方
<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexSe
rvice" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndewxServi
ce" />
多版本
當一個接口實現,出現不兼容升級時,可以用版本號過渡,版本號不同的服務相互間不引用。
可以按照以下的步驟進行版本遷移:
- 在低壓力時間段,先升級一半提供者爲新版本
- 再將所有消費者升級爲新版本
- 然後將剩下的一半提供者升級爲新版本
老版本服務提供者配置:
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
新版本服務提供者配置:
<dubbo:service interface="com.foo.BarService" version="2.0.0" />
老版本服務消費者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
新版本服務消費者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
如果不需要區分版本,可以按照以下的方式配置 :
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
參數驗證依賴
api 接口pom依賴
<!--參數驗證 start-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
</dependency>
<!--參數驗證 end-->
參數校驗註解
public interface UserService {
String userAddress(@NotBlank(message = "userId 不能爲空") String userId);
}
啓用參數校驗
@Reference(validation = "true")
private UserService userService;
異常信息
javax.validation.ConstraintViolationException:
Failed to validate service:
com.myke.api.UserService,
method: userAddress,
cause: [ConstraintViolationImpl{interpolatedMessage='userId 不能爲空',
propertyPath=userAddressArgument0, rootBeanClass=class com.myke.api.UserService_UserAddressParameter_java.lang.String,
messageTemplate='userId 不能爲空'}]
結果緩存
lru 基於最近最少使用原則刪除多餘緩存,保持最熱的數據被緩存。
使用泛化調用(消費者端)
泛化接口調用方式主要用於客戶端沒有 API 接口及模型類元的情況,參數及返回值中的所有POJO 均用 Map 表示,通常用於框架集成,
比如:實現一個通用的服務測試框架,可通過
GenericService 調用所有服務實現。
在 Spring 配置申明 generic=“true” :
<dubbo:reference id="barService" interface="com.foo.BarService" generic="true" />
在 Java 代碼獲取 barService 並開始泛化調用:
GenericService barService = (GenericService) applicationContext.getBean("barService");
Object result = barService.$invoke("sayHello", new String[] { "java.lang.String" }, new
Object[] { "World" });
實現泛化調用(生產者端)
在 Java 代碼中實現 GenericService 接口:
實現 GenericService 接口後,消費者引用改api時,所有調用的方法都會進入到該方法中
package com.foo;
public class MyGenericService implements GenericService {
public Object $invoke(String methodName, String[] parameterTypes, Object[] args) t
hrows GenericException {
if ("sayHello".equals(methodName)) {
return "Welcome " + args[0];
}
}
}
通過 Spring 暴露泛化實現
在 Spring 配置申明服務的實現:
<bean id="genericService" class="com.foo.MyGenericService" />
<dubbo:service interface="com.foo.BarService" ref="genericService" />
回聲測試
回聲測試用於檢測服務是否可用,回聲測試按照正常請求流程執行,能夠測試整個調用是否通暢,可用於監控。
所有服務自動實現 EchoService 接口,只需將任意服務引用強制轉型爲 EchoService ,即可使用。
代碼:
// 遠程服務引用
MemberService memberService = ctx.getBean("memberService");
EchoService echoService = (EchoService) memberService;
// 強制轉型爲EchoService
// 回聲測試可用性
String status = echoService.$echo("OK");
assert(status.equals("OK"));
異步調用
基於 NIO 的非阻塞實現並行調用,客戶端不需要啓動多線程即可完成並行調用多個遠程服
務,相對多線程開銷較小。
在 consumer.xml 中配置:
<dubbo:reference id="fooService" interface="com.alibaba.foo.FooService">
<dubbo:method name="findFoo" async="true" />
</dubbo:reference>
<dubbo:reference id="barService" interface="com.alibaba.bar.BarService">
<dubbo:method name="findBar" async="true" />
</dubbo:reference>
參數回調
參數回調方式與調用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中聲明哪個
參數是 callback 類型即可。
Dubbo 將基於長連接生成反向代理,這樣就可以從服務器端調用客戶端邏輯
服務提供者配置示例
<bean id="callbackService" class="com.callback.impl.CallbackServiceImpl" />
<dubbo:service interface="com.callback.CallbackService" ref="callbackService"
connections="1" callbacks="1000">
<dubbo:method name="addListener">
<dubbo:argument index="1" callback="true" />
<!--也可以通過指定類型的方式-->
<!--<dubbo:argument type="com.demo.CallbackListener" callback="true" />-->
</dubbo:method>
</dubbo:service>
事件通知
在調用之前、調用之後、出現異常時,會觸發 oninvoke 、 onreturn 、 onthrow 三個事件,
可以配置當事件發生時,通知哪個類的哪個方法 。
服務消費者 Callback 接口
interface Notify {
public void onreturn(Person msg, Integer id);
public void onthrow(Throwable ex, Integer id);
}
服務消費者 Callback 配置
<bean id ="demoCallback" class = "com.alibaba.dubbo.callback.implicit.NofifyImpl" />
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.callback.implicit.IDemo
Service" version="1.0.0" group="cn" >
<dubbo:method name="get" async="true" onreturn = "demoCallback.onreturn" onthrow=
"demoCallback.onthrow" />
</dubbo:reference>
本地存根(相當於過濾器)
遠程服務後,客戶端通常只剩下接口,而實現全在服務器端,但提供方有些時候想在客戶端
也執行部分邏輯,比如:做 ThreadLocal 緩存,提前驗證參數,調用失敗後僞造容錯數據等
等,此時就需要在 API 中帶上 Stub,客戶端生成 Proxy 實例,會把 Proxy 通過構造函數傳給
Stub ,然後把 Stub 暴露給用戶,Stub 可以決定要不要去調 Proxy。
在 spring 配置文件中按以下方式配置:
<dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub" />
1.Stub 必須有可傳入 Proxy 的構造函數。
2.在 interface 旁邊放一個 Stub 實現,它實現 BarService 接口,並有一個傳入遠程BarService 實例的構造函數
本地僞裝
本地僞裝 通常用於服務降級
,比如某驗權服務,當服務提供方全部掛掉後,客戶端不拋出
異常,而是通過 Mock 數據返回授權失敗。
在 spring 配置文件中按以下方式配置:
<dubbo:reference id="userService" interface="com.myke.consumer.api.UserService" mock="com.myke.consumer.api.UserServiceMock"/>
1.Mock 是 Stub 的一個子集,便於服務提供方在客戶端執行容錯邏輯,因經常需要在出
現 RpcException (比如網絡失敗,超時等)時進行容錯,而在出現業務異常(比如登錄用戶 名密碼錯誤)時不需要容錯,如果用 Stub,可能就需要捕獲並依賴 RpcException 類,而 用 Mock 就可以不依賴 RpcException,因爲它的約定就是隻有出現 RpcException 時才 執行
。
2.在 interface 旁放一個 Mock 實現,它實現 BarService 接口,並有一個無參構造函數
延遲暴露
延遲到 Spring 初始化完成後,再暴露服務,規避spring的加載死鎖問題
<dubbo:service delay="-1" />
或
<dubbo:provider deplay=”-1” />
併發控制
限制 com.foo.BarService 的每個方法,服務器端併發執行(或佔用線程池線程數)不能超過
10 個:
<dubbo:service interface="com.foo.BarService" executes="10" />
限制 com.foo.BarService 的 sayHello 方法,服務器端併發執行(或佔用線程池線程數)不
能超過 10 個:
<dubbo:service interface="com.foo.BarService">
<dubbo:method name="sayHello" executes="10" />
</dubbo:service>
限制 com.foo.BarService 的每個方法,每客戶端併發執行(或佔用連接的請求數)不能超過
10 個:
<dubbo:service interface="com.foo.BarService" actives="10" />
<dubbo:reference interface="com.foo.BarService" actives="10" />
限制 com.foo.BarService 的 sayHello 方法,每客戶端併發執行(或佔用連接的請求數)不
能超過 10 個:
<dubbo:service interface="com.foo.BarService">
<dubbo:method name="sayHello" actives="10" />
</dubbo:service>
<dubbo:reference interface="com.foo.BarService">
<dubbo:method name="sayHello" actives="10" />
</dubbo:service>
如果 <dubbo:service> 和 <dubbo:reference> 都配了actives, <dubbo:reference> 優先
連接控制
服務端連接控制
限制服務器端接受的連接不能超過 10 個
<dubbo:provider protocol=“dubbo” accepts=“10” />
<dubbo:protocol name=“dubbo” accepts=“10” />
客戶端連接控制
限制客戶端服務使用連接不能超過 10 個
<dubbo:reference interface=“com.foo.BarService” connections=“10” />
<dubbo:service interface=“com.foo.BarService” connections=“10” />
延遲連接
延遲連接用於減少長連接數。當有調用發起時,再創建長連接。
<dubbo:protocol name=“dubbo” lazy=“true” />
注意:該配置只對使用長連接的 dubbo 協議生效。
粘滯連接
粘滯連接用於有狀態服務,儘可能讓客戶端總是向同一提供者發起調用,除非該提供者掛
了,再連另一臺。
粘滯連接將自動開啓延遲連接,以減少長連接數。
<dubbo:protocol name=“dubbo” sticky=“true” />
令牌驗證
通過令牌驗證在註冊中心控制權限,以決定要不要下發令牌給消費者,可以防止消費者繞過
註冊中心訪問提供者,另外通過註冊中心可靈活改變授權方式,而不需修改或升級提供者
可以全局設置開啓令牌驗證:
<!--隨機token令牌,使用UUID生成-->
<dubbo:provider interface="com.foo.BarService" token="true" />
<!--固定token令牌,相當於密碼-->
<dubbo:provider interface="com.foo.BarService" token="123456" />
<!--隨機token令牌,使用UUID生成-->
<dubbo:service interface="com.foo.BarService" token="true" />
<!--固定token令牌,相當於密碼-->
<dubbo:service interface="com.foo.BarService" token="123456" />
路由規則
路由規則 決定一次 dubbo 服務調用的目標服務器,分爲條件路由規則和腳本路由規則,並
且支持可擴展 。
向註冊中心寫入路由規則的操作通常由監控中心或治理中心的頁面完成
registry.register(URL.valueOf(“condition://0.0.0.0/com.foo.BarService?category=routers
&dynamic=false&rule=” + URL.encode(“host = 10.20.153.10 => host = 10.20.153.11”) + "));
condition:// 表示路由規則的類型,支持條件路由規則和腳本路由規則,可擴展,必
填。
0.0.0.0 表示對所有 IP 地址生效,如果只想對某個 IP 的生效,請填入具體 IP,必填。
com.foo.BarService 表示只對指定服務生效,必填。
category=routers 表示該數據爲動態配置類型,必填。
dynamic=false 表示該數據爲持久數據,當註冊方退出時,數據依然保存在註冊中心,
必填。
enabled=true 覆蓋規則是否生效,可不填,缺省生效。
force=false 當路由結果爲空時,是否強制執行,如果不強制執行,路由結果爲空的路
由規則將自動失效,可不填,缺省爲 flase 。
runtime=false 是否在每次調用時執行路由規則,否則只在提供者地址列表變更時預先
執行並緩存結果,調用時直接從緩存中獲取路由結果。如果用了參數路由,必須設爲
true ,需要注意設置會影響調用的性能,可不填,缺省爲 flase 。
priority=1 路由規則的優先級,用於排序,優先級越大越靠前執行,可不填,缺省爲
0 。
rule=URL.encode(“host = 10.20.153.10 => host = 10.20.153.11”) 表示路由規則的內容,
必填。
條件路由規則
基於條件表達式的路由規則,如: host = 10.20.153.10 => host = 10.20.153.11
規則:
=> 之前的爲消費者匹配條件,所有參數和消費者的 URL 進行對比,當消費者滿足匹配
條件時,對該消費者執行後面的過濾規則。
=> 之後爲提供者地址列表的過濾條件,所有參數和提供者的 URL 進行對比,消費者最
終只拿到過濾後的地址列表。
如果匹配條件爲空,表示對所有消費方應用,如: => host != 10.20.153.11
如果過濾條件爲空,表示禁止訪問,如: host = 10.20.153.10 =>
表達式:
參數支持:
服務調用信息,如:method, argument 等,暫不支持參數路由
URL 本身的字段,如:protocol, host, port 等
以及 URL 上的所有參數,如:application, organization 等
條件支持:
等號 = 表示"匹配",如: host = 10.20.153.10
不等號 != 表示"不匹配",如: host != 10.20.153.10
值支持:
以逗號 , 分隔多個值,如: host != 10.20.153.10,10.20.153.11
以星號 * 結尾,表示通配,如: host != 10.20.*
以美元符 $ 開頭,表示引用消費者參數,如: host = $host
白名單 :
host != 10.20.153.10,10.20.153.11 =>
黑名單:
host = 10.20.153.10,10.20.153.11 =>