Dubbo 是一款微服务开发框架,它提供了【RPC通信】与【服务治理】两大关键能力。
RPC远程调用技术
RPC 全称 Remote Procedure Call,是一种进程间通信方式。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。
RPC是一种抽象的概念,像常见的 RMI、WebService、Http 等是它的具体实现。
dubbo 也对 RPC 做了具体实现,制定了 dubbo协议,默认使用的就是 dubbo协议。
dubbo服务发现基本工作原理
服务发现,即消费端自动发现服务地址列表的能力,是微服务框架需要具备的关键能力,借助于自动化的服务发现,微服务之间可以在无需感知对端部署位置与 IP 地址的情况下实现通信。
实现服务发现的方式有很多种,Dubbo 提供的是一种 Client-based 的服务发现机制,通常还需要部署额外的第三方注册中心组件来协调服务发现过程,如常用的 Nacos、Zookeeper 等。
工作原理图
其中:Registry表示注册中心、Provider表示服务提供者、Consumer表示服务消费者。
流程说明
- Provider绑定指定端口并启动服务Provider连接Registry,并将{本机IP、端口、应用信息和提供服务信息}发送至Registry存储Consumer连接Registry ,并发送{应用信息、所求服务信息}至RegistryRegistry根据Consumer所求服务信息匹配对应的Provider列表发送至Consumer应用进行缓存Consumer在发起远程调用时基于缓存的Provider列表择其一发起调用Provider状态变更会实时通知Registry,再由Registry实时推送至Consumer
优势
- Consumer与Provider解偶,双方都可以横向增减节点数注册中心本身可做集群,可动态增减节点,并且任意一台宕掉后,将自动切换到另一台去中心化,双方不直接依懒注册中心,即使注册中心全部宕机短时间内也不会影响服务的调用(Consumer本地缓存Provider列表)服务提供者无状态,任意一台宕掉后,不影响使用
dubbo调用模块原理
dubbo调用模块涉及透明代理、负载均衡、容错机制、调用方式等。
调用流程:
透明代理
通过动态代理技术,屏蔽远程调用细节以提高编程友好性。
客户端在启动时,创建bean的过程中,会给接口创建代理对象。代码是在ReferenceConfig的createProxy方法中。
默认使用Javassist来实现动态代理。使用的类是JavassistProxyFactory。
编程的时候像本地调用接口一样,但实际调用时通过代理对象来操作,通过代理对象调用时可以远程调用。
容错机制
dubbo支持多种容错机制(默认Failover):
| 容错机制 | 说明 |
|---|---|
| 失败自动切换(Failover) | 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。 |
| 快速失败(Failfast) | 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 |
| 失败安全(Failsafe) | 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 |
| 失败自动恢复(Failback) | 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 |
| 并行调用(Forking) | 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。 |
| 广播调用(Broadcast) | 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。 |
| 其他 | ...... |
以 Failover 机制为例,源码是在 FailoverClusterInvoker 中,如果 retries="2",那么最多尝试调用3次。代码中就是循环3次,只要成功,立刻return。
负载均衡
Dubbo支持以下几种负载均衡策略,默认为【加权随机】:
| 策略 | 说明 |
|---|---|
| 加权随机(Random) | 按权重设置随机概率。默认权重相同。 |
| 加权轮循 (RoundRobin) | 按公约后的权重设置轮询比率,循环调用节点。默认权重相同。 |
| 加权最少活跃调用优先(LeastActive) | 活跃数越低,越优先调用,相同活跃数的进行加权随机。 |
| 加权最短响应优先(ShortestResponse) | 在最近一个滑动窗口中,响应时间越短,越优先调用。相同响应时间的进行加权随机。 |
| 一致性哈希(ConsistentHash) | 相同参数的请求总是发到同一提供者。 |
客户端启动时,会调用Protocol的refer方法,来引用服务,最终实现是在DubboProtocol的protocolBindingRefer方法中,获取服务提供者列表,缓存起来(Set
当调用时,从缓存中根据负载均衡策略,取出一个服务提供者。
代码中,进行负载均衡是在 FailoverClusterInvoker(以Failover容错策略为例)的 doInvoke 中调用的,具体实现是在 AbstractClusterInvoker 的 doSelect() 方法中。
调用方式
默认调用方式为:异步调用,同步等待结果返回。也就是“异步转同步”。
从 version 2.7.0 开始,Dubbo 的所有异步编程接口开始以 CompletableFuture 为基础。
接口返回的是一个 AsyncRpcResult 对象,这个对象是立即返回的,但是里面并没有实际结果,接口调用未结束。
之后会调用 AsyncRpcResult 的 get 方法尝试获取实际结果,内部调用了 CompletableFuture 的 waitingGet 方法。在 waitingGet 方法中,通过 while 循环实现阻塞,直到获取实际结果(result)才返回,接口调用结束。以此达到异步转同步的效果。
协议
Dubbo 支持 dubbo、rmi、hessian、http、webservice、thrift 等多种协议。默认使用"dubbo协议"。
"dubbo协议"采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数大于服务提供者机器数的情况。他不适合传送大数据量的服务,比如传文件,传视频等。
当使用"dubbo协议"时,url以"dubbo://"开头,类似如下这样:
dubbo://127.0.0.1:20880/com.xujingyi.contract.service.UserService
dubbo协议报文编码:
| magic | 2字节 | 类似java字节码文件里的魔数,用来判断是不是dubbo协议的数据包。 魔数是常量0xdabb,用于判断报文的开始。 |
|---|---|---|
| flag | 1字节 | 标志位, 一共8个地址位。 低四位用来表示消息体数据用的序列化工具的类型(默认hessian); 高四位中,第一位为1表示是request请求,第二位为1表示双向传输(即有返回response),第三位为1表示是心跳ping事件。 |
| status | 1字节 | 状态位, 设置请求响应状态,dubbo定义了一些响应的类型。 具体类型见 com.alibaba.dubbo.remoting.exchange.Response |
| invoke id | 8字节 | 消息id, long 类型。每一个请求的唯一识别id(由于采用异步通讯的方式,用来把请求request和返回的response对应上) |
| body length | 4字节 | 消息体 body 长度, int 类型,即记录Body Content有多少个字节。 |
dubbo协议编码过程:
远程调用时,假设发送的是一个java对象,但是计算机处理的时候不认java对象、字符串这种,只认字节,所以需要先对消息体进行编码。编码时需要根据协议报文进行。
解码时根据协议报文的格式,解析前16个字节,根据它来准确得获取消息体。比如通过 body length 来确定消息体的长度,防止 Netty 粘包拆包导致获取的消息体不是想要的消息体。
底层通信机制
dubbo 客户端与服务端的通信,底层通过 netty 来实现的。
服务端
dubbo服务端在启动的时候,会通过DubboProtocol中的export方法暴露服务,在其中会创建Netty服务端。具体代码是NettyServer类中的doOpen()方法:
可以看到,这是标准的 Netty 服务端的代码。
Pipeline 中添加了"dubbo协议"的编解码处理器;还有用于处理心跳检测的 IdleStateHandler,负责检测Netty服务端与客户端的连接。
客户端
dubbo客户端在启动时,会创建Netty客户端,具体代码是NettyClient类中的doOpen()方法:
Pipeline 中添加了"dubbo协议"的编解码处理器,还有用于处理心跳检测的 IdleStateHandler。
客户端调用接口的时候,最终会调用到dubbo包中NettyChannel的send()方法来进行远程调用。而NettyChannel中的channel对象,就是Netty客户端的NioSocketChannel,通过NioSocketChannel来跟netty服务端进行通信。



