dubbo系列五、dubbo过滤器

dubbo过滤器

1.前言

dubbo filter的作用和web filter的作用是一样的,在真正调用前做一些公共的处理。这也就是在重要的过程上设置拦截接口,提供扩展供业务实现。

dubbo过滤器是整个dubbo框架中非常重要的组成部分,dubbo中许多重要功能都是基于过滤器扩展而来。过滤器提供了provider和consumer调用过程的拦截,

即每次RPC调用的时候,对应的过滤器都会生效。虽然过滤器功能强大,但由于每次调用都会执行,因此在使用的时候需要注意它对性能的影响。

dubbo filter分为comsumer和provider端两种,我们开发中对dubbo经常扩展的也是filter,因此系统的记录下。

dubbo filter的接口是com.alibaba.dubbo.rpc.Filter,是个SPI(扩展点),仅提供了一个invoker方法,用于rpc调用前后的功能增强。其中dubbo提供的内置filter实现如下:

provider端:AccessLogFilter、ClassloaderFilter、ContextFilter、ExecuteLimitFilter、ExceptionFilter、EchoFilter、TimeoutFilter、GenericFilter、TokenFilter、TpsFilter、TraceFilter、MonitorFilter

consumer端:ConsumerContextFilter、GenericImplFilter、ActiveLimitFilter、DeprecatedFilter、FutureFilter、MonitorFilter

以上内置filter都使用了@Activate注解,默认被激活。provider端的filter只在provider端服务启动时加入到filter chain;consumer端的filter只在consumer端服务启动时加入到filter chain;其中MonitorFilter特殊,它属于consumer和provider端,那么同时会在服务暴露和引用时候被加入到filter chain。

对dubbo filter有了整体了解,下面问题来了,filter chain是如何生成的?

2.dubbo filter chain如何生成

2.1.provider端filter chain生成

provider服务在暴露时候,在com.alibaba.dubbo.registry.integration.RegistryProtocol.export(Invoker<T>) -> com.alibaba.dubbo.registry.integration.RegistryProtocol.doLocalExport(Invoker<T>) 内进行服务暴露,关键是

image-20210718171601843

自适应对象Protocol$Adaptive根据URL上的协议dubbo获取对应的DubboProtocol,但是由于Protocol有三个Wrapper实现,具体是ProtocolFilterWrapper、ProtocolListenerWrapper、QosProtocolWrapper,那么在自适应对象获取com.alibaba.dubbo.rpc.Protocol extension =(com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); //extName是dubbo,实际上获取的是Wrapper类(dubbo SPI的依赖注入功能,即类似AOP),Wrapper类封装了最终的DubboProtocol。即最终这个Protocol对象的结构是QosProtocolWrapper->ProtocolListenerWrapper->ProtocolFilterWrapper->DubboProtocol。wrapper类的作用就是增强,这个是装饰模式。在这里就是使用ProtocolFilterWrapper对DubboProtocol进行了增强,在DubboProtocl生成Invoker后,对Invoker功能增强生成filter chain。代码如下

@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
//invoker参数即服务端使用javaassit生成的动态代理对象
//buildInvokerChain生成filter chain,对服务进行功能增强

provider端生成的默认filter chain [EchoFilter->ClassLoaderFilter->GenericFilter->ContextFilter->TraceFilter->TimeoutFilter->MonitorFilter->ExceptionFilter]

2.2.consumer端filter chain生成

具体是在

com.alibaba.dubbo.registry.integration.RegistryProtocol.refer(Class<T>, URL)
com.alibaba.dubbo.registry.integration.RegistryProtocol.doRefer(Cluster, Registry, Class<T>, URL)
com.alibaba.dubbo.registry.integration.RegistryDirectory.subscribe(URL)
com.alibaba.dubbo.registry.integration.RegistryDirectory.notify(List<URL>)
com.alibaba.dubbo.registry.integration.RegistryDirectory.refreshInvoker(List<URL>)
com.alibaba.dubbo.registry.integration.RegistryDirectory.toInvokers(List<URL>)

