Dubbo3 source code article 9-depth analysis filter FilterChain and filter function

Welcome everyone to pay attention to  github.com/hsfxuebao  , I hope it will be helpful to you. If you think it is possible, please click Star.

1 Introduction

When we use the consumer to call the service provider, it will go through 一系列的Filter链, pay attention, 这个Filter链在服务提供者端有,在服务调用者端也是有的, which can be understood according to the following figure, which is roughly like this.

image.png

2. The timing of the formation of the Filter chain

2.1 Filter interface

First, let's get to know the Filter interface

@SPI
public interface Filter extends BaseFilter {
}

public interface BaseFilter {
    /**
     * Always call invoker.invoke() in the implementation to hand over the request to the next filter node.
     */
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;

    interface Listener {

        void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation);

        void onError(Throwable t, Invoker<?> invoker, Invocation invocation);
    }
}

You can see that the Filter interface is extended based on dubbo spi and includes an invoke abstract method. We can also learn in the comments that we can add our own business logic as a pre-filter before invoker.invoke(), or add our own logic as a post-filter later.

2.2 Service Providers

On the service provider side, when our service starts and exposes the service, com.alibaba.dubbo.config.ServiceConfig#doExportUrlsFor1Protocolthere is a line in the method Exporter<?> exporter = protocol.export(wrapperInvoker);. In fact, the protocol that is actually called is adapted according to the dubbo spi, and then the RegistryProtocol is actually called here, and the implementation class is called according to the dubbo spi. Setter injection and wrapper packaging, the outer layer of the RegistryProtocol object will be wrapped ProtocolFilterWrapper,ProtocolListenerWrapper , that is, when calling, it will first call the two wrapper classes, and finally call the RegistryProtocol, we are mainly concerned here ProtocolFilterWrapper的export方法.

@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    /// registry protocol  就到下一个wapper 包装对象中就可以了
    if (UrlUtils.isRegistry(invoker.getUrl())) {
        return protocol.export(invoker);
    }
    return protocol.export(builder.buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}

Here the protocol happens to be registry, and then it goes in the if statement.

Then go to the export method of RegistryProtocol

@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    ...
    // 服务暴露,即将服务的exporter写入到缓存map
    //export invoker doLocalExport表示本地启动服务不包括去注册中心注册
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
    ...
}

The doLocalExport method is called for service exposure:

image.png

这里就是从bounds这个缓存中获取exporter,如果没有就创建,然后加到缓存中, 我们着重看下画红框那行,其实这个Protocol根据自适应的规则,就是DubboProtocol了,它跟RegistryProtocol 一样,也是被wrapper包装了,然而不一样的是在ProtocolFilterWrapper 的export方法中走的是buildInvokerChain方法,这个方法就是生成Filter链的。

2.3 消费者

服务调用者其实跟服务提供者的生成链的时机差不多,当注册中心的providers,configurators,routers发生变化的时候,就会通知RegistryDirectory的notify方法,然后就需要将url转换成invoker,在转换invoker的时候,有这么一行代码(出自com.alibaba.dubbo.registry.integration.RegistryDirectory#toInvokers)

image.png 这个protocol也是根据场景自适应出来的,这里是DubboProtocol,然后根据dubbo spi 的wrapper机制,它会被包上 上面说的那三个类,我们看下ProtocolFilterWrapper的refer方法。

@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    if (UrlUtils.isRegistry(url)) {
        return protocol.refer(type, url);
    }
    return builder.buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
}

这里也是没有进if判断,而是走了这个buildInvokerChain方法,跟服务暴露一样,接下来我们就来看下这个buildInvokerChain 方法的实现。

3. ProtocolFilterWrapper

我们在上面两个小节已经将ProtocolFilterWrapper 的refer与export方法讲解了,接下来就是buildInvokerChain 方法,我们重点看下子

@Activate(order = 0)
public class DefaultFilterChainBuilder implements FilterChainBuilder {

    /**
     * build consumer/provider filter chain
     *
     * 在构建调用链时方法先获取Filter列表,然后创建与Fitler数量一样多Invoker
     * 结点,接着将这些结点串联在一起,构成一个链表,最后将这个链的首结点返回,随后的调用中,将从首结点开始,依次调用各个结点,
     * 完成调用后沿调用链返回。这里各个Invoker结点的串联是通过与其关联的invoke 方法来完成的。
     */
    @Override
    public <T> Invoker<T> buildInvokerChain(final Invoker<T> originalInvoker, String key, String group) {
        Invoker<T> last = originalInvoker;
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(originalInvoker.getUrl(), key, group);

        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                // 调用buildInvokerChain时会传入invoker参数。
                final Invoker<T> next = last;
                // 通过循环遍历获取到的Filter,同时创建Invoker结点,每个结点对应一个Filter。此时循环内部定义了next指针。
                last = new FilterChainNode<>(originalInvoker, next, filter);
            }
        }

        return last;
    }

首先看下参数:

  • invoker: 在服务调用者端能通过invoker发起远程调用; 在服务提供者端能通过 invoker 调用真实的service(也就是是那个接口实现类)方法
  • key : 在调用者端是reference.filter。 在提供者端是service.filter
  • group: 在调用端是 consumer 。 在提供者端是 provider

接着往下看,根据dubbo spi 自动激活规则,能够获取到某些条件下的实现类集合,这里就是获取filters。这里不理解的可以看下SPI机制(IOC和AOP)、自适应和自动激活机制、JavaAssist源码分析。 接着就是倒着遍历,这里不好理解,我画了张图

image.png

调用的时候就是从上面的那个invoker对象调用下来,然后到filter,然后又到invoker,又到filter,直到真实的那个invoker,其中,filter在list中下标越大的越靠里,也就是在调用的时候这个优先级就越低。 这是invoke方法的调用流程,你要是其他方法的话直接就是调用的真实invoker的。

3.1 获取filters

我们这边稍微讲一下获取filter列表,咱们这边以调用者端为例子

List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

getActivateExtension 方法传入了一个url,key就是reference.filter ,group是 consumer,我们看下getActivateExtension 方法,这是通过自动激活机制 来获取的,这里不理解的可以看下SPI机制(IOC和AOP)、自适应和自动激活机制、JavaAssist源码分析

3.2 服务提供者filters

image.png

这是dubbo v3 里面服务提供者的filters,调用顺序是这个样子的: echo->classloader->generic->context->exception->moniter->timeout->trace

3.3 服务消费者filters

image.png 这是dubbo v3 里面服务调用者的filters, 为ConsumerContext->Future->Monitor。

4. 过滤器Filter

4.1 回声测试EchoFilter

4.1.1 什么是回声测试

我这里引用官方的解释:

回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。

我们可以看出来这个回声测试其实就是检测服务是否可用的,看看调用是否畅通。

4.1.2 回声测试使用

我这里使用注解的方式使用一下这个回声测试。

1. 服务提供者编码

我这里有一个服务提供者,然后暴露接口IHelloProviderService,

public interface IHelloProviderService {
    String getName(Integer id);
}

实现类可以随便写写,别忘了加dubbo 的@Service 注解就行

@org.springframework.stereotype.Service
@Service
public class IHelloProviderServiceImpl  implements IHelloProviderService {
    @Override
    public String getName(Integer id) {
        return "test";
    }
}

2. 服务调用者编码

服务调用者这边使用@Reference注解来生成接口的代理。

@RestController
public class TestController {
    @Reference(check = false,interfaceClass = IHelloProviderService.class)
    private EchoService echoService;
    @RequestMapping("/tests")
    public String test(){
        Object aaa = echoService.$echo("aaa");
        System.out.println(aaa);
        return null;
    }
}

我这里使用的是EchoService类型,然后代理的接口是IHelloProviderService,可以看下这个EchoService,这个接口是dubbo提供的,就一个echo方 法 , 然后在 生 成 代 理 类 的 时 候 代 理 类 实 现 业 务 接 口 的 同 时 也 会 实 现 这 个 接 口 , 重写里面echo方法,然后在生成代理类的时候代理类实现业务接口的同时也会实现这个接口,重写里面echo方法,然后在生成代理类的时候代理类实现业务接口的同时也会实现这个接口,重写里面echo方法,所有我们在获取这个接口实现类的时候可以在 EchoService类型与你的业务接口类型 间相互转换,这里使用注解的方式可能不好理解,使用xml的方式会好理解些。 关于这个实现原理我们在后面解析的时候会说到。

