gRPC

技术栈
后端框架
rpcprotobufhttp2googlemicroservices

概览

gRPC 技术栈概览

gRPC 是 Google 于 2015 年开源的高性能 RPC 框架,基于 HTTP/2 + Protocol Buffers,是微服务内部通信的事实标准。它支持 11 种语言,提供自动代码生成、双向流、内置负载均衡、拦截器等企业级特性,Cloud Native Computing Foundation(CNCF)孵化项目。

解决什么问题

  • REST JSON 序列化开销大 → Protobuf 二进制序列化快 3-10 倍,体积小 50%+
  • HTTP/1.1 连接利用率低 → HTTP/2 多路复用,一个连接承载数百万请求
  • 多语言微服务接口定义不一致 → .proto 作为唯一契约,自动生成所有语言客户端
  • 缺少内置流式通信 → gRPC 四种调用模式(一元/服务端流/客户端流/双向流)

关键特性

  • Protocol Buffers:强类型 IDL,二进制序列化,前向后向兼容
  • HTTP/2 传输:多路复用、头部压缩、服务端推送
  • 多语言代码生成protoc 编译器输出 Java/Go/Python/C++/Node.js 等
  • 四种通信模式:Unary、Server Streaming、Client Streaming、Bidirectional Streaming
  • 拦截器(Interceptor):认证、日志、限流统一注入
  • Deadline/Timeout:自动传播超时上下文
  • 负载均衡:客户端负载均衡(gRPCLB)

安装

环境准备

  • protoc:Protocol Buffers 编译器
  • 对应语言工具链:Go / Java / Python / Node.js 等

安装命令

protoc 编译器

# macOS
brew install protobuf

# Linux
sudo apt install -y protobuf-compiler
# 或手动下载:https://github.com/protocolbuffers/protobuf/releases

# 验证
protoc --version

Go gRPC

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
export PATH="$PATH:$(go env GOPATH)/bin"

Python gRPC

pip install grpcio grpcio-tools

Java gRPC(Gradle / Maven)

implementation 'io.grpc:grpc-netty-shaded:1.60.1'
implementation 'io.grpc:grpc-protobuf:1.60.1'
implementation 'io.grpc:grpc-stub:1.60.1'

定义第一个 .proto 文件

syntax = "proto3";

package hello;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

生成代码

# Go
protoc --go_out=. --go-grpc_out=. hello.proto

# Python
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. hello.proto

常见问题

Q1: protoc 找不到