自适应对象Protocol$Adaptive引用,具体如下图

image-20210718173816425

和provider端一样,获取的Protoco对象结构是QosProtocolWrapper->ProtocolListenerWrapper->ProtocolFilterWrapper->DubboProtocol,在DubboProtocl生成Invoker后,ProtocolFilterWrapper对Invoker功能增强生成filter chain。代码如下

@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
        return protocol.refer(type, url);
    }
    return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
//protocol.refer(type, url)即DubboProtocol.refer生成Invoker引用,即DubboInvoker
//buildInvokerChain对DubboInvoker进行功能增强,增加filter chain

对于conusmer端,默认生成的filter chain [ConsumerContextFilter->FutureFilter->MonitorFilter]

如果consumer端是泛化,默认生成的filter chain [ConsumerContextFilter->FutureFilter->MonitorFilter->GenericImplFilter]

2.3.buildInvokerChain

provider和consumer端生成filter chain都是使用的buildInvokerChain,因此分析下

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 Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }
            };
        }
    }
    return last;
}

代码核心在于List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);,dubbo SPI机制获取filter的实现,并且激活对应的provider或consumer端的filter集合(并且根据Filter上的order进行了排序),然后组成filter chain。这个明白了SPI机制和自动激活就很简单。

3.provider端filter记录

3.1.AccessLogFilter

AccessLogFilter:默认不启用,只有配置了dubbo.provider.accesslog="true/default/custom-access.log"才生效。具体作用是把dubbo日志输出到应用本身的日志组件(比如logback)或者指定的文件。实现原理:如果是输出到应用本身的日志组件,则直接info打印;如果是输出到指定目录文件,则把要写入到日志内容暂存到map,然后使用schedule线程池从map获取从而写入文件。

3.2.ExecuteLimitFilter

ExecuteLimitFilter:默认不启用,只有在配置了@Service(executes=10)或者@Method(executes=10)时候才生效,分表是针对接口级别和方法级别。具体作用是限制每个方法的并发执行数(即占用线程池线程数)不能超过10。在不设置或者设置了小于等于0的数值,是不会进行限制。实现原理是使用ConcurrentHashMap为每个url缓存了RpcStatus,RpcStatus又封装了信号量Semaphore,具体就是请求先获取信号量,信号量消耗完,拒绝执行。具体代码中,个人认为RpcStatus.beginCount(url, methodName); 和 RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, isSuccess);这两行代码应该是无用的(可能是历史遗留,因为历史代码使用原子性进行控制并发数),可以直接废弃掉。因为通过url作为key,method作为分组获取RpcStatus,也就是获取了信号量,没必要再使用原子了。

3.3.ClassLoaderFilter

ClassLoaderFilter:默认启用,此filter的作用就是切换当前线程的类加载器为Invoker的类加载器,用完还原回去。为什么这么做呢?java的类加载器和双亲委派模型我们通常都知道,类的正常加载也都是按照双亲委派模型进行加载的。但是有些时候需要破坏双亲委派模型,比如jdk的spi机制、当前线程的类加载器,就可以破坏双亲委派模型。那么为了避免当前框架的类加载器可能和Invoker的类加载器不是同一个(用户可能对框架进行了扩展,比如自定义了一个filter,在当前线程使用了自定义的类加载器),而当前框架线程中又需要获取Invoker的类加载器中的一些class,为了避免(class存在但实际)出现ClassNotFoundException,此时需要设置Thread.currentThread().setContextClassLoader(invoker.getInterface().getClassLoader()),即设置当前线程的类加载器为Invoker的类加载器。本质是为了避免当前线程和Invoker的类加载(可能)不同而做的兼容。

常见的使用例子是DubboProtocol#optimizeSerialization方法,会根据Invoker中配置的optimizer参数获取扩展的自定义序列化处理类,这些外部引入的序列化类在框架的类加载器中肯定没有,因此需要使用Invoker的类加载器获取对应的类。

3.4.ContextFilter

