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

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

服务框架支持多种形式的服务调用,我们对下面这几种服务调用的原理和设计进行讲解。

同步服务调用

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

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

它的工作原理图如图。

1)消费者调用服务端发布的接口,接口调用由分布式服务框架包装成动态代理,发起远程服务调用。

2)消费者线程调用通信框架的消息发送接口之后,直接或者间接调用 wait()方法,同步阻塞等待应答。

3)通信框架的 I/O线程通过网络将请求消息发送给服务端。

4)服务端返回应答消息给消费者,由通信框架负责应答消息的反序列化。 5)I/O线程获取到应答消息之后,根据消息上下文找到之前同步阻塞的业务线程, notify()阻塞的业务线程,返回应答给消费者,完成服务调用。

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

异步服务调用

基于 JDK的 Future机制,可以非常方便地实现异步服务调用, JDK的 Future接口定义如图 。


JDK Future Doc

JDK原生的 Future主要用于异步操作,它代表了异步操作的执行结果,用户可以通过调用它的 get方法获取结果。如果当前操作没有执行完, get操作将阻塞调用线程。

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

以Netty的 Future接口定义为例,新增了监听器管理接口,监听器主要用于异步通知回调,它的接口定义如图 。


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

异步服务调用的工作流程如下:

1)消费者调用服务端发布的接口,接口调用由分布式服务框架包装成动态代理,发起远程服务调用。

2)通信框架异步发送请求消息,如果没有发生 I/O异常,返回。

3)请求消息发送成功后, I/O线程构造 Future对象,设置到 RPC上下文中。

4)用户线程通过 RPC上下文获取 Future对象。

5)构造 Listener对象,将其添加到 Future中,用于服务端应答异步回调通知。

6)用户线程返回,不阻塞等待应答。

7)服务端返回应答消息,通信框架负责反序列化等。 8)I/O线程将应答设置到 Future对象的操作结果中。

9)Future对象扫描注册的监听器列表,循环调用监听器的operationComplete方法,将结果通知给监听器,监听器获取到结果之后,继续后续业务逻辑的执行,异步服务调用结束。

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

异步服务调用的代码示例如下:

假如 xxxService1和 xxxService2发布成异步服务,则调用xxxMethod之后当前业务线程不阻塞,立即返回 null。用户不能直接使用它的返回值,而是通过当前线程上下文RPCContext获取异步操作结果 Future。获取到 Future之后继续发起其他异步服务调用,然后获取另一个 Future……昀后,通过 Future的 get方法集中获取结果。无论有多少个 Future,采用此种方式用户线程昀长阻塞时间为耗时昀长的 Future,即 T = Max t(future1....N)。如果采用同步服务调用,用户线程的阻塞时间T = t(future1) + t(future2) + ……+ t(futureN)。

异步服务调用相比于同步服务调用有两个优点:

◎化串行为并行,提升服务调用效率,减少业务线程阻塞时间。

◎化同步为异步,避免业务线程阻塞。

串行到并行的优化原理如图 。

由于每次服务调用都是同步阻塞,三个服务调用总耗时为T = T1 + T2 + T3。下面我们看下采用异步服务调用之后的优化效果,如图。

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

第二种基于 Future-Listener的纯异步服务调用,它的代码示例如下:

xxxService1.xxxMethod(Req);

Future f1 =RpcContext.getContext().getFuture();

Listener l = new xxxListener();f1.addListener(l); ......后续代码省略 }

基于 Future-Listener的异步服务调用相比于 Future-get模式更好,但是在实际使用中有一定的局限性,具体的使用限制留给读者自己思考。

并行服务调用

在大多数业务应用中,服务总是被串行地调用和执行,例如 A调用 B服务,B服务调用C服务,昀后形成一个串行的服务调用链: A→B服务→C服务→……

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

◎多个服务之间逻辑上不存在互相依赖关系,执行先后顺序没有严格的要求,逻辑上可以被并行执行。

◎长流程业务,调用多个服务,对时延比较敏感,其中有部分服务逻辑上无上下文关联,可以被并行调用。

