【Dubbo】服务提供方并发控制
一. 概述
版本:2.7.8
解决问题
- 对服务方法或服务接口中所有服务方法并发调用请求数量进行限制
调用时机
- 服务提供方通过 org.apache.dubbo.rpc.filter.ExecuteLimitFilter 进行限制
- 服务提供方的 AbstractProxyInvoker 的 invoke () 方法在真正执行服务调用前,请求会先经过 ExecuteLimitFilter 的 invoke() 方法
二. 源码分析
RpcStatus类(和消费端并发控制共用)
源码说明:
- 该类表示提供端服务接口(包括接口中所有服务方法)、消费端服务接口(包括接口中所有服务方法)的当前调用次数、总数、失败数、调用间隔等状态信息
- 代码中有详细注释,重点关注beginCount方法、endCount方法
- SERVICE_STATISTICS/METHOD_STATISTICS是静态变量,相当于缓存
package org.apache.dubbo.rpc;
public class RpcStatus {
/**
* key 为服务接口(url.toIdentityString()),value 为该服务接口中所有方法的 RpcStatus 状态
* 静态变量:所有服务接口共用(缓存)
*/
private static final ConcurrentMap<String, RpcStatus> SERVICE_STATISTICS = new ConcurrentHashMap<>();
/**
* key 为服务接口(url.toIdentityString()),value 为 ConcurrentMap<String,RpcStatus> 对象,其中key为具体的服务方法,value为服务方法的 RpcStatus 状态
* 静态变量:所有服务接口共用(缓存)
*/
private static final ConcurrentMap<String, ConcurrentMap<String, RpcStatus>> METHOD_STATISTICS = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Object> values = new ConcurrentHashMap<>();
// 服务方法正在执行中的数量
private final AtomicInteger active = new AtomicInteger();
// 服务方法调用的总数量
private final AtomicLong total = new AtomicLong();
// 服务方法调用失败的数量
private final AtomicInteger failed = new AtomicInteger();
private final AtomicLong totalElapsed = new AtomicLong();
private final AtomicLong failedElapsed = new AtomicLong();
private final AtomicLong maxElapsed = new AtomicLong();
private final AtomicLong failedMaxElapsed = new AtomicLong();
private final AtomicLong succeededMaxElapsed = new AtomicLong();
/**
* 根据 URL 返回服务接口的 RpcStatus 状态
*/
public static RpcStatus getStatus(URL url) {
String uri = url.toIdentityString();
return SERVICE_STATISTICS.computeIfAbsent(uri, key -> new RpcStatus());
}
/**
* 根据 URL 返回接口中服务方法的 RpcStatus 状态
*/
public static RpcStatus getStatus(URL url, String methodName) {
String uri = url.toIdentityString();
ConcurrentMap<String, RpcStatus> map = METHOD_STATISTICS.computeIfAbsent(uri, k -> new ConcurrentHashMap<>());
return map.computeIfAbsent(methodName, k -> new RpcStatus());
}
/**
* 服务方法执行前判断是否满足并发控制要求
*
* @param url
* @param methodName 服务方法
* @param max 并发控制的数量
* @return false:并发数已达到,挂起当前线程,等待唤醒
* @return true: 并发数未达到,可以执行
*/
public static boolean beginCount(URL url, String methodName, int max) {
max = (max <= 0) ? Integer.MAX_VALUE : max;
RpcStatus appStatus = getStatus(url);
RpcStatus methodStatus = getStatus(url, methodName);
if (methodStatus.active.get() == Integer.MAX_VALUE) {
return false;
}
for (int i; ; ) {
// 服务方法正在调用中的数量
i = methodStatus.active.get();
// 超过配置的并发数,返回false
if (i + 1 > max) {
return false;
}
// 并发数未达到,返回true
if (methodStatus.active.compareAndSet(i, i + 1)) {
break;
}
}
// 接口所有服务方法正在执行中的数量加1
appStatus.active.incrementAndGet();
return true;
}
/**
* 服务方法执行后减少相关参数值(包括并发控制次数)
*
* @param url
* @param methodName
* @param elapsed
* @param succeeded
*/
public static void endCount(URL url, String methodName, long elapsed, boolean succeeded) {
// 服务接口
endCount(getStatus(url), elapsed, succeeded);
// 服务方法
endCount(getStatus(url, methodName), elapsed, succeeded);
}
private static void endCount(RpcStatus status, long elapsed, boolean succeeded) {
status.active.decrementAndGet();
status.total.incrementAndGet();
status.totalElapsed.addAndGet(elapsed);
if (status.maxElapsed.get() < elapsed) {
status.maxElapsed.set(elapsed);
}
if (succeeded) {
if (status.succeededMaxElapsed.get() < elapsed) {
status.succeededMaxElapsed.set(elapsed);
}
} else {
status.failed.incrementAndGet();
status.failedElapsed.addAndGet(elapsed);
if (status.failedMaxElapsed.get() < elapsed) {
status.failedMaxElapsed.set(elapsed);
}
}
}
}
ExecuteLimitFilter 过滤器
源码说明:
- 是服务提供端并且URL有指定key(executes) Filter才生效(@Activate(group = CommonConstants.PROVIDER, value = EXECUTES_KEY))
- invoke:调用 RpcStatus.beginCount 判断是否满足并发控制条件
- RpcStatus.beginCount返回false:直接抛异常。
- onResponse/onError:方法执行完成或发生异常则调用RpcStatus.endCount减去相应数量
package org.apache.dubbo.rpc.filter;
@Activate(group = CommonConstants.PROVIDER, value = EXECUTES_KEY)
public class ExecuteLimitFilter implements Filter, Filter.Listener {
private static final String EXECUTE_LIMIT_FILTER_START_TIME = "execute_limit_filter_start_time";
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
URL url = invoker.getUrl();
String methodName = invocation.getMethodName();
// 并发调用次数
int max = url.getMethodParameter(methodName, EXECUTES_KEY, 0);
// 判断是不是超过并发限制
// RpcStatus 根据URL和调用方法名获取对应方法的RPC状态对象
// RpcStatus.beginCount返回false:直接抛异常。
if (!RpcStatus.beginCount(url, methodName, max)) {
throw new RpcException(RpcException.LIMIT_EXCEEDED_EXCEPTION,
"Failed to invoke method " + invocation.getMethodName() + " in provider " +
url + ", cause: The service using threads greater than <dubbo:service executes=\"" + max +
"\" /> limited.");
}
invocation.put(EXECUTE_LIMIT_FILTER_START_TIME, System.currentTimeMillis());
try {
return invoker.invoke(invocation);
} catch (Throwable t) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new RpcException("unexpected exception when ExecuteLimitFilter", t);
}
}
}
/**
* 服务方法正常返回
*
* @param appResponse
* @param invoker
* @param invocation
*/
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
RpcStatus.endCount(invoker.getUrl(), invocation.getMethodName(), getElapsed(invocation), true);
}
/**
* 服务方法异常
*
* @param t
* @param invoker
* @param invocation
*/
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
if (t instanceof RpcException) {
RpcException rpcException = (RpcException) t;
if (rpcException.isLimitExceed()) {
return;
}
}
RpcStatus.endCount(invoker.getUrl(), invocation.getMethodName(), getElapsed(invocation), false);
}
private long getElapsed(Invocation invocation) {
Object beginTime = invocation.get(EXECUTE_LIMIT_FILTER_START_TIME);
return beginTime != null ? System.currentTimeMillis() - (Long) beginTime : 0;
}
}
三. 使用
public static void main(String[] args) throws IOException {
ServiceConfig<GreetingService> serviceConfig = new ServiceConfig<>();
serviceConfig.setApplication(new ApplicationConfig("dubbo-provider");
serviceConfig.setRegistry(new RegistryConfig("ZKAddress"));
serviceConfig.setInterface(GreetingService.class);
serviceConfig.setRef(new GreetingServiceImpl());
serviceConfig.setVersion("1.0.0");
serviceConfig.setGroup("dubbo");
// 服务接口所有方法
serviceConfig.setExecutes(10);
// 指定服务方法
final List<MethodConfig> methodList = new ArrayList<MethodConfig>();
MethodConfig methodConfig = new MethodConfig();
methodConfig.setExecutes(10);
methodConfig.setName("sayHello");
methodList.add(methodConfig);
serviceConfig.setMethods(methodList);
serviceConfig.export();
System.out.println("server is started");
System.in.read();
}