ContextFilter:默认启用,统一在过滤器中处理请求的上下文信息RpcContext,比如设置当前请求的上下文如Invoker信息、地址信息、端口信息等,清除异步属性,防止传到过滤器链的下一个环节等。

3.5.ExceptionFilter

ExceptionFilter:默认启用,dubbo框架的全局异常处理器,有个缺点,我们项目中很多情况会抛出一些自定义异常来替代返回错误码,但是ExceptionFilter会将我们的异常信息进行包装,因此通常情况下我们也会重写ExceptionFilter,从而禁用原生ExceptionFilter。当然也可以把项目组的异常定义为extends RpcException,这样项目异常也会直接抛出(这样也不规范,自己项目的异常怎么能是RpcContext)。

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    try {
        Result result = invoker.invoke(invocation);
        if (result.hasException() && GenericService.class != invoker.getInterface()) {//代码@1
            try {
                Throwable exception = result.getException();

                // directly throw if it's checked exception
                if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {//代码@2
                    return result;
                }
                // directly throw if the exception appears in the signature
                try {//代码@3-start
                    Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                    Class<?>[] exceptionClassses = method.getExceptionTypes();
                    for (Class<?> exceptionClass : exceptionClassses) {
                        if (exception.getClass().equals(exceptionClass)) {
                            return result;
                        }
                    }
                } catch (NoSuchMethodException e) {
                    return result;
                }//代码@3-end

                // for the exception not found in method's signature, print ERROR message in server's log.
                logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
                             + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                             + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);

                // directly throw if exception class and interface class are in the same jar file.
                String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());//代码@4-start
                String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                    return result;
                }//代码@4-end
                // directly throw if it's JDK exception
                String className = exception.getClass().getName();
                if (className.startsWith("java.") || className.startsWith("javax.")) {//代码@5
                    return result;
                }
                // directly throw if it's dubbo exception
                if (exception instanceof RpcException) {//代码@6
                    return result;
                }

                // otherwise, wrap with RuntimeException and throw back to the client
                return new RpcResult(new RuntimeException(StringUtils.toString(exception)));//代码@7
            } catch (Throwable e) {
                logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                            + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
                return result;//处理invoker异常又出现异常,直接返回
            }
        }
        return result;
    } catch (RuntimeException e) {//invoke调用异常,直接抛出异常
        logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
                     + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                     + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
        throw e;
    }
}

代码@1:如果有异常并且未实现GenericService接口,进入后续判断逻辑,否则直接返回结果。

代码@2:不是RuntimeException类型的异常,并且是受检异常(继承Exception),直接返回结果。

代码@3: 在方法签名上有声明这个异常,直接返回结果。就是接口方法上直接throws 异常,且调用返回的异常也是声明的异常,直接返回。

代码@4:如果异常类和接口类在同一个jar包中,直接返回结果。

代码@5:以java.或javax.开头的异常直接返回结果。

代码@6:dubbo的自身异常RpcException,直接返回结果。

代码@7:不满足上述条件,会做toString处理并被封装成RuntimeException抛出。这样就吞了业务定义的异常

如何解决

解决方法就针对上面的几个条件进行,有几种方案,我们一一看一下:

1、将该异常的包名以java.或者javax.开头

这个方案不现实,也不符合规范,所以不采用

2、业务异常继承Exception,变为checked异常

自定义的业务异常本身属于RuntimeException,所以也不采用

3、异常类和接口类在同一jar包里

较大的项目一般都会有一些common包,定义好异常类型,使用二方包的方式引用,所以也不适用

4、provider的api明确写明throws XxxException

作为生产服务端,不应显式抛出异常给客户的进行处理,所以也不适用

最终方案

重写ExceptionFilter,并且禁用原生的ExceptionFilter。

比如在自定义的DubboExceptionFilter内加上className.startsWith(“xxx.xxx”),表明是我们项目的异常,直接return result。其它内容和ExceptionFilter保持相同。同时我们应该在加上配置 全局禁用dubbo ExceptionFilter:dubbo.provider.filter=-exception,自定义的DubboExceptionFilter如下

@Activate(group = Constants.PROVIDER)
public class DubboExceptionFilter implements Filter {
	//省略
}

