ExecuteLimitFilter
ExecuteLimitFilter ,在服務提供者,通過 <dubbo:service /> 的 "executes" 統一配置項開啟:
表示每服務的每方法最大可并行執行請求數,
ExecuteLimitFilter是通過信號量來實作的對服務端的并發數的控制,
ExecuteLimitFilter執行流程:
- 首先會去獲得服務提供者每服務每方法最大可并行執行請求數
- 如果每服務每方法最大可并行執行請求數大于零,那么就基于基于服務 URL + 方法維度獲取一個RpcStatus實體
- 通過RpcStatus實體獲取一個信號量,若果獲取的這個信號量呼叫tryAcquire回傳false,則拋出例外
- 如果沒有拋例外,那么久呼叫RpcStatus靜態方法beginCount,給這個 URL + 方法維度開始計數
- 呼叫服務
- 呼叫結束后計數呼叫RpcStatus靜態方法endCount,計數結束
- 釋放信號量
ExecuteLimitFilter
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
URL url = invoker.getUrl();
String methodName = invocation.getMethodName();
Semaphore executesLimit = null;
boolean acquireResult = false;
int max = url.getMethodParameter(methodName, Constants.EXECUTES_KEY, 0);
if (max > 0) {
RpcStatus count = RpcStatus.getStatus(url, invocation.getMethodName());
// if (count.getActive() >= max) {
/**
* http://manzhizhen.iteye.com/blog/2386408
* use semaphore for concurrency control (to limit thread number)
*/
executesLimit = count.getSemaphore(max);
if(executesLimit != null && !(acquireResult = executesLimit.tryAcquire())) {
throw new RpcException("Failed to invoke method " + invocation.getMethodName() + " in provider " + url + ", cause: The service using threads greater than <dubbo:service executes=\"" + max + "\" /> limited.");
}
}
long begin = System.currentTimeMillis();
boolean isSuccess = true;
RpcStatus.beginCount(url, methodName);
try {
Result result = invoker.invoke(invocation);
return result;
} catch (Throwable t) {
isSuccess = false;
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new RpcException("unexpected exception when ExecuteLimitFilter", t);
}
} finally {
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, isSuccess);
if(acquireResult) {
executesLimit.release();
}
}
}
我們接下來看看RpcStatus這個類
private static final ConcurrentMap<String, ConcurrentMap<String, RpcStatus>> METHOD_STATISTICS = new ConcurrentHashMap<String, ConcurrentMap<String, RpcStatus>>();
public static RpcStatus getStatus(URL url, String methodName) {
String uri = url.toIdentityString();
ConcurrentMap<String, RpcStatus> map = METHOD_STATISTICS.get(uri);
if (map == null) {
METHOD_STATISTICS.putIfAbsent(uri, new ConcurrentHashMap<String, RpcStatus>());
map = METHOD_STATISTICS.get(uri);
}
RpcStatus status = map.get(methodName);
if (status == null) {
map.putIfAbsent(methodName, new RpcStatus());
status = map.get(methodName);
}
return status;
}
這個方法很簡單,大概就是給RpcStatus這個類里面的靜態屬性METHOD_STATISTICS里面設值,外層的map是以url為key,里層的map是以方法名為key,
private volatile int executesPermits;
public Semaphore getSemaphore(int maxThreadNum) {
if(maxThreadNum <= 0) {
return null;
}
if (executesLimit == null || executesPermits != maxThreadNum) {
synchronized (this) {
if (executesLimit == null || executesPermits != maxThreadNum) {
executesLimit = new Semaphore(maxThreadNum);
executesPermits = maxThreadNum;
}
}
}
return executesLimit;
}
這個方法是獲取信號量,如果這個實體里面的信號量是空的,那么就添加一個,如果不是空的就回傳,
TPSLimiter
TpsLimitFilter 過濾器,用于服務提供者中,提供限流的功能,
配置方式:
- 通過 <dubbo:parameter key="tps" value="" /> 配置項,添加到 <dubbo:service /> 或 <dubbo:provider /> 或 <dubbo:protocol /> 中開啟,例如:
dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoServiceImpl" protocol="injvm" >
<dubbo:parameter key="tps" value="https://www.cnblogs.com/javastack/p/100" />
</dubbo:service>
- 通過 <dubbo:parameter key="tps.interval" value="" /> 配置項,設定 TPS 周期,
原始碼分析
TpsLimitFilter
private final TPSLimiter tpsLimiter = new DefaultTPSLimiter();
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (!tpsLimiter.isAllowable(invoker.getUrl(), invocation)) {
throw new RpcException(
"Failed to invoke service " +
invoker.getInterface().getName() +
"." +
invocation.getMethodName() +
" because exceed max service tps.");
}
return invoker.invoke(invocation);
}
invoke方法呼叫了DefaultTPSLimiter的isAllowable,我們進入到isAllowable方法看一下
DefaultTPSLimiter
private final ConcurrentMap<String, StatItem> stats
= new ConcurrentHashMap<String, StatItem>();
@Override
public boolean isAllowable(URL url, Invocation invocation) {
//獲取tps這個引數設定的大小
int rate = url.getParameter(Constants.TPS_LIMIT_RATE_KEY, -1);
//獲取tps.interval這個引數設定的大小,默認60秒
long interval = url.getParameter(Constants.TPS_LIMIT_INTERVAL_KEY,
Constants.DEFAULT_TPS_LIMIT_INTERVAL);
String serviceKey = url.getServiceKey();
if (rate > 0) {
StatItem statItem = stats.get(serviceKey);
if (statItem == null) {
stats.putIfAbsent(serviceKey,
new StatItem(serviceKey, rate, interval));
statItem = stats.get(serviceKey);
}
return statItem.isAllowable();
} else {
StatItem statItem = stats.get(serviceKey);
if (statItem != null) {
stats.remove(serviceKey);
}
}
return true;
}
若要限流,呼叫 StatItem#isAllowable(url, invocation) 方法,根據 TPS 限流規則判斷是否限制此次呼叫,
StatItem
private long lastResetTime;
private long interval;
private AtomicInteger token;
private int rate;
public boolean isAllowable() {
long now = System.currentTimeMillis();
// 若到達下一個周期,恢復可用種子數,設定最后重置時間,
if (now > lastResetTime + interval) {
token.set(rate);// 回復可用種子數
lastResetTime = now;// 最后重置時間
}?
// CAS ,直到或得到一個種子,或者沒有足夠種子
int value = https://www.cnblogs.com/javastack/p/token.get();
boolean flag = false;
while (value > 0 && !flag) {
flag = token.compareAndSet(value, value - 1);
value = token.get();
}
return flag;
}
關注公眾號Java技術堆疊,在后臺回復:面試,可以獲取我整理的 Dubbo 系列面試題和答案,
作者: luozhiyun
出處:https://www.cnblogs.com/luozhiyun/p/10960593.html
近期熱文推薦:
1.600+ 道 Java面試題及答案整理(2021最新版)
2.終于靠開源專案弄到 IntelliJ IDEA 激活碼了,真香!
3.阿里 Mock 工具正式開源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式發布,全新顛覆性版本!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/266588.html
標籤:Java