public interface EchoService {

    /**
     * echo test.
     *
     * @param message message.
     * @return message.
     */
    Object $echo(Object message);

}

4.1.3 调用测试

使用postman或者浏览器请求一下,然后可以看到打印出来我们请求的参数,这里dubbo就是将你请求参数原封不动返回给你。

image.png

4.1.4 原理解析

服务调用者端

在服务调用者端,我们在生成某个接口的代理类的时候,dubbo会自动给你的代理类 implements 一个EchoService 接口,然后重写里面的$ehco()方法,多说无益,直接看下dubbo 生成的代理类(这里我们还是将之前讲解Proxy的那张图拿过来)。

image.png 我们可以看到生成的代理类,不光实现类业务接口,也实现了EchoService,所以我们在获取这个代理类的时候转成EchoService类型就可以跟普通方法一样调用了。

服务提供者端

服务提供者这端是使用的Filter进行拦截的,dubbo在服务提供者端提供了一个EchoFilter来处理回声调用,我们可以看下这个Filter的源码。

/**
 * Dubbo provided default Echo echo service, which is available for all dubbo provider service interface.
 * 处理$echo  回声测试
 */
@Activate(group = CommonConstants.PROVIDER, order = -110000)
public class EchoFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
        // 方法名是$echo 的时候,并且参数是一个,然后就将传过来的参数返回去
        if (inv.getMethodName().equals($ECHO) && inv.getArguments() != null && inv.getArguments().length == 1) {
            return AsyncRpcResult.newDefaultAsyncResult(inv.getArguments()[0], inv);
        }
        return invoker.invoke(inv);
    }

}

当调用过来的时候,会被EchoFilter拦截到走到invoke方法中,这个invocation里面其实是封装着调用信息,比如说调用方法,参数类型,参数值,一些附带信息等等。

在invoke方法中,首先 判断,如果 调用方法名字是“$echo”,然后参数不是null,只有一个参数,他就认为是个回声测试调用,直接封装Result 将你传过来的参数返回。

4.2 ContextFilter 过滤器

4.2.1 RpcContext

dubbo提供了一个调用上下文用来给服务提供者或者服务调用者传递一些信息,它只会在当前这个调用链上生效,比如说我服务调用者的某个方法中有两个rpc调用,然后分别调用同一个或者不同的服务提供者的某个方法,这个两个rpc调用就会有两个context,你可以往对应的context中塞东西,然后对应的服务提供者就能够根据context获取到,同理,服务提供者也可以给服务调用着带东西,对应的服务调用者也能够获得到。

我们先来看下这个RpcContext。

public class RpcContext {
 
    private static final RpcContext AGENT = new RpcContext();

    /**
     * use internal thread local to improve performance
     */
    //远端的context,也就是服务提供者的context
    private static final InternalThreadLocal<RpcContextAttachment> SERVER_LOCAL = new InternalThreadLocal<RpcContextAttachment>() {
        @Override
        protected RpcContextAttachment initialValue() {
            return new RpcContextAttachment();
        }
    };

    private static final InternalThreadLocal<RpcContextAttachment> CLIENT_ATTACHMENT = new InternalThreadLocal<RpcContextAttachment>() {
        @Override
        protected RpcContextAttachment initialValue() {
            return new RpcContextAttachment();
        }
    };

    private static final InternalThreadLocal<RpcContextAttachment> SERVER_ATTACHMENT = new InternalThreadLocal<RpcContextAttachment>() {
        @Override
        protected RpcContextAttachment initialValue() {
            return new RpcContextAttachment();
        }
    };

    private static final InternalThreadLocal<RpcServiceContext> SERVICE_CONTEXT = new InternalThreadLocal<RpcServiceContext>() {
        @Override
        protected RpcServiceContext initialValue() {
            return new RpcServiceContext();
        }
    };

    private boolean remove = true;

}

public class RpcServiceContext extends RpcContext {

    protected RpcServiceContext() {
    }

    private List<URL> urls;

    private URL url;

    private String methodName;  // 调用方法名

    private Class<?>[] parameterTypes;  // 参数类型

    private Object[] arguments; // 参数

    private InetSocketAddress localAddress; // 本地地址

    private InetSocketAddress remoteAddress;    // 远端地址

    private String remoteApplicationName;

    @Deprecated
    private List<Invoker<?>> invokers; // 这个一般服务调用者端用到,存储服务提供者invoker们
    @Deprecated
    private Invoker<?> invoker;
    @Deprecated
    private Invocation invocation; // 调用信息

    // now we don't use the 'values' map to hold these objects
    // we want these objects to be as generic as possible
    private Object request;
    private Object response;
    private AsyncContext asyncContext;
}

我们主要就是看下它的成员,我们可以看到有InternalThreadLocal的类成员,这样子rpc调用就不会乱套,因为一个线程绑定一个RpcContext对象,在看下面的一些成员变量,其实就是一些本次rpc调用的调用信息,其中attachments 就是我们带值的map,我们后面讲解源码的时候会看到,这个类自己看看就可以了,很简单。

4.2.2 使用RpcContext

我们这里就使用一下这个RpcContext来帮我们干点事情。这里我们在调用者端使用RpcContext给服务提供者端带点东西,同时服务提供者端回带点东西给服务调用者端。演示如下:

公共接口

public interface IHelloProviderService {
    String getName(Integer id);
}

服务调用者端

@RestController
public class TestController {
    @Reference(check = false)
    private IHelloProviderService iHelloProviderService;
    @RequestMapping("/tests")
    public String test(){
        // 往context 添加点东西,带给服务调用者端
        RpcContext.getContext().getAttachments().put("aaa","ok");
        String name = iHelloProviderService.getName(1);
        // 从服务调用者端context获取带过来的信息
        String remoteData = RpcContext.getServerContext().getAttachments().get("bbb");
        System.out.println("我从提供者端获得的数据:"+remoteData);
        return null;
    }
}

服务调用者端很简单,就是在调用前 获取context,塞了个kv进去,在调用后获取serverContext获取了一下服务提供者给带过来的信息。

服务提供者

我这里就是接口的实现类,我们从context中获取服务调用者带过来的信息,同时往serverContext中塞了kv带给服务调用者。

@org.springframework.stereotype.Service
@Service
public class IHelloProviderServiceImpl  implements IHelloProviderService {
    @Override
    public String getName(Integer id) {
        String remoteData = RpcContext.getContext().getAttachments().get("aaa");
        System.out.println("我从服务调用着端获得的数据是:"+remoteData);
        RpcContext.getServerContext().getAttachments().put("bbb","ccc");
        return "test";
    }
}

4.2.3 测试

我们这里测试一下,然后从服务提供者日志中可以看到

image.png 从服务调用者的日志可以看到:

image.png

4.2.4 这个能干啥

在我们普通的开发中,RpcContext基本用不到,但是在某些框架在做dubbo兼容适配的时候会用到,我这里举两个例子,分布式链路追踪是怎样将traceId,spanId,pId传到服务调用者的,其实就是用了这个context。再就是分布式事务是怎样将全局事务id传到子事务的,其实还是用这个传的,只不过他们在做的时候优雅一下,不会明着调用,可能会使用Filter来往里面塞,也可能是aop的形式往里面塞。

4.2.5 原理解析

在上面的小节中我们讲解了RpcContext,还用RpcContext在服务调用者与服务提供者做了信息传递。那么这个功能是怎样实现的呢,其实很简单。首先我们得了解一下Rpc调用传输的实体RpcInvocation与Result,在进行服务调用者调用往服务提供者端发送请求就是发送的Request对象,将Request对象序列化成二进制,然后传到服务提供者端,服务提供者端就会将二进制反序列化成Request对象,这个request对象中包的就是这个RpcInvocation (调用实体),然后服务提供者进行对应方法调用(对应方法 参数 其实都在RpcInvocation实体中封装着)将执行结果放到RpcResult实体中,然后包上一层Response,序列化成二进制发送给服务调用者端,服务调用者端反序列化成Response,再将Response这层皮拨开,取出RpcResult,这个RpcResult就封装着调用结果集。其实不管是RpcInvocation 还是RpcResult里面都有共同的成员变量,那就是attachments,这个就是存放附加参数的,我们上面塞到Context中的数据,其实都会被封装到这个成员中传输。我们可以看到,他其实就是个map