备注:禁用dubbo所有的默认filter,配置如下 dubbo.provider.filter=-default

3.6.TimeoutFilter

TimeoutFilter:默认启用,记录每个Invoker的调用耗时,如果超过了设置的timeout时间,则会打印一条warn日志,此外别无其他功能。监控可以收集这个日志用于警告。

3.7.TokenFilter

TokenFilter:默认不启用,如果provider不想让consumer绕过注册中心直连自己,则可以使用令牌验证。原理是provider服务生成token并暴露到zk,消费者必须通过注册中心才能获取有令牌provider的url。TokenFilter就是provider端用于过滤consumer的调用,禁用无token的consumer调用。

配置token如下

全局设置 dubbo.provider.token=true 随机token,uuid生成。dubbo.provider.token=123456 固定token

服务级别设置 dubbo.service.token=true 随机token,uuid生成。dubbo.service.token=123456 固定token

协议级别设置 dubbo.protocol.token=true 随机token,uuid生成。dubbo.protocol.token=123456 固定token

通常进行认证,级别都是全局设置。

3.8.TpsLimitFilter

TpsLimitFilter:默认不启用,用于provider端限流。在dubbo的SPI com.alibaba.dubbo.rpc.Filter文件内没有这个配置,如果需要使用,需要如下配置:

resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter内增加tpsLimiter=com.alibaba.dubbo.rpc.filter.TpsLimitFilter,接着在属性文件内配置

dubbo.provider.parameters.tps=1000 每次发放1000个令牌

dubbo.provider.parameters.tps.interval=2000 令牌刷新间隔时间是2s,默认是60s

这样实际就生效。

主要逻辑在com.alibaba.dubbo.rpc.filter.tps.StatItem.isAllowable()内,设计的简单又好用,工作中可以直接参考使用。代码如下

//com.alibaba.dubbo.rpc.filter.tps.StatItem.isAllowable()
public boolean isAllowable() {
    long now = System.currentTimeMillis();
    if (now > lastResetTime + interval) {//当前时间戳大于上次重置时间戳+间隔时间,说明到了下一周期,又可以获取1000个令牌
        token.set(rate);//因此重置令牌数。因为令牌数随着使用递减为0
        lastResetTime = now;
    }

    int value = token.get();//获取令牌值
    boolean flag = false;
    while (value > 0 && !flag) {//使用当前令牌值和flag控制循环进入
        flag = token.compareAndSet(value, value - 1);//令牌减一
        value = token.get();
    }

    return flag;//返回获取令牌的结果。如果在2s内已经获取了1000个令牌,则结果false,不允许请求
}
3.9.EchoFilter

EchoFilter:默认启用,用于回声测试。所谓回声就是返回请求的数据。代码具体很简单,判断请求方法是$echo且只有一个参数,则把请求的数据返回。具体对应consumer端生成的Invoker进行了动态代理增强,增加了EchoSerivce接口。具体是consumer端在com.alibaba.dubbo.config.ReferenceConfig.createProxy(Map<String, String>)生成了Invoker对象,然后接着return (T) proxyFactory.getProxy(invoker);,对生成的Invoker对象进行动态代理给Invoker增加EchoSerivce接口。具体代码在

image-20210720232357036

这样就可以在consumer端引用的服务,强制转换为EchoService类型了。具体作用是如果需要探测 Dubbo 服务能否通,但又不想用启动时检查的方式,那么调用方只需要把其中的一个服务引用强转为 EchoService 然后$echo调用即可。

3.10.GenericFilter

GenericFilter:默认启用,用于处理泛型调用,后续泛型时候写。

3.11.TraceFilter

TraceFilter:默认启用,真实情况是,只有在通过运维命令 trace $服务名 后才会真正触发这个filter的执行。

