RPC(Remote Procedure Cll)远程过程调用是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法并得到返回结果。
备注:本文源码分析基于dubbo-2.6.4
总体架构
请求流程
- 服务消费者(client 客户端)以调用本地服务的方式调用需要消费的服务
- 客户端存根(client stub)接收到调用请求后负责将方法、入参等信息序列化成能够进行网络传输的消息体
- 客户端存根(client stub)找到远程服务地址,将消息通过网络发送给服务端
- 服务端存根(server stub)收到消息后进行解码(反序列化)
- 服务端存根(server stub)根据解码结果调用本地服务进行相关处理(解码后请求转发给分发器 Dispatcher,Dispatcher 将请求派发到指定线程池)
- 本地服务执行具体业务逻辑将处理结果返回给服务端存根(server stub)
- 服务端存根(server stub)将返回结果打包成消息(序列化)并通过网络发送给消费方
- 客户端存根(client stub)接收到消息,进行反序列化
- 服务消费方得到最终结果(根据调用编号从 ConcurrentHashMap 中移除 DefaultFuture 对象并唤醒用户线程(signal/await))
Dubbo 支持同步和异步两种调用方式,其中异步调用还可以细分为有返回值和无返回值两种。无返回值时,服务消费方只管调用不关心调用结果,Dubbo 返回一个空的 RpcResult。默认为同步方式。
Dubbo 实现同步和异步调用(DubboInvoker.doInvoke())关键点在于由谁调用 ResponseFuture 的 get 方法,同步调用模式下,由框架自身调用 FeatureResponse 的 get 方法。异步调用模式下,由用户调用该方法。
源码解析
网络通信
Dubbo 默认使用 Netty 作为底层通信框架,其它可用框架包括 Mina
动态代理
负载均衡
Dubbo内置了4种负载均衡策略:
- RandomLoadBalance:随机负载均衡。随机的选择一个。是Dubbo的默认负载均衡策略。
- RoundRobinLoadBalance:轮询负载均衡。轮询选择一个。
- LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。
- 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 提供了五种容错方案:
- Failover:失败自动切换,默认配置,调用失败后自动切换 Invoker 进行重试
- Failfast:快速失败,只进行一次调用,失败后立即抛出异常,适用于幂等操作
- Failsafe:失败安全,调用出现异常时,仅打印异常,不会抛出异常,适用于写入审计日志等操作
- Failback:失败自动恢复,调用失败后返回一个空结果给客户端,通过定时任务对失败的调用进行重传,适用于消息通知等操作
- Forking:并行调用多个服务提供者,运行时通过线程池创建多个线程,并发调用多个服务提供者,只要有一个服务提供者成功返回结果,doInvoker 方法立即结束运行。适用于实时性要求较高的读场景,会耗费更多资源
- Broadcast:逐个调用每个服务提供者,如果其中一台报错,在循环调用结束后抛出异常。适用于通知所有提供者更新缓存或日志等本地资源
降级
熔断用于调用下游服务连续失败时,开启熔断器,之后的服务不再调用下游服务,要么返回失败要么降级,直到满足熔断器关闭策略后才再次调用下游。
Dubbo 中通过对接口或方法配置 Mock 参数来设置对应的降级实现。具体实现在MockClusterInvoker类中。
降级分 no mock(无降级,直接调用)、force:direct mock(屏蔽,直接调用 mock 方法)、fail-mock(容错,原接口调用失败后走容错逻辑,可返回 null、默认值或降级服务)三种情况。
Dubbo 本身没有熔断器,需要自己实现或整合 Sentinel 或 Hystrix
优雅停机
优雅停机是指在停止应用时,执行的一系列保证应用正常关闭的操作。这些操作往往包括等待已有请求执行完成、关闭线程、关闭连接和释放资源等,优雅停机可以避免非正常关闭程序可能造成数据异常或丢失,应用异常等问题。优雅停机本质上是JVM即将关闭前执行的一些额外的处理代码。
适用场景
- JVM主动关闭(
System.exit(int)
; - JVM由于资源问题退出(
OOM
); - 应用程序接受到
SIGTERM
或SIGINT
信号。
在Dubbo中,优雅停机是默认开启的,停机等待时间为10000毫秒。可以通过配置dubbo.service.shutdown.wait
来修改等待时间。
流程
Provider在接收到停机指令后
- 从注册中心上注销所有服务;
- 从配置中心取消监听动态配置;
- 向所有连接的客户端发送只读事件,停止接收新请求;
- 等待一段时间以处理已到达的请求,然后关闭请求处理线程池;
- 断开所有客户端连接。
Consumer在接收到停机指令后
- 拒绝新到请求,直接返回调用异常;
- 等待当前已发送请求执行完毕,如果响应超时则强制关闭连接。
当使用容器方式运行 Dubbo 时,在容器准备退出前,可进行一系列的资源释放和清理工。
例如使用 SpringContainer时,Dubbo 的ShutdownHook线程会执行ApplicationContext
的stop
和close
方法,保证 Bean的生命周期完整。
配置的优雅停机等待时间timeout
不是所有步骤等待时间的总和,而是每一个destroy
执行的最大时间。例如配置等待时间为5秒,则关闭Server、关闭Client等步骤会分别等待5秒。