image.png

那么怎么从RpcContext中封装到这两个实体中的呢?其实这里是用了dubbo Filter来做的,在服务调用着端有个ConsumerContextFilter过滤器专门将RpcResult中的attachments 塞到本地的RpcServerContext中,最后会清了本次调用context中的attachments,我们来看下实现:

@Activate(group = CONSUMER, order = -10000)
public class ConsumerContextFilter implements ClusterFilter, ClusterFilter.Listener {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        RpcContext.getServiceContext()
                .setInvocation(invocation)
                .setLocalAddress(NetUtils.getLocalHost(), 0);

        RpcContext context = RpcContext.getClientAttachment();
        context.setAttachment(REMOTE_APPLICATION_KEY, invoker.getUrl().getApplication());
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }

        ExtensionLoader<PenetrateAttachmentSelector> selectorExtensionLoader = ExtensionLoader.getExtensionLoader(PenetrateAttachmentSelector.class);
        Set<String> supportedSelectors = selectorExtensionLoader.getSupportedExtensions();
        if (CollectionUtils.isNotEmpty(supportedSelectors)) {
            for (String supportedSelector : supportedSelectors) {
                Map<String, Object> selected = selectorExtensionLoader.getExtension(supportedSelector).select();
                if (CollectionUtils.isNotEmptyMap(selected)) {
                    ((RpcInvocation) invocation).addObjectAttachmentsIfAbsent(selected);
                }
            }
        } else {
            ((RpcInvocation) invocation).addObjectAttachmentsIfAbsent(RpcContext.getServerAttachment().getObjectAttachments());
        }

        Map<String, Object> contextAttachments = RpcContext.getClientAttachment().getObjectAttachments();
        if (CollectionUtils.isNotEmptyMap(contextAttachments)) {
            ...
            ((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
        }

        try {
            ...
            RpcContext.removeServerContext();
            return invoker.invoke(invocation);
        } finally {
            RpcContext.removeServiceContext();
            // 清除context里面的东西
            RpcContext.removeClientAttachment();
        }
    }

    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        // pass attachments to result
        // 将响应结果中的attachments 设置到serverContext中
        RpcContext.getServerContext().setObjectAttachments(appResponse.getObjectAttachments());
    }

    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

    }
}

服务提供者端有个ContextFilter 过滤器,它会在调用前,将调用者端传过来的attachments ,塞到context中同时在调用完成后,将serverContext中的attachments 塞到RpcResult 中,最后移除context与清空serverContext,我们来看下源码实现。

@Activate(group = PROVIDER, order = -10000)
public class ContextFilter implements Filter, Filter.Listener {

    ...
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 从invocation中获取附件参数
        Map<String, Object> attachments = invocation.getObjectAttachments();
        if (attachments != null) {
            Map<String, Object> newAttach = new HashMap<>(attachments.size());
            for (Map.Entry<String, Object> entry : attachments.entrySet()) {
                String key = entry.getKey();
                if (!UNLOADING_KEYS.contains(key)) {
                    newAttach.put(key, entry.getValue());
                }
            }
            attachments = newAttach;
        }

        RpcContext.getServiceContext().setInvoker(invoker)
                .setInvocation(invocation);

        RpcContext context = RpcContext.getServerAttachment();
//                .setAttachments(attachments)  // merged from dubbox
        context.setLocalAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort());

        String remoteApplication = (String) invocation.getAttachment(REMOTE_APPLICATION_KEY);
        if (StringUtils.isNotEmpty(remoteApplication)) {
            RpcContext.getServiceContext().setRemoteApplicationName(remoteApplication);
        } else {
            RpcContext.getServiceContext().setRemoteApplicationName((String) context.getAttachment(REMOTE_APPLICATION_KEY));
        }

        ...
        if (attachments != null) {
            // 如果存在attachments
            if (context.getObjectAttachments() != null) {
                // 将invoker里面的attachments设置到context中
                context.getObjectAttachments().putAll(attachments);
            } else {
                // 设置attachments
                context.setObjectAttachments(attachments);
            }
        }

        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }

        try {
            context.clearAfterEachInvoke(false);
            return invoker.invoke(invocation);
        } finally {
            context.clearAfterEachInvoke(true);
            RpcContext.removeServerAttachment();
            RpcContext.removeServiceContext();
            // IMPORTANT! For async scenario, we must remove context from current thread, so we always create a new RpcContext for the next invoke for the same thread.
            RpcContext.getClientAttachment().removeAttachment(TIME_COUNTDOWN_KEY);
            RpcContext.removeServerContext();
        }
    }

    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        // pass attachments to result
        // 将serverContext中的附件参数 设置到response中
        appResponse.addObjectAttachments(RpcContext.getServerContext().getObjectAttachments());
    }

    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

    }
}

其实这里还有个问题就是在服务调用者端,是在哪里将context中的附加参数添加到invocation里面的,这个其实是在调用的时候,会经过clusterInvoker,然后有个抽象父类里面实现的invoke方法,这个类就是AbstractClusterInvoker,就是在这个类里面将context中的附加参数添加到invocation里面的,我们稍微看下:

@Override
public Result invoke(final Invocation invocation) throws RpcException {
    checkWhetherDestroyed();

    // binding attachments into invocation.
    Map<String, Object> contextAttachments = RpcContext.getClientAttachment().getObjectAttachments();
    if (contextAttachments != null && contextAttachments.size() != 0) {
        ((RpcInvocation) invocation).addObjectAttachmentsIfAbsent(contextAttachments);
    }

    // 通过路由策略,将不符合路由规则的invoker过滤掉,获取所有提供者的集合
    List<Invoker<T>> invokers = list(invocation);
    // 获取负载均衡策略,并创建相应的负载均衡实例,默认是random
    LoadBalance loadbalance = initLoadBalance(invokers, invocation);
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    // 调用具体的集群容错策略中的doInvoke()
    return doInvoke(invocation, invokers, loadbalance);
}

4.3 TimeoutFilter

本篇讲解的TimeoutFilter 超级简单,它的group 组是在服务提供者端,我们可以看下它的源代码:

@Activate(group = CommonConstants.PROVIDER)
public class TimeoutFilter implements Filter, Filter.Listener {

    private static final Logger logger = LoggerFactory.getLogger(TimeoutFilter.class);

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        return invoker.invoke(invocation);
    }

    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        // 从attachment 中获取countdown 对象
        Object obj = RpcContext.getClientAttachment().getObjectAttachment(TIME_COUNTDOWN_KEY);
        if (obj != null) {
            TimeoutCountDown countDown = (TimeoutCountDown) obj;
            // 判断本次响应是否超时
            if (countDown.isExpired()) {
                ((AppResponse) appResponse).clear(); // clear response in case of timeout.
                if (logger.isWarnEnabled()) {
                    logger.warn("invoke timed out. method: " + invocation.getMethodName() + " arguments: " +
                            Arrays.toString(invocation.getArguments()) + " , url is " + invoker.getUrl() +
                            ", invoke elapsed " + countDown.elapsedMillis() + " ms.");
                }
            }
        }
    }

    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

    }
}

判断如果执行耗时时间 大于 服务提供者端的设置的方法超时时间 的话,就打印下warn级别的日志,最后就将执行结果返回了,我们可以看到它这个Filter就是打印方法超时日志,别的就没在再做什么了。

4.4 ExceptionFilter

ExceptionFilter 的group =provider ,也就是在服务提供者端才激活的Filter,它的内容也很简单,我们可以看下源代码:

