0%

Dubbo原理

RPC(Remote Procedure Cll)远程过程调用是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法并得到返回结果。

备注:本文源码分析基于dubbo-2.6.4

总体架构

image-20191203150843596

请求流程

  1. 服务消费者(client 客户端)以调用本地服务的方式调用需要消费的服务
  2. 客户端存根(client stub)接收到调用请求后负责将方法、入参等信息序列化成能够进行网络传输的消息体
  3. 客户端存根(client stub)找到远程服务地址,将消息通过网络发送给服务端
  4. 服务端存根(server stub)收到消息后进行解码(反序列化)
  5. 服务端存根(server stub)根据解码结果调用本地服务进行相关处理(解码后请求转发给分发器 Dispatcher,Dispatcher 将请求派发到指定线程池)
  6. 本地服务执行具体业务逻辑将处理结果返回给服务端存根(server stub)
  7. 服务端存根(server stub)将返回结果打包成消息(序列化)并通过网络发送给消费方
  8. 客户端存根(client stub)接收到消息,进行反序列化
  9. 服务消费方得到最终结果(根据调用编号从 ConcurrentHashMap 中移除 DefaultFuture 对象并唤醒用户线程(signal/await))

Dubbo 支持同步和异步两种调用方式,其中异步调用还可以细分为有返回值和无返回值两种。无返回值时,服务消费方只管调用不关心调用结果,Dubbo 返回一个空的 RpcResult。默认为同步方式。

Dubbo 实现同步和异步调用(DubboInvoker.doInvoke())关键点在于由谁调用 ResponseFuture 的 get 方法,同步调用模式下,由框架自身调用 FeatureResponse 的 get 方法。异步调用模式下,由用户调用该方法。

源码解析

网络通信

Dubbo 默认使用 Netty 作为底层通信框架,其它可用框架包括 Mina

动态代理

负载均衡

Dubbo内置了4种负载均衡策略:

  1. RandomLoadBalance:随机负载均衡。随机的选择一个。是Dubbo的默认负载均衡策略。
  2. RoundRobinLoadBalance:轮询负载均衡。轮询选择一个。
  3. LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。
  4. ConsistentHashLoadBalance:一致性哈希负载均衡。相同参数的请求总是落在同一台机器上。

同步转异步

Dubbo 对服务消费者的调用请求进行分配,避免少数服务提供者负载过大,将负载均衡到每个服务提供者上。

Dubbo 提供了 4 种负载均衡实现:

RandomLoadBalance 基于权重随机算法(默认负载均衡算法)

LeastActiveLoadBalance 基于最少活跃调用数算法

ConsistentHashLoadBalance 基于一致性 Hash

RoundRobinLoadbalance 基于加权轮询算法

集群容错

集群 Cluster 的用途是将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者。

服务消费者只需要通过这个 Invoker 调用即可,至于具体调用哪个服务提供者以及调用失败后如何处理等问题,都交给集群模块处理。

Cluster 工作过程分为两个阶段,第一个阶段在消费者初始化期间,CLuster 实现类为消费者创建 Cluster Invoker 实例。第二阶段在消费者远程调用时,首先调用 Directory 的 list 方法列举 Invoker 列表。Directory 的用途是保存 Invoker,可简单类比为 List<Invoker>,其实现类 RegisterDirectory 是一个动态服务目录,可感知注册中心配置的变化,所持有的 Invoker 列表会随着注册中心内容的变化而变化。当 ClusterInvoker 拿到 Directory 返回的 Invoker 列表后,通过 LoadBalance 从 Invoker 列表中选择一个 Invoker。最后 ClusterInvoker 将参数传递给 Loadbalance 选出的 Invoker 方法,进行真正的远程调用。

Dubbo 提供了五种容错方案:

  1. Failover:失败自动切换,默认配置,调用失败后自动切换 Invoker 进行重试
  2. Failfast:快速失败,只进行一次调用,失败后立即抛出异常,适用于幂等操作
  3. Failsafe:失败安全,调用出现异常时,仅打印异常,不会抛出异常,适用于写入审计日志等操作
  4. Failback:失败自动恢复,调用失败后返回一个空结果给客户端,通过定时任务对失败的调用进行重传,适用于消息通知等操作
  5. Forking:并行调用多个服务提供者,运行时通过线程池创建多个线程,并发调用多个服务提供者,只要有一个服务提供者成功返回结果,doInvoker 方法立即结束运行。适用于实时性要求较高的读场景,会耗费更多资源
  6. Broadcast:逐个调用每个服务提供者,如果其中一台报错,在循环调用结束后抛出异常。适用于通知所有提供者更新缓存或日志等本地资源

降级

熔断用于调用下游服务连续失败时,开启熔断器,之后的服务不再调用下游服务,要么返回失败要么降级,直到满足熔断器关闭策略后才再次调用下游。

Dubbo 中通过对接口或方法配置 Mock 参数来设置对应的降级实现。具体实现在MockClusterInvoker类中。

降级分 no mock(无降级,直接调用)、force:direct mock(屏蔽,直接调用 mock 方法)、fail-mock(容错,原接口调用失败后走容错逻辑,可返回 null、默认值或降级服务)三种情况。

Dubbo 本身没有熔断器,需要自己实现或整合 Sentinel 或 Hystrix

优雅停机

优雅停机是指在停止应用时,执行的一系列保证应用正常关闭的操作。这些操作往往包括等待已有请求执行完成、关闭线程、关闭连接和释放资源等,优雅停机可以避免非正常关闭程序可能造成数据异常或丢失,应用异常等问题。优雅停机本质上是JVM即将关闭前执行的一些额外的处理代码。

适用场景

  • JVM主动关闭(System.exit(int)
  • JVM由于资源问题退出(OOM);
  • 应用程序接受到SIGTERMSIGINT信号。

在Dubbo中,优雅停机是默认开启的,停机等待时间为10000毫秒。可以通过配置dubbo.service.shutdown.wait来修改等待时间。

流程

Provider在接收到停机指令后

  • 从注册中心上注销所有服务;
  • 从配置中心取消监听动态配置;
  • 向所有连接的客户端发送只读事件,停止接收新请求;
  • 等待一段时间以处理已到达的请求,然后关闭请求处理线程池;
  • 断开所有客户端连接。

Consumer在接收到停机指令后

  • 拒绝新到请求,直接返回调用异常;
  • 等待当前已发送请求执行完毕,如果响应超时则强制关闭连接。

当使用容器方式运行 Dubbo 时,在容器准备退出前,可进行一系列的资源释放和清理工。

例如使用 SpringContainer时,Dubbo 的ShutdownHook线程会执行ApplicationContextstopclose方法,保证 Bean的生命周期完整。

配置的优雅停机等待时间timeout不是所有步骤等待时间的总和,而是每一个destroy执行的最大时间。例如配置等待时间为5秒,则关闭Server、关闭Client等步骤会分别等待5秒。

参考资料

  1. Dubbo 官方文档
  2. 看了这篇Dubbo RPC面试题,让天下没有难面的面试题
  3. Dubbo(三):高并发下降级与限流
  4. dubbo的Mock功能与源码实现
  5. Sentinel之熔断降级
  6. Dubbo 优雅停机
  7. 深入理解 RPC 之动态代理篇