与dubbo服务发布相对的,是引用服务进行调用的过程,这个很多步骤都是与服务发布相对的,但是也有特有的地方,比如,负载均衡 ,集群容错等。这篇博客,我们主要关注dubbo服务调用的一个核心过程。

dubbo服务调用的主要过程:将调用信息注册到zk上-> 通知RegistryDirectory刷新可用服务列表->刷新过程中,新服务会与netty服务端建立连接,并封装到DubboInvoker中。-> 选择失败策略通过负载均衡算法,选择服务端具体哪个服务去执行 -> 通过netty返回执行结果。


Dubbo服务调用过程图如下:(看不清,请点击新的页签进行查看)

dubbo使用Java代码 dubbo调用过程_负载均衡


在ReferenceConfig.init的方法里,会把配置信息封闭成一个map,然后去构建一个代理类。在构建这个代理类,会用DubboProtocol获取DubboInvoker。


private T createProxy(Map<String, String> map) {
    // 省略一些代码。。。
    // 通过注册中心配置拼装URL
            List<URL> us = loadRegistries(false);
            if (us != null && us.size() > 0) {
                for (URL u : us) {
                    URL monitorUrl = loadMonitor(u);
                    if (monitorUrl != null) {
                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                    }
                    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                }
            }
            if (urls == null || urls.size() == 0) {
                throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
            }
        }

        if (urls.size() == 1) {
            //会调用RegistryProtocol.refer
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
        } else {
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
           //省略很多代码
    // 创建服务代理,使用JavassistProxyFactory去创建
    return (T) proxyFactory.getProxy(invoker);
}


在RegistryProtocol里,会创建一个RegistryDirectory,这个对象保存的调用服务的信息,并且也是一个监听。


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);
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
    if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
        //调用zk去真实的注册
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
    //最终也是调用zk去订阅监听,监听器是RegistryDirectory
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));
    return cluster.join(directory);
}


在真正注册到zk上,注册监听器到zk相应路径上,会调用RegistryDirectory的notify通知方法,去获取可用的服务列表。


public synchronized void notify(List<URL> urls) {
    List<URL> invokerUrls = new ArrayList<URL>();
    List<URL> routerUrls = new ArrayList<URL>();
    List<URL> configuratorUrls = new ArrayList<URL>();
   //初始化值 
    //省略代码
    // configurators
    if (configuratorUrls != null && configuratorUrls.size() > 0) {
        this.configurators = toConfigurators(configuratorUrls);
    }
    // routers
    if (routerUrls != null && routerUrls.size() > 0) {
        List<Router> routers = toRouters(routerUrls);
        if (routers != null) { // null - do nothing
            setRouters(routers);
        }
    }
    List<Configurator> localConfigurators = this.configurators; // local reference
    // 合并override参数
    this.overrideDirectoryUrl = directoryUrl;
    if (localConfigurators != null && localConfigurators.size() > 0) {
        for (Configurator configurator : localConfigurators) {
            this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
        }
    }
    // providers  刷新服务列表
    refreshInvoker(invokerUrls);
}




其中调用refershInvoker方法时,会去调用toInvokers把URl列表转换为Invoker列表。


private void refreshInvoker(List<URL> invokerUrls) {
    if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
            && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
        this.forbidden = true; // 禁止访问
        this.methodInvokerMap = null; // 置空列表
        destroyAllInvokers(); // 关闭所有Invoker
    } else {
        this.forbidden = false; // 允许访问
        Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
        if (invokerUrls.size() == 0 && this.cachedInvokerUrls != null) {
            invokerUrls.addAll(this.cachedInvokerUrls);
        } else {
            this.cachedInvokerUrls = new HashSet<URL>();
            this.cachedInvokerUrls.addAll(invokerUrls);//缓存invokerUrls列表,便于交叉对比
        }
        if (invokerUrls.size() == 0) {
            return;
        }
        Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// 将URL列表转成Invoker列表
        Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 换方法名映射Invoker列表
        //省略一些代码
        。。。。。。。
    }
}


在将这些url转换为invoker时,会使用DubboProtocol去创建与netty服务端的连接。

private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
    Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
    if (urls == null || urls.size() == 0) {
        return newUrlInvokerMap;
    }
    Set<String> keys = new HashSet<String>();
    String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
    for (URL providerUrl : urls) {
       //封装url信息
        //省略一些代码
        // 缓存key为没有合并消费端参数的URL,不管消费端如何合并参数,如果服务端URL发生变化,则重新refer
        Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
        Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
        if (invoker == null) { // 缓存中没有,重新refer
            try {
                boolean enabled = true;
                if (url.hasParameter(Constants.DISABLED_KEY)) {
                    enabled = !url.getParameter(Constants.DISABLED_KEY, false);
                } else {
                    enabled = url.getParameter(Constants.ENABLED_KEY, true);
                }
                if (enabled) {
                    //服务可以就使用DubboProtocol去创建连接,封装DubboInvoker
                    invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url, providerUrl);
                }


在DubboProtocol.refer里,会去创建netty客户端连接。

public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
    // create rpc invoker.
    DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
    invokers.add(invoker);
    return invoker;
}




最后是返回一个DubboInvoker对象,在这之前会调用getClients先去获取或创建客户端连接。是共享连接就获取之前的加接,不是的话就创建新的连接。

private ExchangeClient initClient(URL url) {

    //设置一些参数,比如心跳机制等
    // client type setting.
    String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));

    String version = url.getParameter(Constants.DUBBO_VERSION_KEY);
    boolean compatible = (version != null && version.startsWith("1.0."));
    url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() && compatible ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
    //默认开启heartbeat
    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

   //省略一些代码

    ExchangeClient client;
    try {
        //设置连接应该是lazy的 
        if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
            client = new LazyConnectExchangeClient(url, requestHandler);
        } else {
            //跟服务发布相对,具体调用HeaderExchanger.connect
            client = Exchangers.connect(url, requestHandler);
        }