@Activate(group = CommonConstants.PROVIDER)
public class ExceptionFilter implements Filter, Filter.Listener {
    private Logger logger = LoggerFactory.getLogger(ExceptionFilter.class);

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        return invoker.invoke(invocation);
    }

    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {

        // 有异常,而且不是泛化
        if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
            try {
                Throwable exception = appResponse.getException();

                // directly throw if it's checked exception
                if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                    return;
                }
                // directly throw if the exception appears in the signature
                try {
                    // 获取方法上的exception,如果本次异常是方法上的异常 则返回
                    Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                    Class<?>[] exceptionClasses = method.getExceptionTypes();
                    for (Class<?> exceptionClass : exceptionClasses) {
                        if (exception.getClass().equals(exceptionClass)) {
                            return;
                        }
                    }
                // 没有找到该方法也将结果返回
                } catch (NoSuchMethodException e) {
                    return;
                }

                // for the exception not found in method's signature, print ERROR message in server's log.
                // 没有在method 上面找到对应的exception信息,就打印error log
                logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().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.
                //如果异常类和接口类在同一个jar文件中,则直接抛出。
                String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                    return;
                }
                // directly throw if it's JDK exception  jdk异常的话返回
                String className = exception.getClass().getName(); // 获取class 全类名
                // java的异常
                if (className.startsWith("java.") || className.startsWith("javax.")) {
                    return;
                }
                // directly throw if it's dubbo exception
                //   dubbo 异常的话也是返回
                if (exception instanceof RpcException) {
                    return;
                }

                // otherwise, wrap with RuntimeException and throw back to the client
                // 将异常使用RuntimeException 包装起来然后返回给客户端
                appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
            } catch (Throwable e) {
                logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
            }
        }
    }

    @Override
    public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
        logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
    }

    // For test purpose
    public void setLogger(Logger logger) {
        this.logger = logger;
    }
}

这个Filter主要是处理异常的,当Result里有异常并且异常不是泛化,就会进入if语句块进行执行,如果是检查异常直接返回result,如果是方法上throws 的异常 也是直接将result返回去,这两个条件都不满足的话就会在服务提供者端打印error日志,如果异常类和接口类在同一个jar文件中,则直接返回result,如果是jdk包的异常也直接返回,dubbo的异常也是直接返回,最后都不满足的就用RuntimeException将exception包起来,返回给客户端处理,我们可以这个过滤器主要是处理异常的,并没有什么复杂逻辑。

4.5 TraceFilter

在讲解TraceFilter 之前我们需要演示一下dubbo telnet 服务治理命令trace。

4.5.1 dubbo telnet trace 演示

如果对dubbo telnet 服务治理命令不熟悉的同学可以去dubbo官网文档学习下:dubbo.apache.org/zh/docs/v3.…

我们这里演示 trace的使用,我们可以使用trace能干什么呢,主要是监听 某个接口的任意方法或者某个方法 n次,然后返回对应的执行时间。 借用下dubbo官网对于trace的解释:

image.png

我们这里演示下,我这里现在有个服务提供者跟服务调用者,然后我这telnet 一下 服务调用者,看下某个接口的某个方法的执行时间。 注意: telnet 的ip就是服务提供者的机器ip ,端口是dubbo 端口,就是你配置的那个端口,我这里是20881

<dubbo:protocol name="dubbo" port="20881"/>

我们看下使用:

这时候你再回下车, 接着 我就输入 trace 命令了,dubbo3不支持trace命令,以下借鉴于dubbo2.6的版本。

trace com.xuzhaocai.dubbo.provider.IHelloProviderService getName 3 

注意: com.xuzhaocai.dubbo.provider.IHelloProviderService 这个是 接口 ,getName 方法名 ,3 这个是监听次数 ,监听3次就不监听了

它的使用也就是这个样子的 : trace [service] [method] [times]
如果你不写method 直接就是监听 这个接口的任意一个方法
times 这个监听次数,你不写就是1次。
我们来看下我的监听截图

image.png

我们看到,监听三次后就不再监听了,每次打印会将你的接口名+方法名+参数+结果值+ 执行时间打印出来。其实我们主角TraceFilter 就与这个功能实现有关,下面我们看下TraceFilter。

4.5.2 TraceFilter解析

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

接下来我们就看下这个TraceFilter ,首先看下 添加tracer与移除tracer方法

// 缓存
private static final ConcurrentMap<String, Set<Channel>> TRACERS = new ConcurrentHashMap<>();

// 添加Tracer
public static void addTracer(Class<?> type, String method, Channel channel, int max) {
    channel.setAttribute(TRACE_MAX, max);
    // 统计count
    channel.setAttribute(TRACE_COUNT, new AtomicInteger());
    // 拼装key,如果method没有的话就使用type的全类名, 如果有method话就是 全类名.方法名
    String key = method != null && method.length() > 0 ? type.getName() + "." + method : type.getName();
    // 从缓存中获取,没有就创建一个
    Set<Channel> channels = TRACERS.computeIfAbsent(key, k -> new ConcurrentHashSet<>());
    // 添加到set集合中
    channels.add(channel);
}

上面这段代码就是添加tracer,先解释下参数Class<?> type 这个就是你监听接口class,method: 监听的方法,channel:你发起telnet那个通道,max: 这个就是监听的最大次数。

首先是往channel 塞了 trace.maxtrace.count 两个属性值,这个 trace.max 就是最大监听次数,trace.count 就是计数器,记录调用了几次了。接着就是拼装key,如果method没有的话就使用type的全类名, 如果有method话就是 全类名.方法名 ,先从tracers这个map里面查找有没有对应的channels,没有找到就创建set集合,最后添加到这个集合中。

// 移除tracer
public static void removeTracer(Class<?> type, String method, Channel channel) {
    // 先从channel 中移除这两个属性
    channel.removeAttribute(TRACE_MAX);
    channel.removeAttribute(TRACE_COUNT);
    // 拼装key
    String key = method != null && method.length() > 0 ? type.getName() + "." + method : type.getName();
    Set<Channel> channels = TRACERS.get(key);
    if (channels != null) {
        // 移除
        channels.remove(channel);
    }
}

这个是移除tracer的方法,先移除channel中的这两个属性,拼装key,根据key从tracers中获取channels,然后移除对应的channel。

接下来我们就要看下invoke方法了

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // 开始时间
    long start = System.currentTimeMillis();
    Result result = invoker.invoke(invocation);
    // 结束时间
    long end = System.currentTimeMillis();
    if (TRACERS.size() > 0) {
        // 拼装key,先使用全类名.方法名的 形式
        String key = invoker.getInterface().getName() + "." + invocation.getMethodName(); // 接口全路径.方法名
        // 获取对应tracers
        Set<Channel> channels = TRACERS.get(key);
        // 没有对应的tracer 就使用 接口全类名做 key
        if (channels == null || channels.isEmpty()) {
            // 接口名
            key = invoker.getInterface().getName();
            channels = TRACERS.get(key);
        }
        if (CollectionUtils.isNotEmpty(channels)) {
            //遍历这堆channel
            for (Channel channel : new ArrayList<>(channels)) {
                //不是关闭状态的话
                if (channel.isConnected()) {
                    try {
                        int max = 1;
                        // 从channel中获取trace.max属性
                        Integer m = (Integer) channel.getAttribute(TRACE_MAX);
                        //如果 m  不是null的话,max=m
                        if (m != null) {
                            max = m;
                        }
                        int count = 0;

                        AtomicInteger c = (AtomicInteger) channel.getAttribute(TRACE_COUNT);
                        if (c == null) {
                            c = new AtomicInteger();
                            channel.setAttribute(TRACE_COUNT, c);
                        }
                        // 调用次数+1
                        count = c.getAndIncrement();
                        // 当count小于max的时候
                        if (count < max) {
                            // 获取那个终端上的头 ,这个不用纠结
                            String prompt = channel.getUrl().getParameter(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT);
                            // 发送 耗时信息
                            channel.send("\r\n" + RpcContext.getServiceContext().getRemoteAddress() + " -> "
                                    + invoker.getInterface().getName()
                                    + "." + invocation.getMethodName()
                                    + "(" + JSON.toJSONString(invocation.getArguments()) + ")" + " -> " + JSON.toJSONString(result.getValue())
                                    + "\r\nelapsed: " + (end - start) + " ms."
                                    + "\r\n\r\n" + prompt);
                        }
                        // 当调用总次数超过 max的时候
                        if (count >= max - 1) {
                            // 就将channel移除
                            channels.remove(channel);
                        }
                    } catch (Throwable e) {
                        channels.remove(channel);
                        logger.warn(e.getMessage(), e);
                    }
                } else {
                    channels.remove(channel);
                }
            }
        }
    }
    // 返回result
    return result;
}

我们可以看到在执行前后都记录了时间戳,然后判断如果有tracer的话,就要拼装key,先拼装全类型.方法名形式的,进行get,如果结果是空的话,就使用接口名获取channels。如果channels不是空,就遍历,如果channel是连接状态的话,先判断trace.max与trace.count。然后对trace.count这个计数器自增1,表示调用次数+1,如果调用次数小于 最大调用次数(就是咱们设置的那个times)的话,就向channel中发送 “接口+方法名+参数+结果值+ 执行时间”给我们。如果count大于等于max-1的话 就移除channel。

