今天咱们就来手撕 Dubbo 源码,来达到彻底了解其本质的目的。

    Dubbo 怎样实现远程过程通信

    手撕代码之前咱们来做一个宏观上的认知。

    Dubbo源码 - 图1

    上面是 Dubbo 的部署架构。注册中心、配置中心、元数据中心这三大中心化组件的各自的职责、工作方式如下:

    • 注册中心。协调 Consumer 与 Provider 之间的地址注册与发现
    • 配置中心。
    • 存储 Dubbo 启动阶段的全局配置,保证配置的跨环境共享与全局一致性
    • 负责服务治理规则(路由规则、动态配置等)的存储与推送。
    • 元数据中心。
    • 接收 Provider 上报的服务接口元数据,为 Admin 等控制台提供运维能力(如服务测试、接口文档等)
    • 作为服务发现机制的补充,提供额外的接口 / 方法级别配置信息的同步能力,相当于注册中心的额外扩展

    以上三个中心并不是运行 Dubbo 的必要条件,用户完全可以根据自身业务情况决定只启用其中一个或多个,以达到简化部署的目的。通常情况下,所有用户都会以独立的注册中心 开始 Dubbo 服务开发,而配置中心、元数据中心则会在微服务演进的过程中逐步地按需被引入进来。

    下面是 Dubbo 早期的架构,这个架构核心组件只包含注册中心,基本就是运行 Dubbo 的最简架构。而注册中心也是异步弱依赖,唯一的强依赖是 4. invoke 这一步。也就是 RPC 调用发起请求到下游的部分。

    Dubbo源码 - 图2

    Dubbo 源码 - 场景设定

    下面来手撕 Dubbo 的核心源码。为了好理解,这里讲 Dubbo 默认的 dubbo 协议使用 http 协议做说明。

    Dubbo源码 - 图3

    下面的代码想达到的效果如上图,先来说说思路:

    1. 先要有服务提供者,注册到注册中心。注册中心本质上就是服务提供者的访问地址存储的地方。只是这个 url 不一定是 http 协议的地址,本质都是应用层协议地址,并没有什么不同。
    2. 服务提供者要指明一个服务的实现类。
    3. 服务提供者开启网络通信服务,将服务暴露出去。
    4. 服务消费者找到对应的服务
    5. 服务消费者发起调用

    先来设计一个场景,要暴露的服务端如下:

    服务的接口

    Dubbo源码 - 图4

    实现类

    Dubbo源码 - 图5

    以上咱们用服务提供者端核心 5 行代码、服务消费者端核心 5 行代码来实现。

    Dubbo 源码 - 服务提供者

    五行代码

    简单来说就是注册并暴露服务。按照这个思路咱们不难得到下面的提供端代码框架。这里面共 5 行有效代码,咱们一行一行来解释。

    Dubbo源码 - 图6

    第一行

    回到主线代码第一行有效代码,封装了一个 url 对象,这个是自己写的:

    Dubbo源码 - 图7

    就是一个主机名端口的存储简单对象。

    第二行

    第二行有效代码作用是将 url 注册到远程注册中心上,咱们脑补一下注册中心的存储大概如下所示:

    Dubbo源码 - 图8

    这里咱用一个 map 来模拟注册中心,不难得到下面的代码。

    Dubbo源码 - 图9

    save 是写文件来模拟的,这块不是重点,为了完整性简单提一下。

    Dubbo源码 - 图10

    第三行

    第三行有效代码作用是指明接口对应的实现类,这个实现时也使用 map 数据结构。本质上就是一个存取。

    Dubbo源码 - 图11

    第四行

    第四行有效代码是获取协议。作为一个框架来说需要具有多协议的支持,这里做了一个简单的实现。

    Dubbo源码 - 图12

    协议的接口规定了两个动作,一个是启动时做的事情,一个是发送时做的事情。

    Dubbo源码 - 图13

    第五行

    先不着急看实现,先回到主线代码第五行。第五行就是把 url 传入后调用协议的 start。这时候咱们来看启动方法的实现:

    Dubbo源码 - 图14

    就是启动了一个 httpServer。咱们来看 httpServer.start 的具体实现。这里面就是启动了一个 tomcat。关键点是加了一个 DispatcherServlet,并对所有的请求进行拦截处理。重点我用红框标出来了

    Dubbo源码 - 图15

    这里本质上说明了 web 容器和 servlet 的核心作用。web 容器主要是负责网络通信,servlet 是 java 应用内部路由分发。咱们来看看路由分发是怎么做的:

    Dubbo源码 - 图16

    咱们来分析一下 HttpServerHandler.handler 方法是怎么实现。分三步:

    Dubbo源码 - 图17

    第一步,解析请求输入流。

    第二步,解析出要调用的接口,从本地注册缓存中获取实现类。

    第三步,利用 java 反射机制将解释出的请求参数传入实现类发起真正调用。

    以上就完成了服务暴露的整个过程。

    Dubbo 源码 - 服务消费者

    五行代码

    客户端调用的整个过程比较简单,分成两步:

    1. 通过代理找到实现类
    2. 发起调用

    Dubbo源码 - 图18

    关键逻辑就是代理如何实现:

    Dubbo源码 - 图19

    第一行

    第一行有效代码:封装 Invacation 对象,将接口名、方法名、方法参数传入。

    Dubbo源码 - 图20

    第二行

    第二行有效代码:从注册中心获取 url 列表

    第三行

    第三行代码,因为获取到的是 url 列表,怎么选择发往哪个呢?这里采用的是随机算法决定发往的地址,这也是 dubbo 默认的地址选择策略。

    Dubbo源码 - 图21

    第四行

    第四行是获取协议,在服务提供端介绍过了,直接往下。

    第五行

    第五行是通过协议将 invacation 对象发送到 url 上。

    Dubbo源码 - 图22

    看看 httpProtocal 内部是怎么实现的。

    Dubbo源码 - 图23

    内部很简单,就是调用 httpClient 把请求发出去。虽然这个 httpClient 是自己写的,但是实际上功能和开源的那个差不多。咱们简单看一下就好:

    Dubbo源码 - 图24

    总结

    在《mybatis 的本质和原理》中,我手撕了一个简易却包含 mybatis 核心的代码,来探究 mybatis 的本质原理。这一篇呢,我手撕了一个 Dubbo 的源码,是不是也没有那么难。

    再来回顾一下今天讲述的代码完整链路:

    Dubbo源码 - 图25

    服务提供者端将将接口注册到注册中心,并指明对应的实现类。通过 tomcat、netty 等实现网络通信,将服务暴露出去。内部使用 servlet 等实现路由在收到消费端请求时找到对应的实现类。

    服务消费者使用从注册中心获取 url 列表,使用随机数等算法找到一个 url,将参数、方法名当做 http 等协议的请求请求参数发起调用。

    现在大家闭上眼睛想一想,Dubbo 框架的核心原理是不是了然于胸了~

    编程一生

    因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下 “在看”,加个 “星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。

    PDCA 方法论,检查自己是否错过更新:每周三晚上 8 点左右,我都会更新文章,如果你没有收到,记得点开【编程一生】公众号找一下 ()
    https://mp.weixin.qq.com/s/wc6ic2wKPCnHy4Bxaj__Yg