找回密码
 立即注册
查看: 299|回复: 0

深入浅出gRPC

[复制链接]
发表于 2022-4-17 20:05 | 显示全部楼层 |阅读模式
一、gRPC介绍

gRPC 是在 HTTP/2 之上实现的 RPC 框架,HTTP/2 是第 7 层(应用层)协议,它运行在 TCP(第 4 层 - 传输层)协议之上,相比于传统的 REST/JSON 机制有诸多的优点:
    基于 HTTP/2 之上的二进制协议(Protobuf 序列化机制);一个连接上可以多路复用,并发处理多个请求和响应;多种语言的类库实现;服务定义文件和自动代码生成(.proto 文件和 Protobuf 编译工具)。

此外,gRPC 还提供了很多扩展点,用于对框架进行功能定制和扩展,例如,通过开放负载均衡接口可以无缝的与第三方组件进行集成对接(Zookeeper、域名解析服务、SLB 服务等)。
二、gRPC服务调用原理

一个完整的 RPC 调用流程示例如下:

RPC 请求消息发送流程


gRPC 默认基于 Netty HTTP/2 + PB 进行 RPC 调用,请求消息发送流程如下所示:

image

RPC 响应接收和处理流程


gRPC 客户端响应消息的接收入口是 NettyClientHandler,它的处理流程如下所示:

并行调用和异步调用


要解决串行调用效率低的问题,有两个解决对策:
    并行服务调用,一次 I/O 操作,可以发起批量调用,然后同步等待响应;异步服务调用,在同一个业务线程中异步执行多个服务调用,不阻塞业务线程。

采用并行服务调用的伪代码示例:
ParallelFuture future = ParallelService.invoke(serviceName [], methodName[], args []);List<Object> results = future.get(timeout);// 同步阻塞式获取批量服务调用的响应列表
并行服务调用的一种实现策略如下所示:

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

异步服务调用相比于同步服务调用有两个优点:
    化串行为并行,提升服务调用效率,减少业务线程阻塞时间。化同步为异步,避免业务线程阻塞。