最后就是将执行结果result返回了。

好了,以上就是TraceFilter的全部的,还是很简单的。

4.6 TokenFilter

4.6.1 令牌验证

引用dubbo官方文档的介绍:

通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者

我们可以了解到,这个token就是防止服务消费者绕过注册中心去访问服务提供者,同时能够使用注册中心进行权限控制。 dubbo官网关于token的流程图,我们拿过来分析下:

image.png

  • 服务调用者生成token ,这个前提是你要在服务提供者端配置token
  • 注册到注册中心后,注册中心能够管理token,同时也能够进行权限控制
  • 服务调用者从注册中心获取服务提供者信息,同时获取token,然后在调用的时候携带者token
  • 服务提供者在提供服务的时候需要检查token,匹配不上的抛出异常。

我来看下服务提供者需要怎样配置(本图也是摘录官网文档): image.png

官方文档:dubbo.apache.org/docs/v2.7/u… 注解方式:在@Service注解添加token属性

4.6.2 TokenFilter解析

我们这里主要解析令牌验证的验证阶段,可以看下TokenFilter源码:

@Activate(group = Constants.PROVIDER, value = Constants.TOKEN_KEY)
public class TokenFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation inv)
            throws RpcException {
        // 获取服务提供者端的token属性
        String token = invoker.getUrl().getParameter(Constants.TOKEN_KEY);
        if (ConfigUtils.isNotEmpty(token)) {// 如果token不是空,这时候就需要验证token
            Class<?> serviceType = invoker.getInterface();// 获取 service type 
            Map<String, String> attachments = inv.getAttachments();//获取inv的附加参数
            String remoteToken = attachments == null ? null : attachments.get(Constants.TOKEN_KEY);
            if (!token.equals(remoteToken)) {// 如果服务调用者携带的token 与服务提供者端的不一致,就抛出异常
                throw new RpcException("Invalid token! Forbid invoke remote service " + serviceType + " method " + inv.getMethodName() + "() from consumer " + RpcContext.getContext().getRemoteHost() + " to provider " + RpcContext.getContext().getLocalHost());
            }
        }
        return invoker.invoke(inv);
    }

}

我们可以看到TokenFilter 的激活条件有两个,一个是服务提供者,一个是token,少一个都不能激活。 在调用请求过来的时候,首先会从服务提供者端获取token属性值,如果token值不为空的话,说明设置了令牌,这时候就从invocation中的附加参数列表中查找 token(这个token是服务调用者带过来的),然后拿着提供者端的token与调用者端的token进行比较,如果匹配不上就抛出异常,能匹配上就继续执行,继续invoke调用。

4.7 并发控制ActiveLimitFilter

4.7.1 actives属性介绍

我们在调用者端配置dubbo:refrence的时候有一个dubbo调优的参数actives。

属性 对应URL参数 类型 是否必填 缺省值 作用 描述
actives actives int 可选 0表示不做限制 性能调优 每服务消费者每服务每方法最大并发调用数

我这里直接从官网拿过来了,我们可以看下描述这一栏,每服务消费者每服务每方法最大并发调用数 这个可能不好理解,我画了张图大家可以理解下:

image.png

这个所谓的每服务消费者每服务每方法最大并发调用数 其实是 某个服务调用者调用 同一个服务提供者(同一个服务提供者实例,也就是上图三个箭头指向的同一个ip+port下的实例)的同一个接口的同一个方法的(可以看到上图三个箭头都调用的com.xxx.UserSerivce.getUserName() 方法)并发数(上图的actives =3 表示在同一时刻最多3个调用,然后多出来的调用只能等待)

4.7.2 配置使用

我们看下这个actives属性的配置使用:

  • 注解的使用:这个直接在@Reference()注解上配置actives属性就可以了,如:
    @Reference(check = false,actives = 1)
    private IHelloProviderService iHelloProviderService;
    
  • xml配置的话在dubbo:refrence 标签添加 actives属性。如:
<dubbo:reference interface="com.xuzhaocai.dubbo.provider.IHelloProviderService" id="iHelloProviderService"  actives="1"></dubbo:reference> 

4.7.3 源码解析

接下来我们就要进入重头戏了,解析下这个actives控制并发数的功能是怎样实现的,这里其实是通过服务调用者端的filter来实现并发数控制的,这个filter就是ActiveLimitFilter,我们先来看下它class 定义:

@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)
public class ActiveLimitFilter implements Filter {

可以看出来这个ActiveLimitFilter 只能用于服务调用者端,而且需要配置actives属性才能激活。 接着再看下invoke方法:

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // 获取URL和调用的方法名称
    URL url = invoker.getUrl();
    String methodName = invocation.getMethodName();
    // 获取actives的值(默认0)和最大可用并发数
    int max = invoker.getUrl().getMethodParameter(methodName, ACTIVES_KEY, 0);
    // 根据URL和方法名获取对应的状态对象
    final RpcStatus rpcStatus = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
    // 判断是不是超过并发限制
    if (!RpcStatus.beginCount(url, methodName, max)) {
        // 获取timeout
        long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), TIMEOUT_KEY, 0);
        // 开始时间
        long start = System.currentTimeMillis();
        // 剩余时间
        long remain = timeout;
        // 超过并发限制则阻塞当前线程的timeout时间
        synchronized (rpcStatus) {
            // 计算
            while (!RpcStatus.beginCount(url, methodName, max)) {
                try {
                    rpcStatus.wait(remain);
                } catch (InterruptedException e) {
                    // ignore
                }
                // 计算消耗多长时间
                long elapsed = System.currentTimeMillis() - start;
                // 剩余时间  超时时间-消耗时间
                remain = timeout - elapsed;
                // 超时了还没被唤醒则抛出异常
                if (remain <= 0) {
                    throw new RpcException(RpcException.LIMIT_EXCEEDED_EXCEPTION,
                            "Waiting concurrent invoke timeout in client-side for service:  " +
                                    invoker.getInterface().getName() + ", method: " + invocation.getMethodName() +
                                    ", elapsed: " + elapsed + ", timeout: " + timeout + ". concurrent invokes: " +
                                    rpcStatus.getActive() + ". max concurrent invoke limit: " + max);
                }
            }
        }
    }

    invocation.put(ACTIVELIMIT_FILTER_START_TIME, System.currentTimeMillis());
    // 到这里说明激活并发数没达到限制,则继续Filter链的调用,正常发起远程调用
    return invoker.invoke(invocation);
}

// 正常响应的调用方法
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
    String methodName = invocation.getMethodName();
    URL url = invoker.getUrl();
    int max = invoker.getUrl().getMethodParameter(methodName, ACTIVES_KEY, 0);

    // 远程调用完成后,当前激活并发数减去1
    RpcStatus.endCount(url, methodName, getElapsed(invocation), true);
    // 通过notifyAll方法激活所有挂起线程
    notifyFinish(RpcStatus.getStatus(url, methodName), max);
}

// 出现错误的调用方法
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
    String methodName = invocation.getMethodName();
    URL url = invoker.getUrl();
    int max = invoker.getUrl().getMethodParameter(methodName, ACTIVES_KEY, 0);

    if (t instanceof RpcException) {
        RpcException rpcException = (RpcException) t;
        if (rpcException.isLimitExceed()) {
            return;
        }
    }
    // 远程调用完成后,当前激活并发数减去1
    RpcStatus.endCount(url, methodName, getElapsed(invocation), false);
    // 通过notifyAll方法激活所有挂起线程
    notifyFinish(RpcStatus.getStatus(url, methodName), max);
}

private long getElapsed(Invocation invocation) {
    Object beginTime = invocation.get(ACTIVELIMIT_FILTER_START_TIME);
    return beginTime != null ? System.currentTimeMillis() - (Long) beginTime : 0;
}

private void notifyFinish(final RpcStatus rpcStatus, int max) {
    if (max > 0) {
        synchronized (rpcStatus) {
            rpcStatus.notifyAll();
        }
    }
}

为了阅读方便我把invoke方法源码分了三部分(我用‘-----’隔开了): 第一部分主要是:获取调用超时时间,获取用户配置并发数active,获取对应invoker对应方法的一个RpcStatus对象 第二部分主要是:对并发限制 第三部分主要是:调用前记录并发数信息,进行调用,调用结束的一些信息的记录。这里主要是onResponse和onError方法。