比如我们执行telnet 192.168.1.155 20880 连接上dubbo服务,执行trace ProductService,这样ProductService服务的调用就会被加入TraceFilter的统计(TraceFilter#addTracer操作),dubbo官方对trace的介绍:

image-20210720235808855

trace 大体的实现流程,telnet 命令会被dubbo handler 单读处理,然后将trace命令解析出来,交给TraceTelnetHandler ,然后它经过一顿操作,找到接口暴露的invoker,然后再向TraceFilter中添加这个tracer,其实就是将要监听接口名,方法名,监听次数,还有channel 给TraceFilter,接下来就是调用的时候收集 执行时间,将执行时间 找到对应的channel 然后发送给telnet客户端。

3.12.MonitorFilter

MonitorFilter:默认启用,用于监控收集,dubbo默认的Monitor太简陋,工作中需要进行统一收集监控信息展示,因此可以自定义Monitor,使用dubbo spi机制扩展即可。

4.consumer端filter记录

4.1.ConsumerContextFilter

默认启用。ConsumerContextFilter会和ContextFilter配合使用,在微服务环境中,有许多链式调用,如A->B->C->D,收到请求时,当前节点被看作一个provider,由ContextFilter社长上下文,当发起请求到下一个服务时候,当前服务变为一个consumer,由ConsumerContextFilter设置上下文。具体逻辑:invoker调用前,设置当前请求上下文,进行invoker调用,invoker调用完毕后,清除当前线程的上下文信息,防止再次调用被误用。

之前遇到一个问题:A应用是web服务(dubbo consumer),B应用是dubbo服务(dubbo provider),A调用B会把用户信息放入隐式参数,这样B应用接收到就可以正常处理。为了方便,用户的信息由A应用的web filter统一设置,用着没有问题。但是,在A应用一次请求内连续多次访问B应用时候,发现第二次请求B应用,就提示用户信息不存在,原因就是ConsumerContextFilter在第一次后清除了当前上下文的用户信息。因此解决办法是定义个dubbo consumer filter,每次dubbo请求就会保存用户信息到当前dubbo隐式参数,如此解决。

4.2.FutureFilter

这个是个钩子,用于用户扩展,在invoker调用前后执行consumer端定义的逻辑。具体使用方法如下:

定义个用户bean,如下

@Component(value="mynotify")
public class Notify {
    public void oninvoke(String msg){
        System.out.println("oninvoke:" + msg);
    }
    public void onreturn(String msg) {
        System.out.println("onreturn:" + msg);
    }
    public void onthrow(Throwable e) {
        System.out.println("onthrow:" + e);
    }
}

在引用的dubbo服务上加@Method注解

@Reference( methods= {@Method(name=“findProduct”,onreturn=“mynotify.onreturn”)})
private ProductService productService;

含义是在请求调用执行后,执行bean mynotify的onreturn方法。不过我在测试时候,发现dubbo2.6.8 FutureFilter对于执行这样的钩子有bug,具体bug是在MethodConfig的构造器内,没有对method进行按.进行截取赋值,报错异常如下

java.lang.IllegalStateException: service:org.pangu.api.ProductService has a onreturn callback config , but no such method found. url:dubbo://192.168.5.1:20880/org.pangu.api.ProductService?anyhost=true&application=pangu-client-consumer&bean.name=ServiceBean:org.pangu.api.ProductService&check=false&default.blueGreenTag=green&default.tps=1000&default.tps.interval=2000&dubbo=2.0.2&findProduct.return=true&generic=false&interface=org.pangu.api.ProductService&methods=findProduct,selectProduct&pid=17972&qos.enable=true&register.ip=192.168.5.1&remote.timestamp=1627181280321&retries=0&revision=1.0-SNAPSHOT&side=consumer&timeout=900000000&timestamp=1627211036576&weight=100
	at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.fireReturnCallback(FutureFilter.java:137) ~[dubbo-2.6.8.jar:2.6.8]
	at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.syncCallback(FutureFilter.java:67) ~[dubbo-2.6.8.jar:2.6.8]
	at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:58) ~[dubbo-2.6.8.jar:2.6.8]

