1. 同步调用

同步服务调用是最常用的一种服务调用方式,它的工作原理和使用都非常简单,分布式服务框架默认都需要支持这种调用形式。

它的工作原理如下:客户端发起远程服务调用请求,用户线程完成消息序列化之后,将消息投递到通信框架,然后同步阻塞,等待通信线程发送请求并接收到应答之后,唤醒同步等待的用户线程,用户线程获取到应答之后返回。

它的工作原理图如图:

  1. 消费者调用服务端发布的接口,接口调用由分布式服务框架包装成动态代理,发起远程服务调用。
  2. 消费者线程调用通信框架的消息发送接口之后,直接或者间接调用 wait()方法,同步阻塞等待应答。
  3. 通信框架的 I/O线程通过网络将请求消息发送给服务端。
  4. 服务端返回应答消息给消费者,由通信框架负责应答消息的反序列化。
  5. I/O线程获取到应答消息之后,根据消息上下文找到之前同步阻塞的业务线程, notify()阻塞的业务线程,返回应答给消费者,完成服务调用。

为了防止服务端长时间不返回应答消息导致客户端用户线程被挂死,用户线程等待的时候需要设置超时时间,这个超时时间与服务端或者客户端配置的超时时间对应。

2. 异步服务调用

基于 JDK的 Future机制,可以非常方便地实现异步服务调用。 在实际项目中,往往会扩展 JDK的 Future,提供 Future-Listener机制,它支持主动获取和被动异步回调通知两种模式,适用于不同的业务场景。
在实际项目中,往往会扩展 JDK的 Future,提供 Future-Listener机制,它支持主动获取和被动异步回调通知两种模式,适用于不同的业务场景。

2.1 添加Listener的异步服务调用

异步服务调用的工作原理如图 :

异步服务调用的工作流程如下:
1)消费者调用服务端发布的接口,接口调用由分布式服务框架包装成动态代理,发起远程服务调用。
2)通信框架异步发送请求消息,如果没有发生 I/O异常,返回。
3)请求消息发送成功后, I/O线程构造 Future对象,设置到 RPC上下文中。
4)用户线程通过 RPC上下文获取 Future对象。
5)构造 Listener对象,将其添加到 Future中,用于服务端应答异步回调通知。
6)用户线程返回,不阻塞等待应答。
7)服务端返回应答消息,通信框架负责反序列化等。 8)I/O线程将应答设置到 Future对象的操作结果中。
9)Future对象扫描注册的监听器列表,循环调用监听器的operationComplete方法,将结果通知给监听器,监听器获取到结果之后,继续后续业务逻辑的执行,异步服务调用结束。

2.2 无Listener的异步服务调用

另外一种异步服务调用形式,就是不添加 Listener,用户连续发起 N次服务调用,然后依次从 RPC上下文中获取 Future对象,最终再主动 get结果,业务线程阻塞,相比于老的同步服务调用,它的阻塞时间更短,其工作原理如图。

2.3 异步服务调用的优点

  1. 化串行为并行,提升服务调用效率,减少业务线程阻塞时间。
  2. 化同步为异步,避免业务线程阻塞。

串行到并行的优化原理如图:由于每次服务调用都是同步阻塞,三个服务调用总耗时为T = T1 + T2 + T3

采用异步服务调用之后的优化效果,如图:

采用异步服务调用模式,最后调用三个服务异步操作结果 Future的 get方法同步等待应答,它的总执行时间 T = Max(T1, T2, T3),相比于同步服务调用,性能提升效果非常明显。

3. 并行服务调用

串行服务调用比较简单,但在一些业务场景中,需要采用并行服务调用来降低 E2E的时延:

  1. 多个服务之间逻辑上不存在互相依赖关系,执行先后顺序没有严格的要求,逻辑上可以被并行执行。
  2. 长流程业务,调用多个服务,对时延比较敏感,其中有部分服务逻辑上无上下文关联,可以被并行调用。

并行服务调用的目标主要有两个:
1)降低业务 E2E时延。
2)提升整个系统的吞吐量。我们以手游购买道具流程为例,对并行服务调用进行说明,如图。

实现并行服务调用的几种技术方案:

  1. JDK 7的 Fork/Join,可以实现子任务的并行执行和结果汇聚(推荐)
  2. BPM的 Parallel Gateway
  3. 批量串行服务调用

关于批量串行服务调用的流程如下:

1)服务框架提供批量服务调用接口供消费者使用,它的定义样例如下:

2)平台的并行服务调用器创建并行 Future,缓存批量服务调用上下文信息。
3)并行服务调用器循环调用普通的 Invoker,通过循环的方式执行单个服务调用,获取到单个服务的 Future之后设置到 Parallel Future中。
4)返回 Parallel Future给消费者。
5)普通 Invoker调用通信框架的消息发送接口,发起远程服务调用。
6)服务端返回应答,通信框架对报文做反序列化,转换成业务对象更新 Parallel Future的结果列表。
7)消费者调用 Parallel Future的 get(timeout)方法, 同步阻塞,等待所有结果全部返回。
8) Parallel Future通过对结果集进行判断,看所有服务调用是否都已经完成(包括成功、失败和异常)。
9)所有批量服务调用结果都已经返回, Notify消费者线程,消费者获取到结果列表,完成批量服务调用,流程继续执行。

通过批量服务调用 + Future机制,我们实现了并行服务调用,而且没有创建新的线程,用户不用担心依赖线程上下文的功能出异常。

4. 泛化调用

泛化调用通常包含两种模式:泛化引用和泛化实现。泛化引用主要用于客户端没有 API接口及数据模型的场景,参数及返回值中的所有 POJO均用 Map表示,通常用于框架集成,比如实现一个通用的服务测试框架。泛化实现主要用于服务器端没有 API接口及数据模型的场景,参数及返回值中的所有 POJO均用 Map表示,通常用于框架集成,比如实现一个通用的远程服务Mock框架。泛化调用的设计要点如下。
1)分布式服务框架提供泛化接口,供服务提供者实现和消费者引用,它的参考定义如下:

2)消费者如果引用泛化接口,则直接将请求参数转换成 Map,应答消息也自动转换成 Map。
3)服务提供者如果使用泛化实现发布服务,则自动将请求参数转换成 Map,调用GenService的泛化实现类,应答消息自动包装成 Map返回。
泛化调用由于比较灵活,没有服务契约,因此在实际项目中慎用,它通常用于测试集成、系统上线之后的回声测试等。

参考资料;

  1. 服务框架多形式的服务调用:同步、异步、并用、泛化