基于 Future-Listener 的纯异步服务调用代码示例如下:
xxxService1.xxxMethod(Req);Future f1 = RpcContext.getContext().getFuture();Listener l = new xxxListener();f1.addListener(l);class xxxListener{public void operationComplete(F future){ // 判断是否执行成功,执行后续业务流程}     }理解误区

1、异步服务就是异步吗?


实际上,通信框架基于 NIO 实现,并不意味着服务框架就支持异步服务调用了,两者本质上不是同一个层面的事情。在 RPC/ 微服务框架中,引入 NIO 带来的好处是显而易见的:
    所有的 I/O 操作都是非阻塞的,避免有限的 I/O 线程因为网络、对方处理慢等原因被阻塞;多路复用的 Reactor 线程模型:基于 Linux 的 epoll 和 Selector,一个 I/O 线程可以并行处理成百上千条链路,解决了传统同步 I/O 通信线程膨胀的问题。

NIO 只解决了通信层面的异步问题,跟服务调用的异步没有必然关系,也就是说,即便采用传统的 BIO 通信,依然可以实现异步服务调用,只不过通信效率和可靠性比较差而已。

对异步服务调用和通信框架的关系进行说明:


用户发起远程服务调用之后,经历层层业务逻辑处理、消息编码,最终序列化后的消息会被放入到通信框架的消息队列中。业务线程可以选择同步等待、也可以选择直接返回,通过消息队列的方式实现业务层和通信层的分离是比较成熟、典型的做法,目前主流的 RPC 框架或者 Web 服务器很少直接使用业务线程进行网络读写。

通过上图可以看出,采用 NIO 还是 BIO 对上层的业务是不可见的,双方的汇聚点就是消息队列,在 Java 实现中它通常就是个 Queue。业务线程将消息放入到发送队列中,可以选择主动等待或者立即返回,跟通信框架是否是 NIO 没有任何关系。因此不能认为 I/O 异步就代表服务调用也是异步的。
2、异步服务调用性能肯定更高吗?


对于 I/O 密集型,资源不是瓶颈,大部分时间都在同步等应答的场景,异步服务调用会带来巨大的吞吐量提升,资源使用率也可以提高,更加充分的利用硬件资源提升性能。

另外,对于时延不稳定的接口,例如依赖第三方服务的响应速度、数据库操作类等,通常异步服务调用也会带来性能提升。

但是,如果接口调用时延本身都非常小(例如毫秒级),内存计算型,不依赖第三方服务,内部也没有 I/O 操作,则异步服务调用并不会提升性能。能否提升性能,主要取决于业务的应用场景。
普通 RPC 调用


普通的 RPC 调用提供了三种实现方式:
    同步阻塞式服务调用,通常实现类是 xxxBlockingStub(基于 proto 定义生成)。异步非阻塞调用,基于 Future-Listener 机制,通常实现类是 xxxFutureStub。异步非阻塞调用,基于 Reactive 的响应式编程模式,通常实现类是 xxxStub。
Streaming 模式服务调用


gRPC 服务调用支持同步和异步方式,同时也支持普通的 RPC 和 streaming 模式,可以最大程度满足业务的需求。
对于 streaming 模式,可以充分利用 HTTP/2.0 协议的多路复用功能,实现在一条 HTTP 链路上并行双向传输数据,有效的解决了 HTTP/1.X 的数据单向传输问题,在大幅减少 HTTP 连接的情况下,充分利用单条链路的性能,可以媲美传统的 RPC 私有长连接协议:更少的链路、更高的性能:

gRPC 的网络 I/O 通信基于 Netty 构建,服务调用底层统一使用异步方式,同步调用是在异步的基础上做了上层封装。因此,gRPC 的异步化是比较彻底的,对于提升 I/O 密集型业务的吞吐量和可靠性有很大的帮助。
三、gRPC线程模型

影响 RPC 框架性能的三个核心要素如下:
    I/O 模型:用什么样的通道将数据发送给对方,BIO、NIO 或者 AIO,IO 模型在很大程度上决定了框架的性能;协议:采用什么样的通信协议,Rest+ JSON 或者基于 TCP 的私有二进制协议,协议的选择不同,性能模型也不同,相比于公有协议,内部私有二进制协议的性能通常可以被设计的更优;线程:数据报如何读取?读取之后的编解码在哪个线程进行,编解码后的消息如何派发,通信线程模型的不同,对性能的影响也非常大。

gRPC 线程模型


消息的序列化和反序列化均由 gRPC 线程负责,而没有在 Netty 的 Handler 中做 CodeC,原因如下:Netty4 优化了线程模型,所有业务 Handler 都由 Netty 的 I/O 线程负责,通过串行化的方式消除锁竞争,原理如下所示:

如果大量的 Handler 都在 Netty I/O 线程中执行,一旦某些 Handler 执行比较耗时,则可能会反向影响 I/O 操作的执行,像序列化和反序列化操作,都是 CPU 密集型操作,更适合在业务应用线程池中执行,提升并发处理能力。因此,gRPC 并没有在 I/O 线程中做消息的序列化和反序列化。
改进点思考

1、时间可控的接口调用直接在 I/O 线程上处理


gRPC 采用的是网络 I/O 线程和业务调用线程分离的策略,大部分场景下该策略是最优的。但是,对于那些接口逻辑非常简单,执行时间很短,不需要与外部网元交互、访问数据库和磁盘,也不需要等待其它资源的,则建议接口调用直接在 Netty /O 线程中执行,不需要再投递到后端的服务线程池。避免线程上下文切换,同时也消除了线程并发问题。

例如提供配置项或者接口,系统默认将消息投递到后端服务调度线程,但是也支持短路策略,直接在 Netty 的 NioEventLoop 中执行消息的序列化和反序列化、以及服务接口调用。
2、减少锁竞争


当前 gRPC 的线程切换策略如下:

优化之后的 gRPC 线程切换策略:

通过线程绑定技术(例如采用一致性 hash 做映射), 将 Netty 的 I/O 线程与后端的服务调度线程做绑定,1 个 I/O 线程绑定一个或者多个服务调用线程,降低锁竞争,提升性能。
四、gRPC 安全性设计

RPC 调用安全主要涉及如下三点:
    个人 / 企业敏感数据加密:例如针对个人的账号、密码、手机号等敏感信息进行加密传输,打印接口日志时需要做数据模糊化处理等,不能明文打印;对调用方的身份认证:调用来源是否合法,是否有访问某个资源的权限,防止越权访问;数据防篡改和完整性:通过对请求参数、消息头和消息体做签名,防止请求消息在传输过程中被非法篡改。
敏感数据加密传输

1. 基于 SSL/TLS 的通道加密

2. 针对敏感数据的单独加密


有些 RPC 调用并不涉及敏感数据的传输,或者敏感字段占比较低,为了最大程度的提升吞吐量,降低调用时延,通常会采用 HTTP/TCP + 敏感字段单独加密的方式,既保障了敏感信息的传输安全,同时也降低了采用 SSL/TLS 加密通道带来的性能损耗,对于 JDK 原生的 SSL 类库,这种性能提升尤其明显。

它的工作原理如下所示:

image

通常使用 Handler 拦截机制,对请求和响应消息进行统一拦截,根据注解或者加解密标识对敏感字段进行加解密,这样可以避免侵入业务。
认证和鉴权

1. 身份认证


2. 权限管控


在 RPC 调用领域比较流行的是基于 OAuth2.0 的权限认证机制,它的工作原理如下:

数据完整性和一致性


利用消息摘要可以保障数据的完整性和一致性,它的特点如下:
    单向 Hash 算法,从明文到密文的不可逆过程,即只能加密而不能解密;无论消息大小,经过消息摘要算法加密之后得到的密文长度都是固定的;输入相同,则输出一定相同。

目前常用的消息摘要算法是 SHA-1、MD5 和 MAC,MD5 可产生一个 128 位的散列值。 SHA-1 则是以 MD5 为原型设计的安全散列算法,可产生一个 160 位的散列值,安全性更高一些。MAC 除了能够保证消息的完整性,还能够保证来源的真实性。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-5-7 15:16 , Processed in 0.137900 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表