Dubbo消费端调用全过程实现分析(一)-代理类生成

748 阅读8分钟

前言

在微服务架构流行的当下,高性能开源RPC框架Dubbo现已被广泛的使用在各类系统中,本文将以消费端的视角,通过Dubbo源码的实现来了解消费端获得代理对象Proxy的过程,有助于对Dubbo的底层实现原理与思想的理解,同时能够帮助使用者在实际系统中应用时更恰当的运用Dubbo的能力。

对于Java开发者而言,在刚开始接触使用Dubbo框架时,最大的感受莫过于其便利性,即调用远程的接口可以通过引入一个SDK(jar包)后即可像使用本地方法一样去进行调用接口。在这种便利使用的背后其实是Dubbo框架内部已经做了很多事情,核心的包括注册订阅、负载均衡、网络通信(通信协议、序列化、反序列化)等,当然还有其他更多保证其健壮性、扩展性的功能。

下面将以消费端的初始生成代理类的过程进行学习分析,全文引用Dubbo源码版本为2.7.8-release。

一、消费端代理类生成

如下图为Dubbo的架构图(图片引自Dubbo官方文档),本次内容涉及到的是步骤2、3,也就是消费端和注册中心之间的交互,了解完步骤2、3之后,对步骤4的调用的内容就会很清晰明了。

dubbo-architecture.jpg

在使用Dubbo的时,可以采用不同的方式来配置你的Dubbo应用,有包括XML配置、属性配置、动态配置中心、自动加载环境变量、API配置、注解配置。早期最常见的方式是XML配置,而后较多的是注解配置,都会配合属性配置进行,不同方式的配置都会使用相同的底层代码。这里为了方便查看理解选用API配置进行分析说明。

如下测试代码所示,消费端获取接口的方法是在ReferenceConfig的get()方法:

@Test
public void test() {
    // <dubbo:application name="consumer-demo" />
    ApplicationConfig application = new ApplicationConfig();
    application.setName("consumer-demo");
    
    // <dubbo:registry address="zookeeper://xx.xx.x.xx:2183" />
    RegistryConfig registry = new RegistryConfig();
    registry.setAddress("zookeeper://xx.xx.x.xx:2183");
    
    // <dubbo:reference interface="org.apache.dubbo.samples.basic.api.DemoService" />
    ReferenceConfig<DemoService> rc = new ReferenceConfig<DemoService>();
    rc.setApplication(application);
    rc.setRegistry(registry);
    rc.setInterface(DemoService.class.getName());
    // 获取服务接口
    DemoService demoService = rc.get();
    
    // 获取接口实例后,可以进行调用
    // 略
}

二、ReferenceConfig获取代理类

2.1 init()

(1)如下图,get方法比较简单,如果已经初始化过ref则直接返回,否则进入初始化方法init()。

// ReferenceConfig.java

/**
 * The interface proxy reference
 */
private transient volatile T ref;

public synchronized T get() {
        if (destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
        }
        if (ref == null) {
            // 初始化
            init();
        }
        return ref;
    }

(2)如下个init()方法代码,省略了非重点的代码

// ReferenceConfig.java

public synchronized void init() {
        // 省略一些初始化检查操作
    	// 省略参数map的构造过程

        // 生成代理类
    	ref = createProxy(map);

        serviceMetadata.setTarget(ref);
        serviceMetadata.addAttribute(PROXY_CLASS_REF, ref);
        ConsumerModel consumerModel = repository.lookupReferredService(serviceMetadata.getServiceKey());
        consumerModel.setProxyObject(ref);
        consumerModel.init(attributes);

        initialized = true;

        checkInvokerAvailable();

        // dispatch a ReferenceConfigInitializedEvent since 2.7.4
        dispatch(new ReferenceConfigInitializedEvent(this, invoker));
    }

init()方法的代码较长,我们主要关注的是创建代理类的方法createProxy。

为方面后面的理解,通过dubug上面test的代码执行到createProxy(map)时的map内容如下:

image-20220823114222195.png

2.2 createProxy

dubbo-refer-03.png

如上,从createProxy方法的代码实现中可以明确的看出有3种创建代理类的场景,也就是**injvm、端对端、注册中心**三种场景。在实际使用中主要的是用注册中心方式,上述test案例会通过单注册中心的方式创建。

(1)injvm

一般在以下3种情况下,会选择injvm方式来创建代理引用类

  • 通过为dubbo:reference配置injvm=true,该配置参数已建议删除
  • 通过为dubbo:reference配置scope=local
  • service export时在本地暴露接口

有一种场景要说明下,如果一个接口的提供者和消费者都在一个应用,消费者在引用是会默认走injvm方式,可以通过设置injvm=false或scope=remote来控制走注册中心的方式。

(2)端对端

端对端的应用场景是在联调测试阶段,可以通过配置指定的提供者地址来进行调用,无需通过注册中心来订阅提供者信息。

  • 通过为dubbo:refenrece配置url,多个地址使用逗号分隔

参考如下配置:

<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />

(3)注册中心

