Dubbo 源码学习系列(一) 浅析远程调用的核心流程

        Dubbo是一个RPC开源框架,自定义Dubbo协议实现远程调用。 

一、Dubbo 官方架构图

      官方给的架构图主要分为了4个版块:  注册中心Registry、监视器Monitor、服务提供者Provider、服务消费者Consumer。

二、功能解析

        在学习Dubbo 源码前,需要了解Dubbo是一个用Java实现的高性能RPC框架, 主要功能包含服务注册与发现、集群容错、远程调用、负载均衡、高度可扩展、运行期流量调度、可视化的服务治理与运维等功能。

三、从消费方解析Dubbo 源码是如何实现调用

首先我们先看一下Consumer的main方法:

     1. 初始化xml配置。

     2. 启动Spring 容器。

     3.  获取Bean对象,即远程服务对象,其实是一个代理的对象。

     4. 执行服务的目标方法。

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.dubbo.demo.consumer;

import org.apache.dubbo.demo.DemoService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Consumer {

    /**
     * To get ipv6 address to work, add
     * System.setProperty("java.net.preferIPv6Addresses", "true");
     * before running your application.
     */
    public static void main(String[] args) {
        // 加载配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
        // 启动Spring 容器, 如果调用getBean()方法,Spring 容器会默认启动
        context.start();
        // 获取代理对象
        DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy
        // 获取接口的代理对象
        while (true) {
            try {
                Thread.sleep(1000);
                // 执行代理对象里的方法
                String hello = demoService.sayHello("world"); // call remote method
                System.out.println(hello); // get result
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
    }
}

为了方便理解,我们可以把消费者远程调用其他服务的过程拆分成以下几个步骤:

1.  准备工作。在Spring 容器启动的时候就开始准备,初始化服务、服务需要的registry、Bean、从注册中心拉取服务地址列表等。

2. 服务发现与导入, 服务生产者把服务推送到zookeeper registry,服务消费者订阅注册中心zookeeper, 从zookeeper 注册中心拉取缓存到本地。

3. 服务调用, consumer 调用远程服务, 包含服务容错机制、负载均衡策略等。

         实际上,dubbo源码的执行流程比上面描述的要复杂很多,在这里我主要是为了去理解远程调用的执行流程和设计思想,先把它概括成三个步骤:  准备、服务导入和服务发现、服务调用

DubboNamespaceHandler

        DubboNamespaceHandler是Dubbo定义的一个基于Spring 初始化容器的入口类, 我们可以在dubbo-config-spring 的module里的spring.handlers文件里找到配置的该类。

http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler

         该类用继承了Spring 的NamespaceHandlerSupport类,重写了init()方法和parse方法,这样dubbo才能实现解析自己的xml配置文件。

Consumer 初始化

1. 初始化 consumer 配置文件,加载并解析dubbo-demo-consumer.xml配置。

         首先我们可以找到加载xml配置文件的类DubboNamespaceHandler,在此类下的init()方法下打一个断点,如下图:

在此行停顿,我们可以发现用到了一个ReferenceBean的类,它其实是META-INF.spring目录下dubbo-demo-consumer.xml文件里配置的 reference:

同时在main()方法的 context.start()方法处打一个断点:

可以发现加载配置的代码是在.start()方法之前执行的,简单的说是通过new ClassPathXmlApplicationContext()方法初始化配置文件。

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});

2. 初始化referenceBean流程解析

      referenceBean 是远程调用服务关键的类,实现了FatoryBean 等Spring 相关的接口,用来读取并初始化<dubbo:reference >标签里配置的interface等属性,referenceBean 里的执行流程可以概况为如下:

   DubboNamespaceHandler:

        ReferenceBean

             . getObject()  # 实现org.springframework.beans.factory.FactoryBean 接口里的getObject() 方法

                      get():

                        # 在init()方法里初始化consumer

                        init()

最终是在init()方法里初始化整个consumer的服务。

3. ReferenceConfig类里的init()方法解析

      init()方法里包含很多初始化的过程,给后面要用到的变量进行赋值。例如初始化要调用的接口名,接口方法、初始化zookeeper注册中心连接、监视器等

      先重点看一下此方法里初始化的一个重要的Map, 里面包含了远程接口的各种信息,如应用名称、dubbo版本、方法名称、自己配置的代理模式(Jdk动态代理)等,此map会作为创建Proxy的参数列表。

        根据map创建代理对象

ref = createProxy(map);

        然后使用DubboSPI 机制获取生成代理对象的工厂,Dubbo的SPI核心实现在ExtensionLoader和ExtensionDirector

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

 点击进入到Protocol接口,可以查看@SPI("dubbo"),选择到的使用的协议是dubbo协议。

        继续进入到createProxy()方法里面, 可以找到  return (T) proxyFactory.getProxy(invoker) 方法处, 如果了解动态代理模式,就能很容易理解此方法的作用是通过代理工厂生成代理对象,最终将代理对象返回。