错误原因:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZgHQwJX3-1649166296284)(https://cdn.jsdelivr.net/gh/zhangyj131/mdpicture/docs/20210725191138.png)]

对MethodConfig改了后(重写同名的MethodConfig,设置method),又发现启动报错,这功能个bug有点多,就不再改了。

Caused by: java.lang.IllegalStateException: java.lang.NoSuchMethodException: No such method onreturn in class class java.lang.String
	at com.alibaba.dubbo.config.ReferenceConfig.getMethodByName(ReferenceConfig.java:147) ~[dubbo-2.6.8.jar:2.6.8]
	at com.alibaba.dubbo.config.ReferenceConfig.checkAndConvertImplicitConfig(ReferenceConfig.java:127) ~[dubbo-2.6.8.jar:2.6.8]
	at com.alibaba.dubbo.config.ReferenceConfig.init(ReferenceConfig.java:321) ~[dubbo-2.6.8.jar:2.6.8]

通过GitHub查看dubbo2.7代码,发现此bug已修复。

4.3.GenericImplFilter

consumer端是泛化调用才启用。和provider端的GenericFilter配合使用。后续泛型时候再写。

4.4.ActiveLimitFilter

默认不启用。和provider端的ExecuteLimitFilter功能类似,ActiveLimitFilter是消费端的filter,限制客户端的并发数,即限制方法在每个客户端的并发执行数(或占用连接的请求数)不能超过配置的actives。

具体逻辑:如果达到限流阈值,和provider端的ExecuteLimitFilter不同,并不是直接抛出异常,而是先等待直到超时,因为请求是有timeout属性的。当并发数达到阈值时,会先加锁抢占当前接口方法的RpcStatus对象,然后通过wait方法进行等待。此时有两种结果:第一种是某个Invoker在调用结束后,并把计数器原子-1并触发此RpcStatus对象的notify,会有一个在wait状态的线程被唤醒并继续执行invoker调用;第二种是wait等待超时都未被唤醒,此时直接抛出异常。具体代码如下

//com.alibaba.dubbo.rpc.filter.ActiveLimitFilter.invoke(Invoker<?>, Invocation)
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    URL url = invoker.getUrl();
    String methodName = invocation.getMethodName();
    int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
    RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
    if (max > 0) {
        long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
        long start = System.currentTimeMillis();
        long remain = timeout;
        int active = count.getActive();
        if (active >= max) {//当前活跃数大于actives
            synchronized (count) {//锁的颗粒小,只加在此方法对应的RpcStatus上
                while ((active = count.getActive()) >= max) {//这里又获取一次是因为可能count.getActive()值已经小于max,因为在此过程可能有释放。使用while是因为被唤醒
                    try {
                        count.wait(remain);//在当前方法对应的RpcStatus上等待,直至唤醒或者超时
                    } catch (InterruptedException e) {
                    }
                    long elapsed = System.currentTimeMillis() - start;
                    remain = timeout - elapsed;
                    if (remain <= 0) {//在timeout时间内获取不到,抛出异常
                        throw new RpcException("Waiting concurrent invoke timeout in client-side for service:  "
                                               + invoker.getInterface().getName() + ", method: "
                                               + invocation.getMethodName() + ", elapsed: " + elapsed
                                               + ", timeout: " + timeout + ". concurrent invokes: " + active
                                               + ". max concurrent invoke limit: " + max);
                    }
                }
            }
        }
    }
    try {
        long begin = System.currentTimeMillis();
        RpcStatus.beginCount(url, methodName);//active +1
        try {
            Result result = invoker.invoke(invocation);
            RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);//active -1
            return result;
        } catch (RuntimeException t) {
            RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
            throw t;
        }
    } finally {
        if (max > 0) {
            synchronized (count) {
                count.notify();//唤醒等待在此RpcStatus对象上的线程
            }
        }
    }
}

5.扩展dubbo filter

dubbo filter可能是我们使用dubbo过程中最经常需要定义的,通常做一些公共的处理,自定义dubbo filter步骤如下:

1.实现filter接口,加上自动激活注解@Activate和group、order

2.在META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter内增加xxx=自定义filter

扩展也是使用dubbo提供的SPI机制,扩展很简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值