微服务框架服务调用与容错
本章主要介绍服务调用的方式:同步调用、异步调用、并行调用、泛化调用等。
一、服务调用概述
简单介绍了RPC框架的调用方式:同步调用和异步调用。服务调用方式按照不同的维度区分,有不同的命名方法,抛开技术不谈,我们还可以把服务调用分为3种:OneWay模式(单向操作)、请求应答模式、回调模式(Call Back)。
1. OneWay模式(单向操作)
简单来说,单向操作没有返回值,客户端只管调用,不管结果,例如消息通知,如图8-1所示。
2. 请求应答模式
请求应答模式是默认的操作模式。这与经典的C/S编程类似,客户端发送请求,阻塞客户端进程,服务端返回操作结果,如图8-2所示。
3. 回调模式
服务1调用服务2,并立即收到响应。服务2处理服务1的业务请求,处理完成后调用服务1提供的回调接口,具体如图8-3所示。
二、服务调用方式
1、同步服务调用
同步调用是分布式微服务架构中最为常用的一种调用方式。客户端调用服务端方法,等待直到服务端返回结果或者超时,再继续自己的操作。同步调用结合RPC框架的工作原理如图8-4所示。
(1)服务A以本地调用方式调用服务B(依赖服务B接口,以接口的方式调用)。
(2)客户端存根(Client Stub)接收到调用请求后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制)。
(3)客户端通过Sockets将消息发送到服务端。
(4)服务端存根(Server Stub)收到消息后对消息进行解码(将消息对象反序列化)。
(5)服务端存根(Server Stub)根据解码结果调用本地的服务(利用反射原理)。
(6)服务B执行并将结果返回给服务端存根(Server Stub)。
(7)服务端存根(Server Stub)将返回结果打包成消息(将结果消息对象序列化)。
(8)服务端(Server)通过Sockets将消息发送到客户端。
(9)客户端存根(Client Stub)接收到结果消息,并进行解码(将结果消息反序列化)。
(10)服务A得到最终结果。
需要注意的是,服务A为了防止服务端长时间不返回应答消息导致线程被挂死,服务A需要设置超时时间。
最后,我们来看简单的同步调用伪代码,具体代码如下所示:
2、异步服务调用
异步调用:客户端调用服务端方法,可不再等待服务端返回,直接继续自己的操作。异步服务调用结合RPC框架的原理如图8-5所示。
(1)服务A以本地调用方式调用服务B(依赖服务B接口,以接口的方式调用)。
(2)RPC框架请求服务B。
(3)RPC同时会设置Future对象,设置到RPC上下文中。
(4)服务A获取RPC上下文中的Future对象。
(5)RPC框架会返回Future对象。
(6)服务A会主动获取Get请求的结果,此时是同步阻塞。
(7)服务B返回请求结果给RPC框架。
(8)RPC框架获取到服务B返回结果后,将结果设置到Future对象中,同时唤醒服务A的阻塞线程。
相对于同步调用的阻塞模式,图8-5异步调用模式步骤6的阻塞时间更短。我们再来看一段基于Future的异步调用伪代码:
通过Future可以并发发出N个请求,然后等待最慢的一个返回,总响应时间为最慢的一个请求返回的时间。
在实际项目中,往往会扩展JDK的Future,提供Future-Listener机制。我们以Netty的Future接口定义为例进行介绍,结合RPC框架,具体工作原理如图8-6所示。
(1)服务A以本地调用方式调用服务B(依赖服务B接口,以接口的方式调用)。
(2)RPC框架请求服务B。
(3)RPC同时会设置Future对象,设置到RPC上下文中。
(4)服务A获取RPC上下文中的Future对象。
(5)构造Listener对象,将其设置到Future对象中。
(6)服务A线程返回,不阻塞。
(7)服务B返回请求结果给RPC框架。
(8)RPC框架获取到服务B返回结果后,将结果设置到Future对象中。
(9)Future对象扫描注册监听器列表,循环调用监听器的operationComplete方法,将结果通知给监听器,监听器获取结果后,继续后续的业务逻辑的执行,异步调用结束。
我们再来看一段基于Future-Listener的异步调用伪代码:
3、并行服务调用
随着业务的增加,服务越来越多,服务之间的调用也越来越复杂,原本一个服务中一次请求就可以完成的工作,现在可能被分散在多个服务中,一次请求需要多个服务的响应。这样就会放大RPC调用延迟带来的副作用,影响系统的高性能需求。
例如,一个RPC接口中需要依赖另外3个RPC服务,各RPC服务的响应时间分别是20ms、10ms、10ms,那么这个接口的对外系统依赖耗时40ms。如果接口依赖越多,响应时间就会越长。
对此,需要在业务范围内进行性能优化,优化思路有两种:
(1)对于RPC接口调用,不需要关心接口的返回值,可以采用异步RPC调用。
(2)如果依赖RPC接口返回值,并且连续调用的多个RPC接口之间没有依赖关系,执行先后顺序没有严格的要求,那么可以采用并行化处理。
并行服务调用原理:一次同时发起多个服务调用,先做流程的Fork,再利用Future等主动等待获取结果,进行结果聚合(Join)。
在JDK 8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法。
4、泛化调用
分布式服务框架可以通过提供泛化接口供服务提供者实现和服务消费者引用,例如:
上述实例中,ProductService实现泛化接口GenericService,并实现invoke方法,在main函数中调用泛化接口。泛化调用通常在测试中被用到,比如Mock框架等。