确认 PATH 包含 protoc 所在目录。或使用 Buf(https://buf.build)替代原生 protoc。

Q2: 生成的代码 import 报错

检查 --go_out--go-grpc_out 输出路径一致,且 Go module 设置了正确的包路径。

Q3: 浏览器不能直接调用 gRPC

gRPC-Web 可让浏览器用 gRPC 协议(需 Envoy 代理),或改用 grpc-gateway 自动生成 REST API。

示例

gRPC 例程:Go 语言四种通信模式

目标

用 Go 实现 gRPC 的四种通信模式:Unary、Server Streaming、Client Streaming、Bidirectional Streaming。

Proto 定义

syntax = "proto3";
package calculator;
option go_package = "example.com/grpc-demo/calculator";

service Calculator {
  // 一元调用
  rpc Add(AddRequest) returns (AddResponse);

  // 服务端流:客户端发一次,服务端发多次
  rpc Fibonacci(FibRequest) returns (stream FibResponse);

  // 客户端流:客户端发多次,服务端发一次
  rpc Average(stream AvgRequest) returns (AvgResponse);

  // 双向流:双方自由发送
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

message AddRequest  { int32 a = 1; int32 b = 2; }
message AddResponse { int32 result = 1; }

message FibRequest  { int32 n = 1; }
message FibResponse { int64 value = 1; }

message AvgRequest  { int32 num = 1; }
message AvgResponse { double average = 1; }

message ChatMessage { string user = 1; string text = 2; }

服务端实现

package main

import (
	"context"
	"io"
	"log"
	"net"

	pb "example.com/grpc-demo/calculator"
	"google.golang.org/grpc"
)

type server struct {
	pb.UnimplementedCalculatorServer
}

// Unary
func (s *server) Add(ctx context.Context, req *pb.AddRequest) (*pb.AddResponse, error) {
	return &pb.AddResponse{Result: req.A + req.B}, nil
}

// Server Streaming
func (s *server) Fibonacci(req *pb.FibRequest, stream pb.Calculator_FibonacciServer) error {
	var a, b int64 = 0, 1
	for i := int32(0); i < req.N; i++ {
		stream.Send(&pb.FibResponse{Value: a})
		a, b = b, a+b
	}
	return nil
}

// Client Streaming
func (s *server) Average(stream pb.Calculator_AverageServer) error {
	var sum, count int32
	for {
		req, err := stream.Recv()
		if err == io.EOF {
			return stream.SendAndClose(&pb.AvgResponse{
				Average: float64(sum) / float64(count),
			})
		}
		if err != nil { return err }
		sum += req.Num
		count++
	}
}

// Bidirectional Streaming
func (s *server) Chat(stream pb.Calculator_ChatServer) error {
	for {
		msg, err := stream.Recv()
		if err == io.EOF { return nil }
		if err != nil { return err }
		log.Printf("[%s]: %s", msg.User, msg.Text)
		stream.Send(&pb.ChatMessage{
			User: "Server",
			Text: "收到: " + msg.Text,
		})
	}
}

func main() {
	lis, _ := net.Listen("tcp", ":50051")
	s := grpc.NewServer()
	pb.RegisterCalculatorServer(s, &server{})
	log.Println("gRPC 服务启动在 :50051")
	s.Serve(lis)
}

运行步骤

# 生成代码
protoc --go_out=. --go-grpc_out=. calculator.proto

# 启动服务端
go run server/main.go

# 启动客户端测试(需编写 client.go)
go run client/main.go

运行结果示例

2024/01/01 gRPC 服务启动在 :50051
[Alice]: Hello!
[Bob]: Hi there!

四种模式对比

模式 客户端 服务端 典型场景
Unary 1 请求 → 1 响应 1 请求 → 1 响应 简单查询
Server Stream 1 请求 → 流式接收 1 请求 → 流式发送 日志订阅
Client Stream 流式发送 → 1 响应 流式接收 → 1 响应 文件上传
Bidi Stream 自由收发 自由收发 聊天、实时协作

教程

gRPC 微服务通信入门教程

第一章:gRPC vs REST

维度 gRPC REST
协议 HTTP/2 HTTP/1.1
序列化 Protobuf(二进制) JSON(文本)
接口定义 .proto OpenAPI/Swagger
代码生成 自动(多语言) 手动或工具
流式 原生支持 需 WebSocket
浏览器 需要 gRPC-Web 原生支持
性能 高 3-10 倍 基准
调试 需工具 浏览器可见

结论:微服务内部通信用 gRPC;对外 API 用 REST(或 grpc-gateway 自动转换)。

第二章:Protobuf 语言指南

syntax = "proto3";

// 消息定义
message User {
  int64 id = 1;           // 字段编号(不是值)
  string name = 2;
  string email = 3;
  repeated string tags = 4;  // 数组
}

// 服务定义
service UserService {
  rpc GetUser(UserRequest) returns (User);
  rpc ListUsers(UserFilter) returns (stream User);
}

字段编号一旦分配不要修改(前向后向兼容的关键)。

第三章:四种通信模式详解

Unary(一元调用)

最常用。请求→响应,类似 HTTP 调用。

Server Streaming(服务端流)

适合:大量数据推送。如日志订阅、行情推送。

Client Streaming(客户端流)

适合:文件上传、数据批量处理。

Bidirectional Streaming(双向流)

适合:聊天、实时协作编辑、游戏。

第四章:拦截器

gRPC 拦截器类似 HTTP 中间件:

// 服务端一元拦截器
func loggingInterceptor(ctx context.Context, req interface{},
    info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    start := time.Now()
    resp, err := handler(ctx, req)
    log.Printf("%s took %v", info.FullMethod, time.Since(start))
    return resp, err
}

第五章:最佳实践

  1. Deadline:每次调用都设 Deadline,避免雪崩
  2. 重试:使用 gRPC Retry Policy(非幂等操作慎用)
  3. 断路器:配合 Istio/Envoy 实现 Circuit Breaking
  4. 元数据:用 gRPC Metadata 传递 traceId/userId(而非请求体)
  5. TLS:生产环境必须启用 mTLS

思考题

  1. Protobuf 为什么比 JSON 快?字段编号在其中起什么作用?
  2. gRPC 的 HTTP/2 多路复用解决了什么问题?为什么 HTTP/1.1 的连接复用(Keep-Alive)不够?
  3. grpc-gateway 如何做到一份 proto 同时提供 gRPC 和 REST 两种接口?

参考资料

暂无参考文献