初识 Protobuf:微服务跨语言通信的一份契约

1. 前因:为什么我会用到 proto

我最近刚写完一个项目,项目中用到了 Protobuf,也就是大家常说的 proto。之前我没接触过它,所以一开始并不清楚它到底是什么。借这次机会,我和 AI 聊了聊,算是有了一点初步认知,也在这里分享一下。

我之所以会用到 proto,是因为当前项目是一个微服务架构的项目。简单说,就是把不同的服务拆开,比如 AI 服务、数据库服务、用户服务,各自作为独立模块来开发和部署。

为什么要拆开,而不是全部写在一个进程里?

假设所有服务都跑在同一个进程中,那这个进程一旦崩溃,整个项目都会挂掉,用户服务也会跟着不可用。拆成多个服务后,AI 服务崩溃通常不会直接拖垮用户服务、数据库服务,影响范围会被隔离。当然,和 AI 服务有依赖关系的模块还是会受影响。

这其实就是分布式的思路:不同服务可以部署在不同机器上,也可以用不同语言实现。

但问题也随之而来:服务之间怎么通信?数据怎么保持一致?

2. proto 出场:一份跨语言契约

不同服务可以部署在不同机器,使用不同语言实现。那么,接口和数据结构怎么对齐?

举个例子:

我用 C++ 实现了一个服务接口 UserService

  • 参数是 GetUserRequest

  • 返回是 User 对象

这时,邮件服务想获取用户信息,但它是 Java 写的,而且和 UserService 不在同一个进程里。

于是就出现了一个问题:

C++ 服务端和 Java 客户端之间,数据怎么传递?

复制代码
C++ 对象 -> 字节流 -> Java 对象

这个过程怎么保证不出错?

答案就是 proto。

proto 可以理解成一份契约。它规定好:不管你是 Java、C++ 还是 Go,大家看到的对象逻辑结构必须一致。

比如 User 对象定义了三个字段:

  • id

  • username

  • password_hash

那么无论生成的是 C++ 类、Java 类还是 Go 结构体,它们都只包含这三个逻辑字段。只是不同语言里的表达方式不同。

更准确地说,proto 的作用不是"直接帮你把 C++ 对象转成 Java 对象",而是:

  1. .proto 文件定义数据结构和接口;

  2. protoc 工具为不同语言生成代码;

  3. 各语言再借助 Protobuf 运行时库,把本地对象序列化成字节流,或从字节流反序列化成对象。

也就是说:

proto 的核心作用,就是通过一份契约文件,让不同语言之间能够互相理解对方的数据。

复制代码
.proto 文件 = 契约
protoc = 代码生成器
Protobuf 运行时库 = 负责对象 <-> 字节流
gRPC = 负责远程调用

3. 一个具体示例

先定义一个 user.proto

bash 复制代码
proto

syntax = "proto3";

package user.v1;

message GetUserRequest {
  int64 id = 1;
}

message User {
  int64 id = 1;
  string username = 2;
  string password_hash = 3; // 真实项目不要明文返回密码
}

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
}
复制代码

这里有几个关键点:

  • message 定义数据结构;

  • service 定义远程接口;

  • rpc 定义具体方法;

  • = 1= 2= 3 是字段编号,不是值。

字段编号非常重要。线上传输时,proto 认的是编号,不是字段名。

比如:

proto

复制代码
int64 id = 1;
string username = 2;

Java 里字段可能叫 idusername,C++ 里也可能叫 idusername,Go 里可能生成 IdUsername。名字不完全一样没关系,只要编号一致,双方就能对上。

4. 生成代码:C++ 服务端,Java 客户端

protoc 生成代码后:

  • C++ 会生成 UserGetUserRequestUserService 服务端基类等;

  • Java 会生成 UserGetUserRequestUserServiceGrpc.UserServiceBlockingStub 等。

C++ 服务端实现业务逻辑,伪代码大概是这样:

cpp

cpp 复制代码
class UserServiceImpl final : public user::v1::UserService::Service {
  grpc::Status GetUser(grpc::ServerContext* context,
                       const user::v1::GetUserRequest* req,
                       user::v1::User* resp) override {
    resp->set_id(req->id());
    resp->set_username("alice");
    resp->set_password_hash("...");
    return grpc::Status::OK;
  }
};