我们先来看下第一部分,从代码可以看出,现获得了调用超时时间timeout,获取配置的active并发数,获取invoker对应调用方法的一个RpcStatus对象(这个对象里面其实就记录在这个服务调用者实例中当前这个 服务提供者实例中该接口中的该方法 一些调用信息,包括现在的一个并发数,总调用次数,总消耗时间等等 ) 到这里可以先了解下 RpcStatus的成员变量(只罗列出了本文用到的):

    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();// 成功最大消耗时间

可以看到都是与调用有关的指标,而且都是使用Atomic 原子类。 我们看下是怎样获取当前这个调用的RpcStatus对象的,也就是源码中的这一句:

 RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());

对应的RpcStatus的getStatus方法源码:

/**
 * @param url
 * @param methodName
 * @return status
 * 获取url对应缓存method的RpcStatus
 */
public static RpcStatus getStatus(URL url, String methodName) {
    // 生成IdentityString 例如:dubbo://192.168.3.33:18109/com.xuzhaocai.dubbo.provider.IHelloProviderService
    String uri = url.toIdentityString();

    // 使用IdentityString  获取
    // key是方法名,value是RpcStatus
    ConcurrentMap<String, RpcStatus> map = METHOD_STATISTICS.computeIfAbsent(uri, k -> new ConcurrentHashMap<>());
    // 获取methodName 对应的RpcStatus
    return map.computeIfAbsent(methodName, k -> new RpcStatus());
}

我们可以看到这个getStatus 方法中首先根据这个invoker的url生成一个Identity (这个invoker对应的时候某个服务提供者的某个接口),这里有个生成的例子 “dubbo://192.168.3.33:18109/com.xuzhaocai.dubbo.provider.IHelloProviderService”,根据这个Identity 从METHOD_STATISTICS 缓存中获取 对应的value ,也是个map(这个map ,就是对应的 key 是某个方法 ,value就是RpcStatus对象),如果还没有这个的缓存,就创建一个塞进去,然后在从这个map 中获取对应调用方法的value(这个value就是RpcStatus) ,如果没有的话就创建RpcStatus 对象,塞到map中缓存起来,最后将对应RpcStatus 对象返回。

我们再来看下第二部分,该部分上面说过主要是做并发数的控制

public static boolean beginCount(URL url, String methodName, int max) {
    max = (max <= 0) ? Integer.MAX_VALUE : max;
    RpcStatus appStatus = getStatus(url);
    // 获取方法对应的RpcStatus
    RpcStatus methodStatus = getStatus(url, methodName);
    if (methodStatus.active.get() == Integer.MAX_VALUE) {
        return false;
    }

    // 原子性递增方法对应激活并发数,若超过最大限制则返回false,否则返回true
    for (int i; ; ) {
        i = methodStatus.active.get();

        if (i == Integer.MAX_VALUE || i + 1 > max) {
            return false;
        }

        if (methodStatus.active.compareAndSet(i, i + 1)) {
            break;
        }
    }

    appStatus.active.incrementAndGet();

    return true;
}

这个beginCount方法核心逻辑就是判断 请求是否超过我们设置的最大的actives参数。超过了返回false,没有超过返回true。

如果超过了并发控制数,我们一直在这里循坏等待,知道不超过时才会调用服务提供者的方法。

第三部分,主要是正常响应和异常的处理逻辑。这里会将当前的激活数减1,并唤醒所有挂起线程。

4.8 MonitorFilter

4.8.1 Monitor与MonitorFilter的关系

首先介绍下Monitor,Monitor字面是监控的意思,在dubbo中可以通过Monitor实时监控(不能算是实时,你要是将上报周期缩小可以接近实时)到服务的调用情况(粒度为方法级别),包括请求成功数,请求失败数,吞吐量,当前并发数,响应时长等等。

而Monitor这些调用统计指标是MonitorFilter 来采集的,MonitorFilter就相当于咱们的source。当我们配置了monitor,这个MonitorFilter就会在服务调用端或者服务提供端进行数据采集,然后上报给对应的monitor对象。 xml配置示例:

 <dubbo:monitor  address="dubbo://127.0.0.1:18109"></dubbo:monitor>

4.8.2 MonitorFilter源码解析

我们先来看下MonitorFilter的class定义:

@Activate(group = {PROVIDER})
public class MonitorFilter implements Filter, Filter.Listener {

我们看到MonitorFilter 适应服务提供者端,接下来看下它的成员变量:

// 缓存着 key=接口名.方法名       value= 计数器
private final ConcurrentMap<String, AtomicInteger> concurrents = new ConcurrentHashMap<String, AtomicInteger>();
private MonitorFactory monitorFactory;

concurrents 缓存着 方法与某时刻调用次数(这里可以理解为某时刻的并发数)的对应关系,monitorFactory 是monitor的工厂,通过它可以获取具体的monitor,monitorFactory 是dubbo spi自动注入的。 接下来看下invoke方法:

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // 判断是否有monitor参数
    if (invoker.getUrl().hasAttribute(MONITOR_KEY)) {
        invocation.put(MONITOR_FILTER_START_TIME, System.currentTimeMillis());
        invocation.put(MONITOR_REMOTE_HOST_STORE, RpcContext.getServiceContext().getRemoteHost());
        // 计数器+1
        getConcurrent(invoker, invocation).incrementAndGet(); // count up
    }
    // 进行调用
    return invoker.invoke(invocation); // proceed invocation chain
}

// concurrent counter
private AtomicInteger getConcurrent(Invoker<?> invoker, Invocation invocation) {
    String key = invoker.getInterface().getName() + "." + invocation.getMethodName();
    return concurrents.computeIfAbsent(key, k -> new AtomicInteger());
}

@Override
public void onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
    if (invoker.getUrl().hasAttribute(MONITOR_KEY)) {
        // 进行收集
        collect(invoker, invocation, result, (String) invocation.get(MONITOR_REMOTE_HOST_STORE), (long) invocation.get(MONITOR_FILTER_START_TIME), false);
        getConcurrent(invoker, invocation).decrementAndGet(); // count down
    }
}

@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
    if (invoker.getUrl().hasAttribute(MONITOR_KEY)) {
        // 进行收集
        collect(invoker, invocation, null, (String) invocation.get(MONITOR_REMOTE_HOST_STORE), (long) invocation.get(MONITOR_FILTER_START_TIME), true);
        getConcurrent(invoker, invocation).decrementAndGet(); // count down
    }
}

这个invoke方法还是很简单,首先判断下是否有monitor参数,如果没有就正常调用,否则获取remoteHost与开始时间start,接着调用getConcurrent(invoker, invocation)方法获取对应的计数器然后自增+1 操作,看下这个getConcurrent 方法:

private AtomicInteger getConcurrent(Invoker<?> invoker, Invocation invocation) {
    String key = invoker.getInterface().getName() + "." + invocation.getMethodName();
    return concurrents.computeIfAbsent(key, k -> new AtomicInteger());
}

可以看到就是根据调用信息的接口+方法为key从concurrents缓存中查找对应的计数器返回,如果没有就创建塞到缓存中。

自增1表示当前并发数+1,接着就是进行真正调用了,之后调用collect 收集方法,该方法最后一个参数 表示是否异常,正常调用的话就是false,出现异常的话就是true。最后调用getConcurrent方法获取计数器自减1,因为这时当前这次调用就结束了,并发数也就是减1。 接下来我们看下collect这个收集方法。

private void collect(Invoker<?> invoker, Invocation invocation, Result result, String remoteHost, long start, boolean error) {
    try {
        Object monitorUrl;
        monitorUrl = invoker.getUrl().getAttribute(MONITOR_KEY);
        if(monitorUrl instanceof URL) {
            // 根据monitorUrl 从监控工厂中获取对应的监控对象
            Monitor monitor = monitorFactory.getMonitor((URL) monitorUrl);
            if (monitor == null) {
                return;
            }
            URL statisticsURL = createStatisticsUrl(invoker, invocation, result, remoteHost, start, error);
            // monitor收集
            monitor.collect(statisticsURL.toSerializableURL());
        }
    } catch (Throwable t) {
        logger.warn("Failed to monitor count service " + invoker.getUrl() + ", cause: " + t.getMessage(), t);
    }
}

