深入浅出gRPC
李林锋
《Netty 权威指南》、《分布式服务框架原理与实践》作者。
立即订阅
11236 人已学习
课程目录
已更新 6 讲 / 共 6 讲
01 | gRPC 入门及服务端创建和调用原理
02 | 客户端创建和调用原理
03 | gRPC 线程模型分析
04 | gRPC 服务调用原理
05 | gRPC 安全性设计
06 | gRPC 序列化机制
深入浅出gRPC
登录|注册

04 | gRPC 服务调用原理

李林锋 2018-03-15

1. 常用的服务调用方式

无论是 RPC 框架,还是当前比较流行的微服务框架,通常都会提供多种服务调用方式,以满足不同业务场景的需求,比较常用的服务调用方式如下:
同步服务调用:最常用的服务调用方式,开发比较简单,比较符合编程人员的习惯,代码相对容易维护些;
并行服务调用:对于无上下文依赖的多个服务,可以一次并行发起多个调用,这样可以有效降低服务调用的时延;
异步服务调用:客户端发起服务调用之后,不同步等待响应,而是注册监听器或者回调函数,待接收到响应之后发起异步回调,驱动业务流程继续执行,比较常用的有 Reactive 响应式编程和 JDK 的 Future-Listener 回调机制。
下面我们分别对上述几种服务调用方式进行讲解。

1.1 同步服务调用

同步服务调用是最常用的一种服务调用方式,它的工作原理和使用都非常简单,RPC/ 微服务框架默认都支持该调用形式。
同步服务调用的工作原理如下:客户端发起 RPC 调用,将请求消息路由到 I/O 线程,无论 I/O 线程是同步还是异步发送消息,发起调用的业务线程都会同步阻塞,等待服务端的应答,由 I/O 线程唤醒同步等待的业务线程,获取应答,然后业务流程继续执行。它的工作原理图如下所示:
第 1 步,消费者调用服务端发布的接口,接口调用由服务框架包装成动态代理,发起远程服务调用。
第 2 步,通信框架的 I/O 线程通过网络将请求消息发送给服务端。
第 3 步,消费者业务线程调用通信框架的消息发送接口之后,直接或者间接调用 wait() 方法,同步阻塞等待应答。(备注:与步骤 2 无严格的顺序先后关闭,不同框架实现策略不同)
第 4 步,服务端返回应答消息给消费者,由通信框架负责应答消息的反序列化。
第 5 步,I/O 线程获取到应答消息之后,根据消息上下文找到之前同步阻塞的业务线程,notify() 阻塞的业务线程,返回应答给消费者,完成服务调用。
同步服务调用会阻塞调用方的业务线程,为了防止服务端长时间不返回应答消息导致客户端用户线程被挂死,业务线程等待的时候需要设置超时时间,超时时间的取值需要综合考虑业务端到端的时延控制目标,以及自身的可靠性,超时时间不宜设置过大或者过小, 通常在几百毫秒到几秒之间。

1.2 并行服务调用

在大多数业务应用中,服务总是被串行的调用和执行,例如 A 业务调用 B 服务,B 服务又调用 C 服务,最后形成一个串行的服务调用链:A 业务 — B 服务 — C 服务…
串行服务调用代码比较简单,便于开发和维护,但在一些时延敏感型的业务场景中,需要采用并行服务调用来降低时延,比较典型的场景如下:
多个服务之间逻辑上不存在上下文依赖关系,执行先后顺序没有严格的要求,逻辑上可以被并行执行;
长流程业务,调用多个服务,对时延比较敏感,其中有部分服务逻辑上无上下文关联,可以被并行调用。
并行服务调用的主要目标有两个:
降低业务 E2E 时延。
提升整个系统的吞吐量。
以游戏业务中购买道具流程为例,对并行服务调用的价值进行说明:
在购买道具时,三个鉴权流程实际可以并行执行,最终执行结果做个 Join 即可。如果采用传统的串行服务调用,耗时将是三个鉴权服务时延之和,显然是没有必要的。
计费之后的通知类服务亦如此(注意:通知服务也可以使用 MQ 做订阅 / 发布),单个服务的串行调用会导致购买道具时延比较长,影响游戏玩家的体验。
要解决串行调用效率低的问题,有两个解决对策:
并行服务调用,一次 I/O 操作,可以发起批量调用,然后同步等待响应;
异步服务调用,在同一个业务线程中异步执行多个服务调用,不阻塞业务线程。
采用并行服务调用的伪代码示例:
ParallelFuture future = ParallelService.invoke(serviceName [], methodName[], args []);
List<Object> results = future.get(timeout);//同步阻塞式获取批量服务调用的响应列表
采用并行服务调用之后,它的总耗时为并行服务调用中耗时最大的服务的执行时间,即 T = Max(T(服务 A),T(服务 B),T(服务 C)),如果采用同步串行服务调用,则总耗时为并行调用的各个服务耗时之和,即:T = T(服务 A) + T(服务 B) + T(服务 C)。服务调用越多,并行服务调用的优势越明显。
并行服务调用的一种实现策略如下所示:
第 1 步,服务框架提供批量服务调用接口供消费者使用,它的定义样例如下:ParallelService.invoke(serviceName [], methodName[], args []);
第 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 机制,可以实现并行服务调用,由于在调用过程中没有创建新的线程,用户就不需要担心依赖线程上下文的功能发生异常。

1.3 异步服务调用

JDK 原生的 Future 主要用于异步操作,它代表了异步操作的执行结果,用户可以通过调用它的 get 方法获取结果。如果当前操作没有执行完,get 操作将阻塞调用线程:
在实际项目中,往往会扩展 JDK 的 Future,提供 Future-Listener 机制,它支持主动获取和被动异步回调通知两种模式,适用于不同的业务场景。
异步服务调用的工作原理如下:
异步服务调用的工作流程如下:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入浅出gRPC》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(5)

  • H.L.
    是java的啊,没有go语言版本的源码讲解吗?
    2018-11-07
    3
  • wanglong
    我一个前端竟也看到了现在,全java,js多简单呐,用js写吧
    2018-10-31
    1
  • 张云潮
    请问老师如何实现grpc服务器的高可用性,以及如何构建grpc服务器集群。

    作者回复: 这个需要自己扩展实现,主要包括:1.集群路由策略扩展 2. 失败重试机制 3. 集群调度管理 4.进程监控等

    2018-05-09
    1
  • 王尚秀
    老师,想请教一下,系列文章提到的grpc原理,用法对于c++也适用吗?比如同步,异步调用?
    2019-07-06
  • tjstqq
    tls握手过程按作者讲的能保证安全性吗?
    2019-05-07
收起评论
5
返回
顶部