Java 邮件服务作为客户端,只需要调用生成的 Stub:

java

java 复制代码
ManagedChannel channel = ManagedChannelBuilder
    .forAddress("user-service", 50051)
    .usePlaintext()
    .build();

UserServiceGrpc.UserServiceBlockingStub stub =
    UserServiceGrpc.newBlockingStub(channel);

User user = stub.getUser(
    GetUserRequest.newBuilder().setId(123).build()
);

System.out.println(user.getUsername());

Java 这边不需要知道 C++ 对象长什么样,也不需要自己手写序列化逻辑。它只调用 Java 版 Stub,底层由 Protobuf 和 gRPC 处理。

5. 数据到底是怎么过去的

以 Java 调用 C++ 服务为例:

  1. Java 客户端构造 GetUserRequest 对象;

  2. Java 调用 proto 生成的序列化方法,把对象变成字节流;

  3. 字节流通过网络发送到 C++ 服务端;

  4. C++ 服务端收到字节流,用 C++ 版 Protobuf 代码反序列化成 C++ 对象;

  5. C++ 执行 GetUser 业务逻辑;

  6. C++ 把返回的 User 对象序列化成字节流;

  7. 字节流返回给 Java 客户端;

  8. Java 客户端反序列化成 Java 的 User 对象。

整个过程中,双方传的是字节流,不是对方的语言对象。

proto 保证的是:

  • 字段编号一致;

  • 字段类型一致;

  • 序列化规则一致;

  • 逻辑结构一致。

至于各语言里的对象叫法、内存布局、方法名,可以不同。

所以可以这样理解:

proto 的核心思想,就是通过一份契约,让不同语言之间能够互相理解对方的数据。

复制代码
物理结构不同,逻辑结构一致;
表达形式不同,数据语义一致。

6. 我最终的理解

proto 最大的价值,不是单纯做序列化,而是提供了一份跨语言的接口契约。

它让不同服务、不同语言、不同进程之间可以按同一套规则通信。

收益主要有:

  1. 接口和数据结构统一定义,减少扯皮;

  2. 自动生成多语言代码,减少手写 DTO;

  3. 跨进程、跨语言调用时,数据逻辑保持一致;

  4. 字段编号支持版本演进,新增字段更安全;

  5. 二进制序列化通常比 JSON 更小、更快。

当然,proto 也不是万能的。它保证的是结构和类型一致,不保证业务语义一定一致。比如 price 到底是分还是元,status = 1 代表什么,这些还需要文档、注释、测试和团队约定。

7. 总结

在微服务项目里,服务往往跨进程、跨机器、跨语言。直接调用接口不现实,只能通过网络传数据。

proto 的作用就是:

用一份 .proto 契约,定义数据结构和接口;

protoc 生成各语言代码;

用 Protobuf 运行时库完成对象和字节流的互转;

用 gRPC 等框架完成远程调用。

最终实现:

一份契约,多语言生成;

物理结构不同,逻辑结构一致;

跨进程调用,数据不跑偏。

相关推荐
Liaiyang6643 分钟前
# 自研 AST 容错初筛工具:横向评测 Django、PyTorch、TensorFlow 三大开源 Python 框架异常收容风险
python·测试工具·自动化·开源软件·代码规范·devops·代码复审
空奈qwq1 小时前
机器学习入门:从核心概念到建模全流程
人工智能·python·机器学习
longxuan011 小时前
倍增算法详解
c++·st表·倍增算法
xcl09251 小时前
宠物商城社交托运购物系统开发实战:从架构设计到完整实现指南
java·spring boot
孙启超1 小时前
【AI开发之Rust】第 12 课:并发模型与同步原语
开发语言·后端·rust
-森屿安年-1 小时前
返回最大数
c++·贪心算法
没差c1 小时前
RetrofitClient写接口、导包、配置、路径
java·spring boot·okhttp
0+1111 小时前
算法 --二分查找
c++·算法·leetcode
IvanCodes1 小时前
Python 基础语法(六):字典与集合
python