在使用注册中心方式时,首先会检查注册中心配置的信息,然后使用一个list来保存注册中心的url,这里使用的是单注册中心,此处最终用于向注册中心订阅的url如下,refer的value会使用utf-8进行编码

-- 编码的url
registry://xx.xx.x.xx:2183/org.apache.dubbo.registry.RegistryService?application=consumer-demo&dubbo=2.0.2&pid=17120&refer=application%3Dconsumer-demo%26dubbo%3D2.0.2%26injvm%3Dfalse%26interface%3Dorg.apache.dubbo.config.api.DemoService%26methods%3DsayName%2CgetUsers%2Cecho%2CthrowDemoException%2CgetBox%26pid%3D17120%26register.ip%3D172.xx.xx.xx%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1661236826707&registry=zookeeper&timestamp=1661236836715
​
-- 对应refer解码如下
refer=application=consumer-demo&dubbo=2.0.2&injvm=false&interface=org.apache.dubbo.config.api.DemoService&methods=sayName,getUsers,echo,throwDemoException,getBox&pid=17120&register.ip=172.xx.xx.xx&side=consumer&sticky=false&timestamp=1661236826707

经过分析可以将createProxy方法的代码简化如下,接下来就是看invoker的生成过程,最后生成代理代理类是以invoker为目标类。

// ReferenceConfig.javaprivate static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
​
private T createProxy(Map<String, String> map) {
        if (shouldJvmRefer(map)) {
            // (1)injvm
            // 省略....
        } else {
            urls.clear();
            if (url != null && url.length() > 0) {
                // (2)端对端
                // 省略...
            } else {
                // (3)注册中心
                
                // 省略 构造注册中心urls...
​
            if (urls.size() == 1) {
                // 关键步骤
                invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
            }else {
               // 多注册中心
                
            }
        }
        // ...
        
        // create service proxy
        return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
    }

注意到在这里会使用dubbo的SPI扩展自适应获取Protocol的实现类,而refer方法是被注解@Adaptive修饰的。关于Dubbo的SPI可参见:dubbo.apache.org/zh/docs/v2.…

根据Dubbo SPI实现的方式,此处的url为registry://...(完整数据见上面),所以在调用refer方法时会经过以下链路:

ProtocolFilterWrapper
    ProtocolListenerWrapper
        RegistryProtocol

其中,在ProtocolFilterWrapper和ProtocolListenerWrapper的refer方法中对注册中心的url都会直接跳过往下执行,所以最终执行的位置在RegistryProtocol中。

2.3 RegistryProtocol

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        url = getRegistryUrl(url);
        // 选择注册中心
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }
​
        // group="a,b" or group="*"
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
        String group = qs.get(GROUP_KEY);
        if (group != null && group.length() > 0) {
            if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
                return doRefer(Cluster.getCluster(MergeableCluster.NAME), registry, type, url);
            }
        }
        // 集群模式
        Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY));
        return doRefer(cluster, registry, type, url);
    }

在RegistryProtocol.refer的过程中会根据注册中心url选择相应的注册中心,根据cluster配置选择集群模式,然后完成在注册中心的注册、订阅,并返回Invoker实例。

(1)注册中心

以此处测试的zookeeper注册中心为例,对应的url见上方。最终,对应的registry为ListenerRegistryWrapper包含ZookeeperRegistryFactory实例。

(2)集群模式

集群模式可通过配置cluster进行,可选failover/failfast/failsafe/failback/forking,默认是failover。此处对应的cluster为MockClusterWrapper包含FailoverCluster实例。

MockClusterWrapper
    FailoverCluster

(3)doRefer

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
        URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
        if (directory.isShouldRegister()) {
            directory.setRegisteredConsumerUrl(subscribeUrl);
            // 注册
            registry.register(directory.getRegisteredConsumerUrl());
        }
        directory.buildRouterChain(subscribeUrl);
        // 订阅
        directory.subscribe(toSubscribeUrl(subscribeUrl));
​
        // 这一步返回的Invoker很重要
        Invoker<T> invoker = cluster.join(directory);
        List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
        if (CollectionUtils.isEmpty(listeners)) {
            return invoker;
        }
​
        RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker);
        for (RegistryProtocolListener listener : listeners) {
            listener.onRefer(this, registryInvokerWrapper);
        }
        return registryInvokerWrapper;
    }

如上doRefer代码所示,首先会创建RegisteyDerecroty,并将注册中心实例注入。以此处test为例,此时的subscribeUrl为

consumer://172.xx.xx.xx/org.apache.dubbo.config.api.DemoService?application=consumer-demo&dubbo=2.0.2&injvm=false&interface=org.apache.dubbo.config.api.DemoService&methods=sayName,getUsers,echo,throwDemoException,getBox&pid=15080&side=consumer&sticky=false&timestamp=1661243789699

注册的过程是在zookeeper上创建一个临时节点

/dubbo
    /org.apache.dubbo.config.api.DemoService
        /consumers
            /consumer://172.xx.xx.xx/org.apache.dubbo.config.api.DemoService?...

