使用Dubbo中需要注意的事項
一、前言
Dubbo作為高效能RPC框架,已經進入Apache卵化器專案,雖然官方給出了dubbo使用的使用者手冊,但是大多是一概而過,使用dubbo時候要儘量瞭解原始碼,不然會很容易入坑。
二 、服務消費端ReferenceConfig需要自行快取
ReferenceConfig例項是個很重的例項,每個ReferenceConfig例項裡面都維護了與服務註冊中心的一個長鏈,並且維護了與所有服務提供者的的長鏈。假設有一個服務註冊中心和N個服務提供者,那麼每個ReferenceConfig例項裡面維護了N+1個長鏈,如果頻繁的生成ReferenceConfig例項,可能會造成效能問題,甚至產生記憶體或者連線洩露的風險。特別是使用dubbo api程式設計時候容易忽略這個問題。
為了解決這個問題,之前都是自行快取,但是自從dubbo2.4.0版本後,dubbo 提供了簡單的工具類 ReferenceConfigCache 用於快取ReferenceConfig 例項。使用如下:
//建立服務消費例項
ReferenceConfig<XxxService> reference = new ReferenceConfig<XxxService>();
reference.setInterface(XxxService.class);
reference.setVersion("1.0.0");
......
//獲取dubbo提供的快取
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
// cache.get方法中會快取 reference物件,並且呼叫reference.get方法啟動ReferenceConfig,並返回經過代理後的服務介面的物件
XxxService xxxService = cache.get(reference);
// 使用xxxService物件
xxxService.sayHello();
需要注意的是 Cache內持有ReferenceConfig物件的引用,不要在外部再呼叫ReferenceConfig的destroy方法了,這會導致Cache內的ReferenceConfig失效!
如果要銷燬 Cache 中的 ReferenceConfig ,將銷燬 ReferenceConfig 並釋放對應的資源,具體使用下面方法來銷燬:
ReferenceConfigCache cache = ReferenceConfigCache.getCache(); cache.destroy(reference);
另外以服務 Group、介面、版本為快取的 Key,ReferenceConfig例項為對應的value。如果你需要使用自定義的key,可以在建立cache時候呼叫ReferenceConfigCache cache = ReferenceConfigCache.getCache(keyGenerator );方法傳遞自定義的keyGenerator。
三、 併發控制
3.1 服務消費方併發控制
在服務消費方法進行併發控制需要設定actives引數,如下:
<dubbo:reference id="userService" interface="com.test.UserServiceBo"
group="dubbo" version="1.0.0" timeout="3000" actives="10"/>
設定com.test.UserServiceBo介面中所有方法,每個方法最多同時併發請求10個請求。
也可以使用下面方法設定介面中的單個方法的併發請求個數,如下:
<dubbo:reference id="userService" interface="com.test.UserServiceBo"
group="dubbo" version="1.0.0" timeout="3000">
<dubbo:method name="sayHello" actives="10" />
</dubbo:reference>
如上設定sayHello方法的併發請求數量最大為10,如果客戶端請求該方法併發超過了10則客戶端會被阻塞,等客戶端併發請求數量少於10的時候,該請求才會被髮送到服務提供方伺服器。在dubbo中客戶端併發控制是使用ActiveLimitFilter過濾器來控制的,程式碼如下:
public class ActiveLimitFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
URL url = invoker.getUrl();
String methodName = invocation.getMethodName();
//獲取設定的acvites的值,預設為0
int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
//獲取當前方法目前併發請求數量
RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
if (max > 0) {//說明設定了actives變數
long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
long start = System.currentTimeMillis();
long remain = timeout;
int active = count.getActive();
//如果該方法併發請求數量大於設定值,則掛起當前執行緒。
if (active >= max) {
synchronized (count) {
while ((active = count.getActive()) >= max) {
try {
count.wait(remain);
} catch (InterruptedException e) {
}
//如果等待時間超時,則丟擲異常
long elapsed = System.currentTimeMillis() - start;
remain = timeout - elapsed;
if (remain <= 0) {
throw new RpcException("Waiting concurrent invoke timeout in client-side for service: "
+ invoker.getInterface().getName() + ", method: "
+ invocation.getMethodName() + ", elapsed: " + elapsed
+ ", timeout: " + timeout + ". concurrent invokes: " + active
+ ". max concurrent invoke limit: " + max);
}
}
}
}
}
//沒有限流時候,正常呼叫
try {
long begin = System.currentTimeMillis();
RpcStatus.beginCount(url, methodName);
try {
Result result = invoker.invoke(invocation);
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
return result;
} catch (RuntimeException t) {
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
throw t;
}
} finally {
if (max > 0) {
synchronized (count) {
count.notify();
}
}
}
}
}
可知客戶端併發控制,是如果當併發量達到指定值後,當前客戶端請求執行緒會被掛起,如果在等待超時期間併發請求量少了,那麼阻塞的執行緒會被啟用,然後傳送請求到服務提供方,如果等待超時了,則直接丟擲異常,這時候服務根本都沒有傳送到服務提供方伺服器。
四、 改進的廣播策略
前面我們講解叢集容錯時候談到有一個廣播策略,該策略主要用於對所有服務提供者進行廣播訊息,那麼有個問題需要思考,廣播是是說你在客戶端呼叫介面一次,內部就是輪詢呼叫所有服務提供者的機器的服務,那麼你呼叫一次該介面,返回值是什麼那?比如內部輪詢了10臺機器,每個機器應該都有一個返回值,那麼你呼叫的這一次返回值是10個返回值的組成?其實不是,返回的是輪詢呼叫的最後一個機器結果,那麼如果我們想把所有的機器返回的結果聚合起來如何做的? 使用dubbo中更多需要注意的事情 單擊我檢視文章 , 單擊我觀看視訊即可知曉。