3. 深入研究创建代理对象的机制。

        由上述2可以发现,获取到最终的接口对象是通过动态代理创建的,而dubbo  默认使用的代理工厂的方式是 JavassistProxyFactory

        我们也可以通过Jdk动态代理工厂来生成代理对象, 在consumer的 dubbo:reference 标签里添加proxy属性为 "jdk"

    <dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService" proxy="jdk" />

配置好后,dubbo就会使用JdkProxyFactory来创建代理对象。 

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.dubbo.rpc.proxy.jdk;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.proxy.AbstractProxyFactory;
import org.apache.dubbo.rpc.proxy.AbstractProxyInvoker;
import org.apache.dubbo.rpc.proxy.InvokerInvocationHandler;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JavaassistRpcProxyFactory
 */
public class JdkProxyFactory extends AbstractProxyFactory {

    @Override
    @SuppressWarnings("unchecked")
    // 使用jdk动态d代理
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));
    }

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                Method method = proxy.getClass().getMethod(methodName, parameterTypes);
                return method.invoke(proxy, arguments);
            }
        };
    }
}

 如果不是很理解JDK动态代理,可以参考如下文章:
 JDK动态代理模式详解_Dream_it_possible!的博客-CSDN博客

服务发现与导入

         Directory

          在AbstractClusterInvoker抽象类里维护了一个 directory目录,该目录用来维护了从zookeeper注册中心里的目录节点数据,因为本身zookeeper是一个文件系统。

Dubbo通过容错机制,再调用失败时会重新从zookeeper里拉取所有的目录节点。在FailoverClusterInvoker类的 doInvoke() 方法有个list()方法:

if (i > 0) {
                checkWhetherDestroyed();
                copyinvokers = list(invocation);
                // check again
                checkInvokers(copyinvokers, invocation);
            }

上面的服务发现与导入是为了下面的服务调用做准备工作。

服务调用

 dubbo远程调用的结构图:

 Mock为服务容错,Cluster为集群容错。

   # 最外层为MockClusterInvoker   
    MockClusterInvoker
        # 集群容错策略: 默认为FailOverCluterInvoker
         FailOverClusterInvoker
            # 负载均衡策略
            LoadBalance
               # 服务列表
               List<T> invokers

最终是通过invoker.invoke(invocation)执行远程调用,底层是通过Netty的NIO将方法对象和Url发送到服务提供者,由服务提供者去处理方法url和方法对象。

服务容错机制

在MockClusterInvoker下一层的FailOverClusterInvoker类里能找到服务容错的方法, 如果没有配置重试次数,默认为2次,因此总共会发3次请求,如下:

继续往下看,可以发现,在此处有个for 循环,因为FailOverClusterInvoker的策略是失败重试,直到有个成功就返回!    

服务容错FailOverClusterInvoker的效果演示:

负载均衡机制

        在每次调用前,会使用负载均衡策略去选择一个节点进行调用,dubbo默认地负载均衡策略是随机。

Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);

        同样地dubbo也可以通过配置更改负载均衡策略!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 回答:Dubbo的调用流程主要包括:服务提供方、服务消费方和注册中心三部分组成。其中,服务提供方向注册中心注册服务,服务消费方通过注册中心发现服务并向服务提供方发起调用;服务提供方收到调用请求后,通过调用服务提供者实现服务,并将结果返回给服务消费方。可以参考以下源码解读:http://www.sohu.com/a/242295322_465833。 ### 回答2: Dubbo的调用流程分为三个阶段:服务导出、服务引用和远程调用。下面我将结合源码解读这三个阶段的具体流程。 首先是服务导出阶段。当服务提供者启动时,Dubbo会解析@Service注解,生成ServiceBean对象。然后,Dubbo会调用Protocol的export方法,将ServiceBean暴露成一个Invoker对象,该对象持有ServiceBean的引用和一系列的过滤器链。接着,Dubbo会通过Exporter对象,将Invoker注册到注册中心。最后,Dubbo会根据配置选择合适的Server实现,启动Server并监听服务端口。 接下来是服务引用阶段。当服务消费者启动时,Dubbo会解析@Reference注解,生成ReferenceBean对象。然后,Dubbo会调用ReferenceConfig的get方法,根据配置从注册中心获取服务提供者的Invoker对象。接着,Dubbo会通过Protocol的refer方法,将Invoker封装成一个代理对象,并返回给业务逻辑,实现远程调用。 最后是远程调用阶段。当业务代码调用代理对象的方法时,Dubbo会通过Invocation对象封装方法调用的相关信息。然后,Dubbo会根据服务的URL信息选择合适的InvocationHandler,发起远程调用。在InvocationHandler中,Dubbo会对请求进行编码、协议适配和消息传输,最终将请求发送给服务提供者。服务提供者接收到请求后,会根据请求内容执行相应的服务逻辑,并返回结果。最后,Dubbo会将结果进行解码、协议适配和消息传输,将结果返回给服务消费者。 通过源码解读可以看出,Dubbo的调用流程主要包括服务导出、服务引用和远程调用三个阶段,并且每个阶段都有一系列的类和方法来协调和处理相应的任务。这样的设计使得Dubbo具备了高度的扩展性和灵活性,能够支持不同的协议和中间件,并提供了一系列的配置选项,满足不同场景下的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乌托邦钢铁侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值