订阅的过程是在zookeeper上创建一个永久节点,并注册一个监听器,在此接口对应的节点数据发生变化时会notify到消费端。

/dubbo
    /org.apache.dubbo.config.api.DemoService
        /configurators

在订阅过程中,有一个很关键的过程是初始化RegistryDirectory的invokers属性,这个invokers在调用的过程中会真实用到。

2.4 RegistryDirectory的invokers生成

订阅过程在zookeeper上创建节点后会调用RegistryDirectory的notify方法(在zk节点变化时也会调用)。由于代码较多,下面给出调用的链路

RegistryDirectory#notify
    RegistryDirectory#refreshOverrideAndInvoker
        RegistryDirectory#refreshInvoker
            RegistryDirectory#toInvokers

最终会定位到

invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);

此处的serviceType为接口org.apache.dubbo.config.api.DemoServiceurldubbo://172.xx.xx.xx:20880/org.apache.dubbo.config.api.DemoService?anyhost=true&application=consumer-demo&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&injvm=false&interface=org.apache.dubbo.config.api.DemoService&methods=sayName,getUsers,echo,throwDemoException,getBox&pid=26868&register.ip=172.xx.xx.xx&release=&remote.application=consumer-demo&scope=local&side=consumer&sticky=false&timestamp=1661246567095

InvokerDelegate为RegistryDirecroty的内部静态类,其会持有protocol.refer(serviceType, url)生成代理类,这个类就是最终调用时会用到的Invoker,其组成图大致如下:

ProtocolFilterWrapper
    ListenerInvokerWrapper
        AsyncToSyncInvoker
            DubboInvoker

ProtocolFilterWrapper作为最外层会生成一个Filter调用链,具体代码在ProtocolFilterWrapper的buildInvokerChain中,最终生成的Invoker关系如下:

image-20220823214121493.png

如上图所示,本质上是组成一个Invoker链,在消费端执行rpc调用时,首先会执行消费端的Filter链,然后再依次执行ListenerInvokerWrapper、AsyncToSyncInvoker、DubboInvoker。

从源码中也可以看出,可通过配置ReferenceConfig的filter来配置消费端的Filter,像ConsumerContextFilter、FutureFilter、MonitorFilter都是默认激活的消费端的filter,如下为ConsumerContextFilter的注解配置信息:

@Activate(group = CONSUMER, order = -10000)
public class ConsumerContextFilter implements Filter {
}

注意,在这里的invokers是保存在RegistryDirectory实例中,同时invokers会保存在routerChain中,routerChain会保存在RegistryDirectory中,而RegistryDirectory的实例会最终传入到Invoker invoker = cluster.join(directory)中。

2.5 MockClusterWrapper

最终用于生成代理的Invoker的过程就在如下这行代码中:

Invoker<T> invoker = cluster.join(directory);

这里的cluster在上面的2.3中已说明,即为MockClusterWrapper包含了FailoverCluster的实例。

public class MockClusterWrapper implements Cluster {
​
    private Cluster cluster;
​
    public MockClusterWrapper(Cluster cluster) {
        this.cluster = cluster;
    }
​
    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory,
                this.cluster.join(directory));
    }
​
}

最终会返回MockClusterInvoker实例,而this.cluster.join(directory)可以继续跟进到FailoverCluster中,如下:

public abstract class AbstractCluster implements Cluster {
​
    // 这一步生成Invoker很重要
    private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker, String key) {
        AbstractClusterInvoker<T> last = clusterInvoker;
        List<ClusterInterceptor> interceptors = ExtensionLoader.getExtensionLoader(ClusterInterceptor.class).getActivateExtension(clusterInvoker.getUrl(), key);
       
        if (!interceptors.isEmpty()) {
            for (int i = interceptors.size() - 1; i >= 0; i--) {
                final ClusterInterceptor interceptor = interceptors.get(i);
                final AbstractClusterInvoker<T> next = last;
                last = new InterceptorInvokerNode<>(clusterInvoker, interceptor, next);
            }
        }
        return last;
    }
​
    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return buildClusterInterceptors(doJoin(directory), directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
    }
    
    }

其中doJoin会返回FailoverClusterInvoker,然后通过buildClusterInterceptors,生成一个拦截器链,在此链中FailoverClusterInvoker处于最后一个节点。

2.6 生成代理类

最后,回到ReferenceConfig#createProxy方法的最后一行将对上面生成的MockClusterInvoker生成代理类

private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
​
private T createProxy(Map<String, String> map) {
    
    // 省略
    
    return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}

通过ProxyFactory的实现类来完成动态代理类的生成,有javassist和jdk两种生成方式,可以通过proxy参数进行配置或设置,默认是使用javassist的方式生成,最终使用InvokerInvocationHandler来持有invoker。

三、总结

对代理类的生成过程进行了解后,才能更好的了解整个消费端在进行rpc调用过程细节,包括怎么去自定义扩展实现拦截器、过滤器;还能更好的了解路由选择、负载均衡、集群容错的方式。下图为消费端调用类链路图:

Dubbo消费端调用类图链路-第 1 页.jpg