并行服务调用的目标主要有两个:

1)降低业务 E2E时延。

2)提升整个系统的吞吐量。我们以手游购买道具流程为例,对并行服务调用进行说明,如图。

在购买道具时,三个鉴权流程实际可以并行执行,昀终执行结果做个 Join即可。如果采用传统的串行服务调用,耗时将是三个鉴权服务时延之和,显然是没有必要的。计费之后的通知类服务亦如此(注意:通知服务也可以使用MQ做订阅/发布),单个服务的串行调用会导致购买道具时延比较长,影响游戏玩家的体验。

要解决串行调用效率低的问题,有两个解决对策:

◎异步服务调用。

◎并行服务调用。在上一节中已经对异步服务调用进行了讲解,下面我们对并行服务调用进行详细介绍。并行服务调用的原理:一次同时发起多个服务调用,先做流程的 Fork,再利用 Future

等主动等待获取结果,进行结果汇聚( Join)。实现并行服务调用的几种技术方案:

◎ JDK 7的 Fork/Join,可以实现子任务的并行执行和结果汇聚。

◎ BPM的 Parallel Gateway。

◎批量串行服务调用。

JDK7的 Fork/Join底层会开启多个线程来分解任务,在服务框架中使用会导致依赖线程上下文传递的变量丢失、线程膨胀不可控等问题,因此在并行服务调用时不适合使用 JDK的 Fork/Join并行执行框架。

BPM流程引擎支持并行流程(子流程)调用,它的执行示意图如图。

 Parallel Gateway(并行网关)能在一个流程里用来对并发建模。在一个流程模型里引入并发昀直接的网关就是并行网关( Parallel Gateway),它允许 Fork执行多个路径,或者 Join多个执行的到达路径。

并行网关的功能基于即将到达的和即将离开的流程顺序流。

◎ Fork:所有即将离开的顺序流将以并行方式,为每个顺序流程建立一个并发执行器。

◎ Join:所有的并发执行到达并行网关,在网关里面等待直到每个来到的顺序流的执行到达,条件满足后流程继续通过合并网关。

从技术上看,不同的 BPM流程引擎具体实现细节也不同,但大多数都支持:通过创建子线程的方式实现并行调用、通过批量调用的方式实现伪异步并行调用。对于服务框架而言,BPM Parallel Gateway的功能可以满足需求,但是为了并行服务调用引入 BPM流程引擎显然是得不偿失,我们可以参考 Parallel Gateway的伪异步并行调用来实现服务框架的并行服务调用。

下面我们对批量串行服务调用实现并行服务调用的原理进行讲解,如图。

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机制,我们实现了并行服务调用,而且没有创建新的线程,用户不用担心依赖线程上下文的功能出异常。该方案唯一的缺点就是用户需要调用平台提供的并行服务调用接口,这个会导致 API层面的依赖,对于努力构建零依赖的服务框架而言不是昀优的选择。但事实上完全的零依赖是不存在的,即便 100% XML配置也是一种配置依赖,所以在设计过程中要能够识别并抓主要矛盾点,做到有所舍,否则设计工作将步履维艰。

泛化调用

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

1)分布式服务框架提供泛化接口,供服务提供者实现和消费者引用,它的参考定义如下:

2)消费者如果引用泛化接口,则直接将请求参数转换成 Map,应答消息也自动转换成 Map。

3)服务提供者如果使用泛化实现发布服务,则自动将请求参数转换成 Map,调用GenService的泛化实现类,应答消息自动包装成 Map返回。

泛化调用由于比较灵活,没有服务契约,因此在实际项目中慎用,它通常用于测试集成、系统上线之后的回声测试等。


转载地址:http://mp.weixin.qq.com/s?__biz=MjM5NTk0NjMwOQ==&mid=401933719&idx=1&sn=dadc10aaaf7b308494b7db63a551c15c&scene=25&srcid=0104CmhNfIXHYMaOgYMfFxvc&from=timeline&isappinstalled=0#wechat_redirect
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值