Custom Filter
We first manually define a Filter to count the execution time of each interface on the server.
- Implement the Filter interface
- Create a new org.apache.dubbo.rpc.Filter file under the resources/META-INF/dubbo folder
- Write the path of Filter in the org.apache.dubbo.rpc.Filter file
@Slf4j
@Activate(group = PROVIDER)
public class CostFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
long start = System.currentTimeMillis();
Result result = invoker.invoke(invocation);
long cost = System.currentTimeMillis() - start;
log.info("request cost " + invocation.getMethodName() + " " + cost);
return result;
}
}
Content in the org.apache.dubbo.rpc.Filter file
cost=com.javashitang.producer.conf.CostFilter
Log output
INFO c.j.producer.conf.CostFilter - request cost interface com.javashitang.api.service.UserService hello 5
INFO c.j.producer.conf.CostFilter - request cost interface com.javashitang.api.service.UserService hello 0
How does Filter work?
During web development, we often deal with Servlet Filter (filter) and Spring MVC Interceptor (interceptor), and do some additional operations before and after a request. Filters and interceptors are implemented in the chain of responsibility model.
Dubbo Filter also enhances the request process, but unlike Servlet Filter, Dubbo Filter is implemented based on the decorator pattern . The developers of Dubbo really love to use decorator mode
How does Filter work on the service provider?
When the service is exported, the Invoker needs to be exported according to the specific protocol. Due to the role of Dubbo Aop, the export process will execute the ProtocolFilterWrapper#export method, and the original Invoker is constantly decorated
// ProtocolFilterWrapper
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 暴露远程服务,registry协议不会执行filter链
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
The original Invoke is constantly decorated, service providers and callers use this method to decorate the original Invoker
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
// 获取自动激活的扩展类
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public URL getUrl() {
return invoker.getUrl();
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result result = filter.invoke(next, invocation);
if (result instanceof AsyncRpcResult) {
AsyncRpcResult asyncResult = (AsyncRpcResult) result;
asyncResult.thenApplyWithContext(r -> filter.onResponse(r, invoker, invocation));
return asyncResult;
} else {
return filter.onResponse(result, invoker, invocation);
}
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
You can see the specific decorator from the process of calling the server. A decorator executes a Filter
like this
How does Filter work on the service caller?
The service caller also decorates the original Invoker in ProtocolFilterWrapper, and uses the decorating class to execute the Filter
In the actual call, it passed through multiple Filters before finally calling the DubboInvoker#doInvoke method.
Common filters in Dubbo
Common filters | Introduction | User |
---|---|---|
ActiveLimitFilter | Limit the maximum number of concurrent calls from the consumer to the server | Consumer side |
ExecuteLimitFilter | Limit the maximum number of concurrent calls on the server | Server |
AccessLogFilter | Print request access log | Server |
TokenFilter | The server provides tokens to the consumer to prevent the consumer from bypassing the registry to directly call the server | Server |
MonitorFilter | Monitor and count the call status of all interfaces, such as success, failure, time-consuming, and then send the data to the Dubbo-Monitor server | Consumer + server |
Analysis of several common filters, the routines are similar
ActiveLimitFilter
Limit the maximum number of concurrent calls from the consumer to the server
@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)
public class ActiveLimitFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
URL url = invoker.getUrl();
String methodName = invocation.getMethodName();
// 获取设置的actives的值
int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
if (!count.beginCount(url, methodName, max)) {
// 超过并发限制,超时时间默认为0
long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
long start = System.currentTimeMillis();
long remain = timeout;
synchronized (count) {
while (!count.beginCount(url, methodName, max)) {
try {
// 等待过程中会被notify(),如果等待了remain毫秒,则下面一定会抛出异常
count.wait(remain);
} catch (InterruptedException e) {
// ignore
}
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: " + count.getActive()
+ ". max concurrent invoke limit: " + max);
}
}
}
}
// 没有超过并发限制
boolean isSuccess = true;
long begin = System.currentTimeMillis();
try {
return invoker.invoke(invocation);
} catch (RuntimeException t) {
isSuccess = false;
throw t;
} finally {
count.endCount(url, methodName, System.currentTimeMillis() - begin, isSuccess);
if (max > 0) {
synchronized (count) {
count.notifyAll();
}
}
}
}
}