在HeaderExchanger里,也是通过具体的NettyTransports连接去创建一个NettyClient,在doOpen方法里,创建netty连接,熟悉netty的应该对这段代码不陌生


protected void doOpen() throws Throwable {
    NettyHelper.setNettyLoggerFactory();
    bootstrap = new ClientBootstrap(channelFactory);
    // config
    // @see org.jboss.netty.channel.socket.SocketChannelConfig
    bootstrap.setOption("keepAlive", true);
    bootstrap.setOption("tcpNoDelay", true);
    bootstrap.setOption("connectTimeoutMillis", getTimeout());
    final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
    bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
        public ChannelPipeline getPipeline() {
            //使用适配,用DubboCodec去编码解码
            NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
            ChannelPipeline pipeline = Channels.pipeline();
            pipeline.addLast("decoder", adapter.getDecoder());
            pipeline.addLast("encoder", adapter.getEncoder());
            //工作线程的处理handler
            pipeline.addLast("handler", nettyHandler);
            return pipeline;
        }
    });
}


在RegistryDirectory中,缓存了可用的服务列表Invoker,之后具体使用哪个服务去调用,就看选择的负载均衡策略了。在

RegistryProtocol.doRefer里,会去执行“cluster.join(directory)”,这里的cluster看是使用哪种失败策略。默认会调用FailoverCluster.join。在FailoverClusterInvoker.doInvoke里,会去选择具体要调用哪参机器上的哪个服务。

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    List<Invoker<T>> copyinvokers = invokers;
    checkInvokers(copyinvokers, invocation);
    int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
    if (len <= 0) {
        len = 1;
    }
    // retry loop.
    RpcException le = null; // last exception.
    List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
    Set<String> providers = new HashSet<String>(len);
    for (int i = 0; i < len; i++) {
        //重试时,进行重新选择,避免重试时invoker列表已发生变化.
        //注意:如果列表发生了变化,那么invoked判断会失效,因为invoker示例已经改变
        if (i > 0) {
            checkWhetherDestroyed();
            copyinvokers = list(invocation);
            //重新检查一下
            checkInvokers(copyinvokers, invocation);
        }
        //使用loadbalance负载均衡选择哪个服务invoker去调用
        Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
        invoked.add(invoker);
        RpcContext.getContext().setInvokers((List) invoked);
        try {
            //调用执行调用链
            Result result = invoker.invoke(invocation);
            
            //省略一些代码。。。。


最后真正调用服务端的DubboProtocol.requestHandler.reply去处理,

private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {

    public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
        if (message instanceof Invocation) {
            Invocation inv = (Invocation) message;
            //从DubboExporter里获取Invoker
            Invoker<?> invoker = getInvoker(channel, inv);
            //如果是callback 需要处理高版本调用低版本的问题
            if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
               //callback的处理,省略。。。。
            }
            RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
            //反射调用,真正客户端方法调用的地方
            return invoker.invoke(inv);
        }



处理结果再通过netty返回到客户端resutlt中。

到此,一个服务调用过程就基本完成了。与之前的dubbo 源码学习笔记 (二) —— dubbo发布服务的过程相对应,形成dubbo服务发布调用的整个过程。这里总结一下整体过程:

dubbo使用Java代码 dubbo调用过程_源码_02


dubbo工作原理分为服务发布和服务引用两个核心过程。

1、服务发布的时候,DubboProtocol将调用链封装为DubboExporter,放入到netty服务端工作线程池中

2、URL配置信息注册到zk注册中心,并注册override监听,触发订阅。

3、服务引用时,也将服务引用的信息封装成URL并注册到zk注册中心,同时监听category、providers、configurators、routers这四个目录节点(才能感知服务上下线的变化)。将这些信息包装成DubboInvoker客户端的调用链,返回代理。

4、客户端使用代理进行调用时,经过负载均衡,选择其中一个服务的URL,根据URl信息与netty建立连接,发送Invocation到netty服务端;服务端在工作线程池中找一个线程,处理Invocation,并把RpcResult结果返回给客户端;客户端接收解析RpcResult,获取处理结果。

这样整个过程就基本结束。理解这一整个过程,也就相当于了解了dubbo整体的原理。也就能从大的方向把握这一框架,当然,dubbo框架还有很多值得学习研究的地方。在之后的博客中会继续分析。