private URL createStatisticsUrl(Invoker<?> invoker, Invocation invocation, Result result, String remoteHost, long start, boolean error) {
    // ---- service statistics ----
    // 调用耗时
    long elapsed = System.currentTimeMillis() - start; // invocation cost
    // 获取当前的并发数
    int concurrent = getConcurrent(invoker, invocation).get(); // current concurrent count
    String application = invoker.getUrl().getApplication();
    // 服务名
    String service = invoker.getInterface().getName(); // service name
    // 方法名
    String method = RpcUtils.getMethodName(invocation); // method name
    // 分组
    String group = invoker.getUrl().getGroup();
    String version = invoker.getUrl().getVersion();

    int localPort;
    String remoteKey, remoteValue;
    // 服务调用者端
    if (CONSUMER_SIDE.equals(invoker.getUrl().getSide())) {
        // ---- for service consumer ----
        localPort = 0;
        remoteKey = MonitorService.PROVIDER;
        // 远端地址,在这里就是服务提供者方的地址
        remoteValue = invoker.getUrl().getAddress();
    } else {
        // 服务提供者端
        // ---- for service provider ----
        // 获取服务提供者端的port
        localPort = invoker.getUrl().getPort();
        remoteKey = MonitorService.CONSUMER;
        // 远端地址,这里是服务提供者方的地址
        remoteValue = remoteHost;
    }
    String input = "", output = "";
    // 这两个会在Serialize层添加上
    // 获取input
    if (invocation.getAttachment(INPUT_KEY) != null) {
        input = invocation.getAttachment(INPUT_KEY);
    }
    // 获取output
    if (result != null && result.getAttachment(OUTPUT_KEY) != null) {
        output = result.getAttachment(OUTPUT_KEY);
    }

    return new ServiceConfigURL(COUNT_PROTOCOL, NetUtils.getLocalHost(), localPort, service + PATH_SEPARATOR + method, MonitorService.APPLICATION, application, MonitorService.INTERFACE, service, MonitorService.METHOD, method, remoteKey, remoteValue, error ? MonitorService.FAILURE : MonitorService.SUCCESS, "1", MonitorService.ELAPSED, String.valueOf(elapsed), MonitorService.CONCURRENT, String.valueOf(concurrent), INPUT_KEY, input, OUTPUT_KEY, output, GROUP_KEY, group, VERSION_KEY, version);
}

先解释下这几个参数: invoker是服务提供者
invocation调用信息,里面有接口,方法,一些附加参数等,
result调用返回结果,
remoteHost远端host ,
start 调用的开始时间戳,
error调用是否出现异常,false表示正常调用,true表示要调用发生异常。

这个collect方法看起来比较长,实则内容比较简单,我把它分为了两部分,上面主要是一些调用信息与统计指标的收集工作,下面就是拼装这些调用信息url,然后调用对应monitor的collect 方法进行收集。

先看下上面收集调用信息与指标统计工作:通过start 算出调用耗时elapsed, 通过getConcurrent方法获取当前方法一个并发数concurrent,下面获取接口名,方法名我就不解释了,后面通过url获取monitorUrl,接着使用监控工厂获取monitorUrl对应的monitor对象,在往后就是根据端来获取远端key与远端地址,有个获取input与output参数值需要说下,这两个属性值其实是在序列化层(在DubboCountCodec中)设置进去的。

下面这部分其实就是一行monitor.collect(url) ,使用monitor对象进行收集,只不过这个url是在这现拼装成的。可以看出来设置比较常规的参数外还设置了成功或失败,调用耗时,input,output,并发数这些调用统计指标。

4.9 泛化调用GenericImplFilter和GenericFilter

4.9.1 泛化调用

什么是泛化调用?我个人是这样理解的,服务调用者不需要引用服务接口就能够调用服务提供者,我们用过dubbo的都知道,在开发的时候服务双方需要引用相同的接口,这种其实依赖性还是比较强,但是有某些场景使用共同接口的开发方式并不方便,dubbo提供了泛化调用的功能,我们服务调用端不需要定义共同的情况下就可以调用服务提供者。

4.9.2 泛化调用实例

我们这里简单演示下泛化调用的使用,这里使用的spring的方式,如果想体验下api的方式可以访问dubbo官网,官网提供了spring与api的实例。

服务提供者

服务提供者 定义接口,然后书写实现类 实现接口,并把进行服务暴露。服务提供者端就按照正常的开发流程开发就行。 接口:

public interface IUserInfoService {
   public String  getUserName(Integer id);
}

实现类:

@org.springframework.stereotype.Service
@Service
public class IUserInfoServiceImpl implements IUserInfoService {
    @Override
    public String getUserName(Integer id) {
        return "0";
    }
}

服务调用者

之前开发服务调用者,需要引用服务提供者共同的接口,咱们这里泛化调用就不需要了,dubbo给我们提供了一个泛化接口GenericService,我们在使用的时候直接注入就可以了。 咱们这里先配置一下:

 <dubbo:reference interface="com.hsf.dubbo.provider.IUserInfoService" id="iUserInfoService" generic="true" check="false" retries="0">
    </dubbo:reference>

这里我们虽然没有IUserInfoService 接口,但是interface需要写上,同时generic="true"表示使用泛化。 使用:我这里直接使用controller 注入iUserInfoService,我们要关心的是注入的类型GenericService。

image.png 在使用的时候,调用GenericService的$invoke 方法,参数: 第一参数是 调用的方法名, 第二个是参数的类型们,第三个就是参数值。

4.9.3 调用测试

我们来使用postman测试下

image.png 我们可以看到调用成功了,那dubbo是怎样实现的呢,我们接下来看下它的实现源码。

4.9.4 泛化调用源码分析

这个要从服务调用端的服务引用说起,首先在ReferenceConfig的checkAndUpdateSubConfigs方法中,有这么一段代码:

// 如果泛化参数为true,将interfaceClass设置成GenericService.class
if (ProtocolUtils.isGeneric(generic)) {
    interfaceClass = GenericService.class;
} else {
    try {
        interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                .getContextClassLoader());
    } catch (ClassNotFoundException e) {
        throw new IllegalStateException(e.getMessage(), e);
    }
    //checkInterfaceAndMethods(interfaceClass, getMethods());
}

如果是泛化调用,就将interfaceClass设置成GenericService.class,这时候interfaceName 属性还是咱们那个配置的那个接口,在上面案例中就是com.hsf.dubbo.provider.IUserInfoService,接着在一个收集信息的map 添加interface=com.hsf.dubbo.provider.IUserInfoService 键值对。

image.png 再往后走将map拼成一个长串,然后添加refer参数中。 同时我们生成的代理类,实现了GenericService 接口,重写$invoke方法。下面截图是我使用arthas获取的代理类。

image.png

接着当注册中心变动的时候会通知RegistryDirectory进行toInvokers,在new DubboInvoker 的构造中,有这么一段代码,

image.png

我们在看下super的处理:

image.png 这个convertAttachment 其实就是从url中获取这个属性,然后将值塞到AbstractInvoker的attachment成员中。接下来我们再来看下服务调用者调用过程。

我们在服务调用端 invoke的时候会调用到一个GenericImplFilter ,我们看下源码实现

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // 是否是泛化调用
    String generic = invoker.getUrl().getParameter(GENERIC_KEY);

    // calling a generic impl service
    if (isCallingGenericImpl(generic, invocation)) {
        RpcInvocation invocation2 = new RpcInvocation(invocation);

        /**
         * Mark this invocation as a generic impl call, this value will be removed automatically before passing on the wire.
         * See {@link RpcUtils#sieveUnnecessaryAttachments(Invocation)}
         */
        invocation2.put(GENERIC_IMPL_MARKER, true);
        //方法名
        String methodName = invocation2.getMethodName();
        // 参数类型们
        Class<?>[] parameterTypes = invocation2.getParameterTypes();
        // 具体参数
        Object[] arguments = invocation2.getArguments();

        // 将参数类型class 们转成 string类型
        String[] types = new String[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            types[i] = ReflectUtils.getName(parameterTypes[i]);
        }

        Object[] args;
        if (ProtocolUtils.isBeanGenericSerialization(generic)) {
            args = new Object[arguments.length];

            for (int i = 0; i < arguments.length; i++) {
                // 进行序列化
                args[i] = JavaBeanSerializeUtil.serialize(arguments[i], JavaBeanAccessor.METHOD);
            }
        } else {
            args = PojoUtils.generalize(arguments);
        }

        // 这里封装成 $invoke的 形式,就是封装成 跟调用 GenericService接的 $invoke 方法一样
        if (RpcUtils.isReturnTypeFuture(invocation)) {
            invocation2.setMethodName($INVOKE_ASYNC);
        } else {
            invocation2.setMethodName($INVOKE);
        }
        invocation2.setParameterTypes(GENERIC_PARAMETER_TYPES);
        invocation2.setParameterTypesDesc(GENERIC_PARAMETER_DESC);
        invocation2.setArguments(new Object[]{methodName, types, args});
        return invoker.invoke(invocation2);
    }
    // ----------------------------------------------
    // making a generic call to a normal service
    // 方法名=$invoke
    else if (isMakingGenericCall(generic, invocation)) {

        Object[] args = (Object[]) invocation.getArguments()[2];
        if (ProtocolUtils.isJavaGenericSerialization(generic)) {

            for (Object arg : args) {
                if (byte[].class != arg.getClass()) {
                    error(generic, byte[].class.getName(), arg.getClass().getName());
                }
            }
        } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
            for (Object arg : args) {
                if (!(arg instanceof JavaBeanDescriptor)) {
                    error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName());
                }
            }
        }
        // 设置generic=true
        invocation.setAttachment(
                GENERIC_KEY, invoker.getUrl().getParameter(GENERIC_KEY));
    }
    return invoker.invoke(invocation);
}

