# RPC
RPC(Remote Procedure Call)是一种编程技术,允许程序员在编写程序时调用其他系统上的过程或函数,就像这些过程和函数是在本地执行一样。这种技术使得分布式应用程序的开发更加简单和直观。
在 RPC 中,客户端通过网络向远程服务器发送一个请求,请求包含要调用的函数名称、参数等信息。服务器接收到请求后,执行相应的函数,并将结果返回给客户端。这个过程对开发者来说是透明的,他们只需要关心函数调用的结果,而不必处理底层的网络通信细节。
RPC 的优点包括:
- 简化分布式系统的开发:它使开发者可以像调用本地函数一样调用远程函数。
- 提高代码复用性:服务可以被多个客户端使用,而不需要复制代码。
- 提高效率:某些情况下,RPC 可以比传统 HTTP 请求更快,因为它通常使用二进制编码而不是文本编码,并且可能具有更高效的序列化和反序列化机制。
然而,RPC 也存在一些挑战,例如需要处理网络错误、不同操作系统之间的兼容性问题以及安全问题。为了克服这些问题,现代的 RPC 框架通常会提供诸如负载均衡、容错、安全传输等功能。
# RPC 与 HTTP
RPC(Remote Procedure Call)和 HTTP 请求有以下几点主要区别:
目的:
- RPC 的目标是让远程调用看起来像本地函数调用,尽可能地隐藏网络细节。
- HTTP 请求通常用于客户端与服务器之间的数据交换,它是一种应用层协议,广泛应用于 Web 开发。
协议:
- RPC 可以基于多种传输协议实现,包括 TCP、UDP 或自定义二进制协议等。例如,gRPC 使用 HTTP/2 作为传输协议。
- HTTP 是一种应用层协议,基于 TCP/IP 协议栈,使用请求 - 响应模型进行通信。
数据格式:
- RPC 通常使用二进制编码来传递数据,这样可以减少传输的字节数量,提高效率。
- HTTP 主要使用文本格式(如 JSON 或 XML)来传输数据,尽管也可以使用二进制格式(如 protobuf)。
语义:
- RPC 的语义更接近于直接执行程序逻辑,通常具有更好的性能表现。
- HTTP 更关注资源访问和状态转移,它的设计是为了在分布式系统中提供文档共享和协作。
复杂性:
- RPC 实现起来可能比简单的 HTTP 调用更为复杂,因为它需要处理更多底层的网络通信细节。
- HTTP 请求相对简单,很多编程语言都有内置的库或框架来支持 HTTP 操作。
安全性:
- RPC 的安全机制取决于具体的实现,可以通过加密和其他技术来保证数据的安全性。
- HTTP 提供了一些标准的安全机制,比如 HTTPS(HTTP over SSL/TLS),能够提供端到端的数据加密。
跨平台和语言支持:
- RPC 的跨平台和跨语言支持依赖于所使用的特定实现。
- HTTP 具有很好的跨平台和跨语言支持,因为它是互联网上的标准协议之一。
中间件和缓存:
- RPC 对中间件的支持程度取决于具体实现,但通常不如 HTTP 那么广泛。
- HTTP 支持各种中间件,如代理、缓存服务器、负载均衡器等。
总之,RPC 和 HTTP 请求各有优缺点,适用于不同的场景。RPC 更适合那些对性能要求高、强调低延迟、需要隐藏网络细节的场景;而 HTTP 则更适合构建 RESTful API 和 Web 应用,因为它更加灵活,易于集成,并且有大量的现有工具和库支持。
# 分布式
分布式(Distributed)是一个广泛的计算机科学概念,指的是将一个整体的系统或任务分解成多个部分,并分布在不同的物理位置、计算机节点或者网络中的多台机器上执行。这些分散的部分通过网络进行通信和协作,以完成共同的任务目标。
分布式系统的例子包括:
- 分布式计算:比如 MapReduce 模型,它将大数据处理任务分解成多个子任务,分布到多台服务器上并行处理。
- 分布式数据库:数据在多台服务器上分片存储,使得读写操作可以在多台服务器上同时进行,提高性能和可用性。
- 分布式文件系统:如 Hadoop HDFS,将大文件分割成多个块存储在多台服务器上,提供高容错性和高吞吐量的数据访问。
- 分布式服务架构:例如微服务架构,将大型应用程序拆分成一组小型的服务,每个服务运行在独立的进程中,可以独立部署和扩展。
分布式系统的主要优势包括可伸缩性、容错性、资源共享以及更高的并发处理能力。然而,分布式系统的设计和实现也面临一些挑战,如数据一致性、网络延迟、故障恢复、安全性等问题。
# gRPC 与 RPC
gRPC 和传统 RPC(Remote Procedure Call)的核心目标都是实现远程服务调用,但它们在实现方式、性能、功能特性等方面存在显著差异。以下是两者的关键区别:
# 1. 协议与传输层
- RPC:
- 通用概念:RPC 是一种通信模式的抽象,本身不绑定具体协议。传统 RPC 实现(如早期的 XML-RPC、JSON-RPC)可能基于 HTTP/1.x、TCP 或其他协议。
- 效率较低:许多传统 RPC 框架使用文本格式(如 XML、JSON),序列化 / 反序列化开销较大。
- gRPC:
- 基于 HTTP/2:默认使用 HTTP/2 协议,支持多路复用(Multiplexing)、头部压缩(HPACK)、服务端推送等特性,显著提升传输效率。
- 二进制协议:使用 Protocol Buffers(Protobuf)作为默认的序列化格式,体积更小、解析更快。
# 2. 接口定义与代码生成
- RPC:
- 松散定义:传统 RPC 可能依赖文档或人工约定接口,容易导致前后端不一致。
- 手动处理:需要开发者手动编写客户端和服务端代码,易出错。
- gRPC:
- 强类型接口:通过
.proto文件明确定义服务接口和数据结构(使用 IDL,接口定义语言)。 - 自动代码生成:Protobuf 编译器自动生成客户端和服务端代码(支持多种语言),确保类型安全,减少手写代码的错误。
- 强类型接口:通过
# 3. 通信模式
RPC:
- 简单请求 - 响应:大多数传统 RPC 仅支持单向的同步调用(客户端发送请求,等待服务端响应)。
gRPC:
多种通信模式
:
- Unary RPC:传统请求 - 响应模式。
- Server Streaming RPC:服务端推送多个响应(如实时日志)。
- Client Streaming RPC:客户端发送多个请求后,服务端返回一个响应(如文件上传)。
- Bidirectional Streaming RPC:双向实时流式通信(如聊天室)。
# 4. 跨语言支持
- RPC:
- 依赖具体实现:不同 RPC 框架的跨语言支持差异较大,例如 Java RMI 仅支持 Java。
- gRPC:
- 多语言原生支持:官方支持 10+ 种语言(如 Go、Java、Python、C++、Node.js 等),通过 Protobuf 实现无缝跨语言通信。
# 5. 生态与工具链
- RPC:
- 碎片化:传统 RPC 框架(如 Dubbo、Thrift、XML-RPC)各自为战,生态工具分散。
- gRPC:
- 丰富生态:集成 Kubernetes、Envoy 等云原生工具链,支持负载均衡、重试、超时等特性。
- 与云原生融合:天然适配微服务架构,常用于服务网格(如 Istio)中的服务间通信。
# 6. 典型应用场景
- RPC:
- 简单的内部服务调用。
- 对性能要求不高的场景(如小型单体应用)。
- gRPC:
- 微服务架构:需要高效、类型安全的服务间通信。
- 移动端与后端通信:利用 HTTP/2 和 Protobuf 减少带宽消耗。
- 实时流式系统:如 IoT 设备数据推送、在线游戏、金融行情等。
# 总结对比表
| 特性 | RPC | gRPC |
|---|---|---|
| 协议 | HTTP/1.x、TCP 等 | HTTP/2 |
| 序列化 | XML、JSON 等文本格式 | Protocol Buffers(二进制) |
| 接口定义 | 松散约定 | 强类型 .proto 文件 |
| 代码生成 | 手动编写 | 自动生成多语言代码 |
| 通信模式 | 单向请求 - 响应 | 支持 4 种模式(含流式) |
| 性能 | 较低 | 高(HTTP/2 + Protobuf) |
| 适用场景 | 简单内部调用 | 微服务、移动端、实时流 |
# 何时选择?
- 选 gRPC:需要高性能、强类型接口、流式通信,或跨语言微服务。
- 选传统 RPC:遗留系统兼容、简单场景(如少量内部服务调用)。
gRPC 可以看作 RPC 的现代化演进版本,结合了高性能协议、强类型接口和丰富的生态,是云原生时代的首选 RPC 框架。
# proto
# 1. 基本结构
一个 .proto 文件通常包含:
- 版本声明(可选,默认
proto3) - 包名(用于防止命名冲突)
- 消息类型(定义数据结构)
- 服务(定义 RPC 接口,需配合 gRPC 使用)
# 示例模板:
syntax = "proto3"; // 指定版本(proto3 或 proto2) | |
package mypackage; // 包名(可选) | |
// 定义消息类型(类似类或结构体) | |
message MyMessage { | |
// 字段定义 | |
int32 id = 1; | |
string name = 2; | |
} | |
// 定义服务(RPC 接口) | |
service MyService { | |
rpc MyMethod(MyRequest) returns (MyResponse); | |
} |
# 2. 字段定义规则
# (1) 字段格式
[字段规则] 数据类型 字段名 = 字段编号; |
- 字段规则(proto3 默认是 singular,即单值):
optional:可选字段(proto3 中需显式声明)。repeated:数组 / 列表(如repeated string tags = 3;)。oneof:多选一(类似联合类型)。
# (2) 数据类型
| 类型 | 说明 | 对应编程语言示例 |
|---|---|---|
int32 | 32 位整数 | Java int |
string | 字符串 | Python str |
bool | 布尔值 | Go bool |
bytes | 二进制数据 | C++ std::string |
double | 双精度浮点数 | Java double |
message | 嵌套的自定义消息类型 | 自定义类 |
# (3) 字段编号
- 必须唯一:每个字段的编号在消息内不可重复。
- 范围:1 到 536,870,911(但 1~15 占用 1 字节,适合高频字段)。
# 3. 枚举(Enum)
定义一组固定值:
message SearchRequest { | |
string query = 1; | |
enum SearchType { | |
IMAGE = 0; // 枚举值必须从 0 开始 | |
VIDEO = 1; | |
TEXT = 2; | |
} | |
SearchType type = 2; | |
} |
# 4. 服务(Service)
定义 RPC 接口(需配合 gRPC 使用):
service UserService { | |
// 定义一个方法,接收 Request 消息,返回 Response 消息 | |
rpc GetUser(GetUserRequest) returns (GetUserResponse); | |
} | |
message GetUserRequest { | |
int32 user_id = 1; | |
} | |
message GetUserResponse { | |
string name = 1; | |
string email = 2; | |
} |
# 5. 高级特性
# (1) 嵌套消息
message Outer { | |
message Inner { | |
int32 id = 1; | |
} | |
repeated Inner inner_list = 1; | |
} |
# (2) 导入其他 proto 文件
import "other.proto"; // 导入其他 proto 定义 |
# (3) 选项(Options)
控制代码生成行为:
option java_package = "com.example.mypackage"; // 指定 Java 包名 | |
option optimize_for = SPEED; // 优化模式(SPEED, CODE_SIZE, LITE_RUNTIME) |
# 6. 编译 proto 文件
使用 protoc 编译器生成代码:
# 生成 Go 代码 | |
protoc --go_out=. myproto.proto | |
# 生成 Java 代码 | |
protoc --java_out=. myproto.proto | |
# 生成 Python 代码 | |
protoc --python_out=. myproto.proto | |
# 生成 C++ 代码 | |
protoc --cpp_out=. myproto.proto |
# 7. 注意事项
字段编号不可重复:同一消息内的字段编号必须唯一。
保留字段:防止旧版本字段被误用:
message Foo {
reserved 2, 15 to 20; // 保留字段编号或名称
reserved "email";
}包名冲突:不同
.proto文件避免使用相同包名。
# 综合示例
syntax = "proto3"; | |
package example; | |
message Person { | |
string name = 1; | |
int32 age = 2; | |
repeated string hobbies = 3; // 列表 | |
optional string email = 4; // 可选字段 | |
Gender gender = 5; | |
enum Gender { | |
UNKNOWN = 0; | |
MALE = 1; | |
FEMALE = 2; | |
} | |
} | |
service PersonService { | |
rpc GetPerson(GetPersonRequest) returns (Person); | |
} | |
message GetPersonRequest { | |
int32 person_id = 1; | |
} |
# tRPC
trpc/README.zh_CN.md
# Protocol Buffers—— 跨语言
Protocol Buffers,通常简称为 protobuf,是 Google 开发的一种数据序列化协议和工具包。它是一种灵活、高效且独立于语言的结构化数据编码方式,可以用于通信协议、数据存储等各种场景。
Protocol Buffers 的主要优点包括:
- 效率:与 XML 或 JSON 等文本格式相比,protobuf 采用二进制编码,所以它的数据占用空间更小,传输更快。
- 灵活性:你可以在不修改已部署的应用程序的情况下更新数据结构。
- 跨平台:官方支持 C++、Java、Python、Objective C、C#、Ruby、PHP、JavaScript 等多种编程语言,并且有大量的第三方拓展包几乎覆盖了所有流行的语言。
- 可扩展性:通过简单的 “版本控制” 机制,可以在不影响旧代码的情况下添加新字段。
使用 Protocol Buffers 的过程主要包括以下几个步骤:
- 定义数据结构:首先,你需要用 Protocol Buffers 的语法(
.proto文件)来定义你的数据结构。 - 生成代码:接着,使用 Protocol Buffers 编译器(protoc)根据
.proto文件生成对应编程语言的类库。 - 编写应用程序:在你的应用程序中,你可以直接使用这些生成的类库来进行数据的序列化(将数据对象转换为字节流)和反序列化(从字节流恢复数据对象)。
Protocol Buffers 被广泛应用于各种场景,如网络通信、配置文件、数据持久化等。由于其高效的性能和良好的跨平台特性,它已经成为一种非常流行的数据交换格式。
# AOP
AOP(Aspect-Oriented Programming)是面向切面编程的简称,它是一种编程思想和方法论,旨在解决横切关注点问题。在软件开发中,常常存在一些功能或需求,它们跨越多个对象或者类,例如日志记录、安全检查、事务管理等。这些被称为横切关注点,因为它们贯穿于系统的多个模块。
传统的面向对象编程(OOP)通过封装、继承和多态来组织代码,但对处理这类横切关注点并不方便,往往导致大量重复代码或者 “面条式” 代码(即逻辑被分散到各个地方)。AOP 正是为了解决这个问题而提出的。
AOP 通过引入 “切面”(Aspects)的概念,将这些横切关注点从主要业务逻辑中分离出来,然后以声明的方式将它们织入(Weave)到程序的不同位置。切面可以包含通知(Advice)、切入点(Pointcut)和连接点(Join Point)等元素。
- 通知:指的是在某个特定的连接点上执行的操作,比如方法调用前后的增强。
- 切入点:定义了通知应该在哪些连接点上执行,通常是匹配一组方法签名的表达式。
- 连接点:程序执行过程中的一些具体时间点,如方法调用、异常抛出等。
使用 AOP 可以在不修改原有代码的基础上添加新的功能,提高代码的可维护性和复用性。常见的 AOP 实现有 Spring AOP(基于 Java)和 AspectJ(独立于语言的 AOP 框架)。