Dubbo source code analysis 13: Dubbo filter

Insert picture description here

Custom Filter

We first manually define a Filter to count the execution time of each interface on the server.

  1. Implement the Filter interface
  2. Create a new org.apache.dubbo.rpc.Filter file under the resources/META-INF/dubbo folder
  3. 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;
    }
}

Insert picture description here
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.

Insert picture description here
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
Insert picture description here

// 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
Insert picture description here
like this
Insert picture description here

How does Filter work on the service caller?

Insert picture description here
The service caller also decorates the original Invoker in ProtocolFilterWrapper, and uses the decorating class to execute the Filter

Insert picture description here
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();
                }
            }
        }
    }
}

Reference blog

Guess you like

Origin blog.csdn.net/zzti_erlie/article/details/109405149