private void error(String generic, String expected, String actual) throws RpcException {
    throw new RpcException("Generic serialization [" + generic + "] only support message type " + expected + " and your message type is " + actual);
}

@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
    String generic = invoker.getUrl().getParameter(GENERIC_KEY);
    String methodName = invocation.getMethodName();
    Class<?>[] parameterTypes = invocation.getParameterTypes();
    Object genericImplMarker = invocation.get(GENERIC_IMPL_MARKER);
    if (genericImplMarker != null && (boolean) invocation.get(GENERIC_IMPL_MARKER)) {
        // 没有异常
        if (!appResponse.hasException()) {
            // 获取结果值
            Object value = appResponse.getValue();
            try {
                // 获取原先调用的那个method
                Class<?> invokerInterface = invoker.getInterface();
                if (!$INVOKE.equals(methodName) && !$INVOKE_ASYNC.equals(methodName)
                        && invokerInterface.isAssignableFrom(GenericService.class)) {
                    try {
                        // find the real interface from url
                        String realInterface = invoker.getUrl().getParameter(Constants.INTERFACE);
                        invokerInterface = ReflectUtils.forName(realInterface);
                    } catch (Throwable e) {
                        // ignore
                    }
                }

                Method method = invokerInterface.getMethod(methodName, parameterTypes);
                if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                    if (value == null) {
                        appResponse.setValue(value);
                    } else if (value instanceof JavaBeanDescriptor) {
                        // 需要反序列化
                        appResponse.setValue(JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) value));
                    } else {
                        throw new RpcException("The type of result value is " + value.getClass().getName() + " other than " + JavaBeanDescriptor.class.getName() + ", and the result is " + value);
                    }
                } else {
                    Type[] types = ReflectUtils.getReturnTypes(method);
                    appResponse.setValue(PojoUtils.realize(value, (Class<?>) types[0], types[1]));
                }
            } catch (NoSuchMethodException e) {
                throw new RpcException(e.getMessage(), e);
            }
        } else if (appResponse.getException() instanceof com.alibaba.dubbo.rpc.service.GenericException) {
            com.alibaba.dubbo.rpc.service.GenericException exception = (com.alibaba.dubbo.rpc.service.GenericException) appResponse.getException();
            try {
                String className = exception.getExceptionClass();
                Class<?> clazz = ReflectUtils.forName(className);
                Throwable targetException = null;
                Throwable lastException = null;
                try {
                    targetException = (Throwable) clazz.newInstance();
                } catch (Throwable e) {
                    lastException = e;
                    for (Constructor<?> constructor : clazz.getConstructors()) {
                        try {
                            targetException = (Throwable) constructor.newInstance(new Object[constructor.getParameterTypes().length]);
                            break;
                        } catch (Throwable e1) {
                            lastException = e1;
                        }
                    }
                }
                if (targetException != null) {
                    try {
                        Field field = Throwable.class.getDeclaredField("detailMessage");
                        if (!field.isAccessible()) {
                            field.setAccessible(true);
                        }
                        field.set(targetException, exception.getExceptionMessage());
                    } catch (Throwable e) {
                        logger.warn(e.getMessage(), e);
                    }
                    appResponse.setException(targetException);
                } else if (lastException != null) {
                    throw lastException;
                }
            } catch (Throwable e) {
                throw new RpcException("Can not deserialize exception " + exception.getExceptionClass() + ", message: " + exception.getExceptionMessage(), e);
            }
        }
    }
}

@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

}

代码中隔离线下面的就是处理$invoke这种的,我们看到最后其实就是往invocation 添加了个generic=true的属性。其他两种需要 进行序列化,nativejava与bean。

我们再来看下服务提供者端的GenericFilter:

@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
    // 判断是不是泛化调用
    if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
            && inv.getArguments() != null
            && inv.getArguments().length == 3
            && !GenericService.class.isAssignableFrom(invoker.getInterface())) { // interface !=GenericService
        // 获取第一个参数,也就是 方法名
        String name = ((String) inv.getArguments()[0]).trim();
        // 参数类型
        String[] types = (String[]) inv.getArguments()[1];
        // 参数列表
        Object[] args = (Object[]) inv.getArguments()[2];
        try {
            // 通过 interface  , 方法名 ,方法参数类型 获得method
            Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
            Class<?>[] params = method.getParameterTypes();
            // 如果是null
            if (args == null) {
                args = new Object[params.length];
            }

            if(types == null) {
                types = new String[params.length];
            }

            if (args.length != types.length) {
                throw new RpcException("GenericFilter#invoke args.length != types.length, please check your "
                        + "params");
            }
            //generic
            String generic = inv.getAttachment(GENERIC_KEY);

            // 从rpccontext  中获取 generic
            if (StringUtils.isBlank(generic)) {
                generic = RpcContext.getClientAttachment().getAttachment(GENERIC_KEY);
            }

            // generic不是null,然后=true
            ...

            RpcInvocation rpcInvocation =
                    new RpcInvocation(method, invoker.getInterface().getName(), invoker.getUrl().getProtocolServiceKey(), args,
                            inv.getObjectAttachments(), inv.getAttributes());
            rpcInvocation.setInvoker(inv.getInvoker());
            rpcInvocation.setTargetServiceUniqueName(inv.getTargetServiceUniqueName());

            //进行执行
            return invoker.invoke(rpcInvocation);
        } catch (NoSuchMethodException | ClassNotFoundException e) {
            throw new RpcException(e.getMessage(), e);
        }
    }
    return invoker.invoke(inv);
}

@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation inv) {
    if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
            && inv.getArguments() != null
            && inv.getArguments().length == 3
            && !GenericService.class.isAssignableFrom(invoker.getInterface())) {

        String generic = inv.getAttachment(GENERIC_KEY);
        if (StringUtils.isBlank(generic)) {
            generic = RpcContext.getClientAttachment().getAttachment(GENERIC_KEY);
        }

        if (appResponse.hasException()) {
            Throwable appException = appResponse.getException();
            if (appException instanceof GenericException) {
                GenericException tmp = (GenericException) appException;
                appException = new com.alibaba.dubbo.rpc.service.GenericException(tmp.getExceptionClass(), tmp.getExceptionMessage());
            }
            if (!(appException instanceof com.alibaba.dubbo.rpc.service.GenericException)) {
                appException = new com.alibaba.dubbo.rpc.service.GenericException(appException);
            }
            appResponse.setException(appException);
        }
        ...
    }
}

@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

}

我们可以看到首先判断inv的方法名是不是$invoke,参数个数,interface不是GenericService。然后获取generic,进行反序列化,因为在服务调用端的filter中有对参数值序列化。接着就是重新封装invocation,替换成真正要调用的方法名,方法参数类型,方法参数,然后进行调用用,封装结果或者处理异常返回给调用端。

参考文章

Dubbo3.0 source code comment github address In-
depth analysis of Apache Dubbo core technology insider
dubbo source code series
dubbo source code analysis column

Guess you like

Origin juejin.im/post/7120596556541067300