grpc实践之路:06.rpc的大问题思考
前言
在之前的源码剖析文章中,我们深入了 gRPC 的一些具体实现。
但随着探索的深入,我时常感到一种“盲人摸象”式的困惑——我们触摸到了大象的腿、鼻子、耳朵,但大象的全貌究竟是怎样的?如果只是追逐源码的细枝末节,很容易迷失在复杂的调用链中。
我决定或一种方式,像笛卡尔在沉思中探求“我思故我在”那样,让我们暂时忽略所有 RPC 框架的具体实现,回到最根本的出发点,用第一性原理去思考:如果让我们自己从零开始设计一个 RPC 框架,必然要解决哪些问题?
那些必然要解决的问题,或者说自己思考到的问题,就是现阶段自己可以学习与掌握的问题,也就是RPC中的大问题。
本文,就是我对自己这些思考的总结。之后我的行动也将从这些能力出发,探索框架如何实现这些能力。
思考过程:从网络编程推导 RPC 的必然形态
- 起点: RPC 是进程间通信,其底层是网络编程。那么,一次 RPC 调用,本质上就是一次网络请求;函数的返回值,就是网络响应。
- 翻译: 为了让远端的服务器能执行本地函数调用,客户端必须将“调用”这个行为,翻译成一种能在网络上传输的数据格式。同理,服务端也需要将“执行结果”翻译回来。
- 管理: 为了模拟本地函数调用的体验,客户端需要管理所有发出去的请求,确保响应能准确地返回给对应的调用者。
- 效率: 为了应对海量的并发请求,服务端必须设计一套高效的网络 I/O 处理模型。
- 健壮性: 网络是不可靠的。因此,一个合格的 RPC 框架必须处理各种网络异常。
基于此,我们可以推断出 RPC 框架的核心功能。
一、 RPC 框架的核心职责:客户端与服务端的“契约”
一个 RPC 框架,首先要明确定义通信双方各自需要完成的任务。
客户端的核心任务:
- 请求的构建与封装:
- 根据用户调用的方法,构造一个标准的网络请求。这个请求必须清晰地包含两部分:
- “信封”(元数据/请求头): 用来告诉服务端“我要调用哪个方法”、“这次通话的超时时间是多久”等控制信息。
- “信件”(数据/请求体): 将用户传入的 C++ 函数参数,通过序列化,转换成二进制字节流。
- 根据用户调用的方法,构造一个标准的网络请求。这个请求必须清晰地包含两部分:
- 请求的生命周期管理:
- 发出请求后,需要像一个“任务管理器”一样,追踪每一个请求的生命周期。
- 必须将处理网络异常的能力(如超时、重试、取消),无缝地集成到看似简单的函数调用中。
- 响应的处理:
- 接收网络响应后,能准确地找到这个响应属于哪个请求。
- 将响应的二进制数据反序列化,转换成用户代码可以理解的 C++ 对象。
服务端的核心任务:
- 服务的注册与管理:
- 在启动时,必须提供一种机制,让开发者能将业务逻辑(服务)注册到框架中。
- 内部必须维护一个高效的“路由表”,能够根据请求“信封”中的方法标识,快速找到对应的处理函数。
- 请求与响应的关联:
- 收到一个请求后,必须为其分配一个唯一的上下文,确保处理完成后,能将正确的响应准确无误地发回给对应的客户端。
- 高效的 I/O 处理:
- 这是高性能服务器的灵魂。必须采用高效的网络 I/O 模型(如基于 epoll 的 Reactor 模式),用少量线程处理海量并发连接。
- 异常情况的处理:
- 能够优雅地处理客户端的取消操作,及时释放资源。
- 能够处理自身的超时和内部错误,并向客户端返回明确的错误状态。
二、 RPC 的本质:网络编程的“三位一体”
总结来说,RPC 框架就是网络编程的进一步抽象和封装。它将繁琐的网络细节隐藏起来,让开发者能像调用本地函数一样进行远程通信。这个封装主要体现在三个方面:
- 协议 (Protocol) - 通信的“语言”
- 这是客户端与服务端之间最重要的契约。它定义了“信封”和“信件”的格式。
- 方法签名(如 gRPC 的 /package.Service/Method)就是这个语言中的“动词”,它规定了要执行什么操作。
- 序列化 (Serialization) - 数据的“标准化”
- 这是将内存中千奇百怪的 C++ 对象,转换为可以在网络上传输的、统一格式的二进制流的过程。
- Protobuf, JSON, FlatBuffers 等就是不同的序列化方案。
- 高级网络处理 (Advanced Networking) - 健壮性的“保障”
- 一个 RPC 框架的价值,很大程度上体现在它如何处理那些棘手的网络编程问题。
- 超时、重试、取消、负载均衡、流量控制等进阶功能,都是对底层网络问题的上层封装和策略实现。
三、 我的行动指南:带着问题探索源码
基于上述的思考,我为自己接下来的源码探索之旅,列出了几个核心问题。这些问题,以及不同框架对它们的解答,也将是我学习的重点。
- 问题 1:如何保证每个请求的唯一标识?
- 问题 2:如何保证请求在网络上的安全?
- 问题 3:如何保证查找请求(路由)的高效性?
- 问题 4:重试与超时机制是如何无缝衔接到函数调用中的?
- 问题 5:同步与异步处理的底层机制是怎样的?
- 问题 6:如何做好高并发下的线程安全?
四、结语
这,就是我的探索地图。希望它也能为你带来一些启发,如果有什么错误或者不足,希望也能在评论区指出。

浙公网安备 33010602011771号