Dubbo 是如何控制并发数和限流的?
共 5139字,需浏览 11分钟
·
2021-02-24 14:18
点击关注公众号,Java干货及时送达
ExecuteLimitFilter
ExecuteLimitFilter ,在服务提供者,通过
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 + 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> METHOD_STATISTICS = new ConcurrentHashMap>();
public static RpcStatus getStatus(URL url, String methodName) {
String uri = url.toIdentityString();
ConcurrentMap map = METHOD_STATISTICS.get(uri);
if (map == null) {
METHOD_STATISTICS.putIfAbsent(uri, new ConcurrentHashMap());
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;
}
这个方法是获取信号量,如果这个实例里面的信号量是空的,那么就添加一个,如果不是空的就返回。另外,关注公众号Java技术栈,在后台回复:面试,可以获取我整理的 Dubbo 系列面试题和答案。
TPSLimiter
TpsLimitFilter 过滤器,用于服务提供者中,提供限流的功能。
配置方式:
dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoServiceImpl" protocol="injvm" >
"tps" value="100" />
源码分析
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 stats
= new ConcurrentHashMap();
@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 = token.get();
boolean flag = false;
while (value > 0 && !flag) {
flag = token.compareAndSet(value, value - 1);
value = token.get();
}
return flag;
}
关注公众号Java技术栈,在后台回复:面试,可以获取我整理的 Dubbo 系列面试题和答案。
作者:luozhiyun
出处:www.cnblogs.com/luozhiyun/p/10960593.html
![](https://filescdn.proginn.com/ad6f7c36bad6cd5f7cd67ae31587c782/6f2bd79ce754eb6ef4d79c542636ea09.webp)
![](https://filescdn.proginn.com/0210a5b61bac82eecd7734aa3348a0b7/a02524a932b2a397d6d04a892507f41f.webp)
![](https://filescdn.proginn.com/e57b861731d7f6fd1e69ba9bca4fd4d4/1a474aa2ef47d667955fb5d4df4461ff.webp)
![](https://filescdn.proginn.com/e0d74d2091338e7a29b42485955263db/b5ce9e10cd41de79b0993d18c5890086.webp)
![](https://filescdn.proginn.com/76b1c910cde35ca290a26e490f38d9a1/3d380c331086a1c00898366eb0d8c8ff.webp)
关注Java技术栈看更多干货
![](https://filescdn.proginn.com/a36c35d1a1c474a4f8f3b3bf0bc98fe8/67867d53f8783acd326130fec9a4e752.webp)