3. gRPC基础
gRPC 是 Google 开源的高性能远程过程调用框架,基于 HTTP/2 协议传输,默认使用 Protocol Buffers(protobuf)作为接口定义语言与序列化工具。在微服务架构中,gRPC 凭借其低延迟、强类型、多语言支持和双向流特性,成为服务间通信的核心选项之一。
3.1 Protocol Buffers 语法基础
Protocol Buffers 是一种语言中立、平台中立的序列化结构数据机制,通过 .proto 文件定义数据结构与服务接口。相比 JSON/XML,protobuf 序列化后体积小 3~10 倍,解析速度快 20~100 倍。
3.1.1 消息类型定义
使用 message 关键字定义结构化数据,每个字段包含类型、名称和唯一编号。
syntax = "proto3";
package ecommerce;
message Product {
int32 id = 1;
string name = 2;
double price = 3;
repeated string tags = 4; // 数组类型
map<string, string> attributes = 5; // 映射类型
Category category = 6; // 嵌套消息
}
message Category {
int32 id = 1;
string name = 2;
}
3.1.2 标量类型与字段规则
| Protobuf 类型 | C# 对应类型 | Java 对应类型 | Go 对应类型 | 字段规则 |
|---|---|---|---|---|
| double | double | double | float64 | 默认值 0.0 |
| float | float | float | float32 | 默认值 0.0 |
| int32 | int | int | int32 | 可变长编码,负数效率低 |
| int64 | long | long | int64 | 可变长编码 |
| string | string | String | string | UTF-8 编码,默认空串 |
| bool | bool | boolean | bool | 默认 false |
| bytes | ByteString | ByteString | []byte | 不进行 UTF-8 校验 |
| repeated | List<T> | List<T> | []T | 有序列表,可包含 0 个元素 |
| map<K,V> | Dictionary<K,V> | Map<K,V> | map[K]V | 键类型必须是标量 |
字段编号规则:1~15 编号使用 1 字节编码,16~2047 使用 2 字节编码。频繁出现的字段应分配 1~15 编号以节省空间。预留编号范围 19000~19999 供内部实现使用。
3.1.3 枚举与 oneof
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0; // 必须从 0 开始
PENDING = 1;
CONFIRMED = 2;
SHIPPED = 3;
DELIVERED = 4;
CANCELLED = 5;
}
message Order {
string order_id = 1;
oneof payment { // 互斥字段,只能设置其中一个
string credit_card = 2;
string paypal_id = 3;
string bank_transfer = 4;
}
OrderStatus status = 5;
}
3.2 gRPC 服务定义
在 .proto 文件中使用 service 关键字定义 RPC 服务,每个方法指定请求消息类型和响应消息类型。gRPC 支持四种通信模式,通过不同的流式关键字组合实现。
3.2.1 服务定义语法
service OrderService {
// Unary:客户端发送单个请求,服务端返回单个响应
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
// Server Streaming:客户端发送单个请求,服务端返回流式响应
rpc ListOrders(ListOrdersRequest) returns (stream Order);
// Client Streaming:客户端发送流式请求,服务端返回单个响应
rpc BatchCreateOrder(stream CreateOrderRequest) returns (BatchCreateOrderResponse);
// Bidirectional Streaming:双方同时发送流式数据
rpc ProcessOrderStream(stream OrderEvent) returns (stream OrderResult);
}
3.2.2 生成代码与依赖
通过 protoc 编译器生成目标语言的 gRPC 代码:
# 生成 Go 代码
protoc --go_out=. --go-grpc_out=. ecommerce/order.proto
# 生成 Java 代码
protoc --java_out=. --grpc-java_out=. ecommerce/order.proto
# 生成 Python 代码
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ecommerce/order.proto
关键依赖:protobuf 编译器(protoc) + 对应语言的 gRPC 插件。Go 需要
protoc-gen-go-grpc,Java 需要 protoc-gen-grpc-java,Python 需要 grpcio-tools。
3.3 四种通信模式详解
gRPC 基于 HTTP/2 的流式传输能力,提供了四种通信模式,覆盖从简单请求-响应到复杂双向流的所有场景。
3.4 四种通信模式实现示例
3.4.1 Unary RPC(一元模式)
最基础的请求-响应模式,客户端发送一个请求,服务端返回一个响应。适用于查询、创建、更新等原子操作。
// Go 服务端实现
func (s *orderServer) CreateOrder(ctx context.Context, req *pb.CreateOrderRequest) (*pb.CreateOrderResponse, error) {
// 业务逻辑:验证请求、写入数据库
order := &Order{
ID: generateOrderID(),
UserID: req.UserId,
Items: req.Items,
Status: OrderStatusPending,
}
if err := db.Save(order); err != nil {
return nil, status.Errorf(codes.Internal, "failed to create order: %v", err)
}
return &pb.CreateOrderResponse{
OrderId: order.ID,
Status: "created",
}, nil
}
// Go 客户端调用
resp, err := client.CreateOrder(ctx, &pb.CreateOrderRequest{
UserId: "user_123",
Items: []*pb.OrderItem{{ProductId: "prod_456", Quantity: 2}},
})
if err != nil {
log.Fatalf("create order failed: %v", err)
}
fmt.Printf("Order created: %s\n", resp.OrderId)
3.4.2 Server Streaming RPC(服务端流模式)
客户端发送单个请求,服务端返回一个消息流。适用于需要推送大量数据的场景,如日志流、监控指标、大列表分页。
// Go 服务端实现
func (s *orderServer) ListOrders(req *pb.ListOrdersRequest, stream pb.OrderService_ListOrdersServer) error {
orders, err := db.GetOrdersByUser(req.UserId)
if err != nil {
return status.Errorf(codes.Internal, "failed to list orders: %v", err)
}
for _, order := range orders {
if err := stream.Send(order.ToProto()); err != nil {
return err
}
// 可添加限速逻辑
time.Sleep(10 * time.Millisecond)
}
return nil
}
// Go 客户端调用
stream, err := client.ListOrders(ctx, &pb.ListOrdersRequest{UserId: "user_123"})
for {
order, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("receive order failed: %v", err)
}
fmt.Printf("Order: %s, Status: %s\n", order.OrderId, order.Status)
}
3.4.3 Client Streaming RPC(客户端流模式)
客户端发送一个消息流,服务端返回单个响应。适用于批量提交、文件上传、事件聚合等场景。
// Go 服务端实现
func (s *orderServer) BatchCreateOrder(stream pb.OrderService_BatchCreateOrderServer) error {
var orders []*pb.CreateOrderRequest
for {
req, err := stream.Recv()
if err == io.EOF {
// 所有请求接收完毕,批量处理
results := processBatch(orders)
return stream.SendAndClose(&pb.BatchCreateOrderResponse{
TotalCreated: int32(len(results)),
OrderIds: results,
})
}
if err != nil {
return status.Errorf(codes.Internal, "receive failed: %v", err)
}
orders = append(orders, req)
// 可设置最大批量限制
if len(orders) >= 1000 {
results := processBatch(orders)
return stream.SendAndClose(&pb.BatchCreateOrderResponse{
TotalCreated: int32(len(results)),
OrderIds: results,
})
}
}
}
// Go 客户端调用
stream, err := client.BatchCreateOrder(ctx)
for _, req := range batchRequests {
if err := stream.Send(req); err != nil {
log.Fatalf("send failed: %v", err)
}
}
resp, err := stream.CloseAndRecv()
if err != nil {
log.Fatalf("close and recv failed: %v", err)
}
fmt.Printf("Created %d orders\n", resp.TotalCreated)
3.4.4 Bidirectional Streaming RPC(双向流模式)
客户端和服务端可以同时发送和接收消息流,双方独立读写。适用于实时聊天、协作编辑、游戏状态同步等场景。
// Go 服务端实现
func (s *orderServer) ProcessOrderStream(stream pb.OrderService_ProcessOrderStreamServer) error {
// 使用 goroutine 处理并发读写
errChan := make(chan error, 1)
// 接收客户端消息
go func() {
for {
event, err := stream.Recv()
if err == io.EOF {
errChan <- nil
return
}
if err != nil {
errChan <- err
return
}
// 处理事件并发送结果
result := processEvent(event)
if err := stream.Send(result); err != nil {
errChan <- err
return
}
}
}()
return <-errChan
}
// Go 客户端调用
stream, err := client.ProcessOrderStream(ctx)
// 启动接收 goroutine
go func() {
for {
result, err := stream.Recv()
if err == io.EOF {
return
}
if err != nil {
log.Fatalf("receive failed: %v", err)
}
fmt.Printf("Result: %s\n", result.Status)
}
}()
// 发送事件
for _, event := range events {
if err := stream.Send(event); err != nil {
log.Fatalf("send failed: %v", err)
}
}
stream.CloseSend()
3.5 通信模式对比与选型
| 特性 | Unary | Server Streaming | Client Streaming | Bidirectional |
|---|---|---|---|---|
| 请求数量 | 1 | 1 | N(流) | N(流) |
| 响应数量 | 1 | N(流) | 1 | N(流) |
| 延迟 | 最低 | 中等 | 中等 | 较高(首包) |
| 吞吐量 | 中等 | 高 | 高 | 最高 |
| 实现复杂度 | 低 | 中 | 中 | 高 |
| 连接复用 | HTTP/2 多路复用 | 单流 | 单流 | 单流 |
| 典型场景 | CRUD、查询 | 日志、监控、推送 | 批量导入、上传 | 聊天、实时协作 |
| 错误处理 | 简单 | 流中断处理 | 流中断处理 | 复杂,需协调 |
| 背压控制 | 不适用 | 服务端控制发送速率 | 客户端控制发送速率 | 双方需协商 |
流式通信注意事项:
- 流式连接会长期占用 HTTP/2 流,需合理设置连接池大小
- 服务端流和双向流必须实现背压控制,防止内存溢出
- 客户端流需在发送完毕后调用
CloseAndRecv()或CloseSend() - 所有流式模式都需处理
io.EOF作为正常结束信号 - 生产环境建议为流式连接设置超时和重试策略
- 双向流中,客户端和服务端的读写 goroutine 需独立处理,避免死锁
3.6 gRPC 安全基础
gRPC 原生支持 TLS/mTLS 加密通信,确保服务间数据传输的机密性和完整性。安全配置在服务端和客户端分别进行。
3.6.1 服务端 TLS 配置
// Go 服务端启用 TLS
func main() {
// 加载证书和密钥
certFile := "server.crt"
keyFile := "server.key"
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
if err != nil {
log.Fatalf("failed to load credentials: %v", err)
}
// 创建 gRPC 服务器并配置 TLS
s := grpc.NewServer(
grpc.Creds(creds),
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: 5 * time.Minute,
MaxConnectionAge: 30 * time.Minute,
MaxConnectionAgeGrace: 5 * time.Minute,
Time: 1 * time.Hour,
Timeout: 20 * time.Second,
}),
)
pb.RegisterOrderServiceServer(s, &orderServer{})
lis, _ := net.Listen("tcp", ":50051")
s.Serve(lis)
}
3.6.2 客户端 TLS 配置
// Go 客户端启用 TLS
func main() {
// 加载 CA 证书用于验证服务端
caFile := "ca.crt"
creds, err := credentials.NewClientTLSFromFile(caFile, "server.example.com")
if err != nil {
log.Fatalf("failed to load CA: %v", err)
}
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithTransportCredentials(creds),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 10 * time.Second,
Timeout: 20 * time.Second,
PermitWithoutStream: true,
}),
)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewOrderServiceClient(conn)
// 后续调用...
}
安全最佳实践:
- 生产环境必须启用 TLS,禁止使用
grpc.WithInsecure() - 使用 mTLS 实现双向认证,服务端和客户端都验证对方证书
- 证书使用内部 CA 签发,定期轮换(建议 90 天)
- 结合 Token 认证(JWT/OAuth2)实现应用层鉴权
- 流式连接需设置 keepalive 参数,防止连接泄漏
- 使用
grpc.MaxRecvMsgSize和grpc.MaxSendMsgSize限制消息大小