前言
💡 痛点: gRPC 和 REST 怎么选?Proto3 怎么写?四种 RPC 类型怎么用?服务间通信怎么保障安全和性能?前端怎么调用 gRPC?
🎯 解决方案: 本文系统覆盖 gRPC 全栈开发:Proto3 语法与代码生成、四种 RPC 类型(Unary/ServerStreaming/ClientStreaming/BidirectionalStreaming)、元数据与拦截器、认证与 TLS、负载均衡、gRPC-Web 前端集成、NestJS + gRPC 实战、Docker Compose 生产部署。
#mermaid-svg-V9kmfUqjb8qMvQKS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V9kmfUqjb8qMvQKS .error-icon{fill:#552222;}#mermaid-svg-V9kmfUqjb8qMvQKS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V9kmfUqjb8qMvQKS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V9kmfUqjb8qMvQKS .marker.cross{stroke:#333333;}#mermaid-svg-V9kmfUqjb8qMvQKS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V9kmfUqjb8qMvQKS p{margin:0;}#mermaid-svg-V9kmfUqjb8qMvQKS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster-label text{fill:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster-label span{color:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster-label span p{background-color:transparent;}#mermaid-svg-V9kmfUqjb8qMvQKS .label text,#mermaid-svg-V9kmfUqjb8qMvQKS span{fill:#333;color:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .node rect,#mermaid-svg-V9kmfUqjb8qMvQKS .node circle,#mermaid-svg-V9kmfUqjb8qMvQKS .node ellipse,#mermaid-svg-V9kmfUqjb8qMvQKS .node polygon,#mermaid-svg-V9kmfUqjb8qMvQKS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V9kmfUqjb8qMvQKS .rough-node .label text,#mermaid-svg-V9kmfUqjb8qMvQKS .node .label text,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape .label,#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape .label{text-anchor:middle;}#mermaid-svg-V9kmfUqjb8qMvQKS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V9kmfUqjb8qMvQKS .rough-node .label,#mermaid-svg-V9kmfUqjb8qMvQKS .node .label,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape .label,#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape .label{text-align:center;}#mermaid-svg-V9kmfUqjb8qMvQKS .node.clickable{cursor:pointer;}#mermaid-svg-V9kmfUqjb8qMvQKS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V9kmfUqjb8qMvQKS .arrowheadPath{fill:#333333;}#mermaid-svg-V9kmfUqjb8qMvQKS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V9kmfUqjb8qMvQKS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V9kmfUqjb8qMvQKS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V9kmfUqjb8qMvQKS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V9kmfUqjb8qMvQKS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V9kmfUqjb8qMvQKS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster text{fill:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster span{color:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V9kmfUqjb8qMvQKS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS rect.text{fill:none;stroke-width:0;}#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape p,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape .label rect,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V9kmfUqjb8qMvQKS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V9kmfUqjb8qMvQKS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V9kmfUqjb8qMvQKS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础设施
四种 RPC 类型
Proto3 核心
Message
消息定义
Service
服务定义
Option
自定义选项
Import
导入复用
Unary
请求-响应
Server Streaming
服务端流
Client Streaming
客户端流
Bidirectional
双向流
拦截器
Auth/Logging/Retry
mTLS
双向认证
负载均衡
客户端/服务端
gRPC-Gateway
HTTP/JSON 桥接
一、Proto3 深度语法
1.1 消息定义进阶
protobuf
syntax = "proto3";
package api.v1;
option go_package = "github.com/example/api/v1;apiv1";
option java_multiple_files = true;
option java_package = "com.example.api.v1";
option java_outer_classname = "UserProtos";
// ======== 导入(可导入 google 类型)=======
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/empty.proto";
import "google/api/field_behavior.proto";
import "validate/validate.proto";
// ======== 消息定义(完整示例)=======
message User {
// 标量类型:double, float, int32, int64, uint32, uint64, sint32, sint64,
// fixed32, fixed64, sfixed32, sfixed64, bool, string, bytes
string user_id = 1 [json_name = "userId", (validate.rules).string.min_len = 1];
string username = 2 [(validate.rules).string.min_len = 3, (validate.rules).string.max_len = 50];
string email = 3 [(validate.rules).string.email = true];
// oneof(互斥字段)
oneof contact {
string phone = 4;
string wechat = 5;
}
// 枚举
UserStatus status = 6;
map<string, string> metadata = 7; // Map 类型
repeated Address addresses = 8; // repeated = 数组
// Google 标准类型
google.protobuf.Timestamp created_at = 9;
google.protobuf.Timestamp updated_at = 10;
// Optional(Proto3.15+,显式可选字段)
optional string nickname = 11;
// 保留字段(防止字段号被误用)
reserved 100, 101 to 105;
reserved "deprecated_field";
}
// ======== 枚举(带默认值处理)=======
enum UserStatus {
// 第一个枚举值必须是 0(默认值)
USER_STATUS_UNSPECIFIED = 0;
USER_STATUS_ACTIVE = 1;
USER_STATUS_INACTIVE = 2;
USER_STATUS_SUSPENDED = 3;
USER_STATUS_DELETED = 4;
// 枚举别名
option allow_alias = true;
USER_STATUS_BANNED = 3; // 别名,与 USER_STATUS_SUSPENDED 相同
}
// ======== Map 类型 ========
message Config {
map<string, string> settings = 1;
map<string, int64> counters = 2;
map<string, UserRole> permissions = 3;
}
// ======== 嵌套消息 ========
message Address {
string street = 1;
string city = 2;
string country = 3;
string postal_code = 4 [(validate.rules).string.pattern = "^\\d{5,6}$"];
Location location = 5;
message Location {
double latitude = 1 [(validate.rules).double.gte = -90, (validate.rules).double.lte = 90];
double longitude = 2 [(validate.rules).double.gte = -180, (validate.rules).double.lte = 180];
}
}
// ======== Oneof(互斥字段)=======
message LoginRequest {
oneof login_method {
string email = 1 [(validate.rules).string.email = true];
string phone = 2 [(validate.rules).string.pattern = "^\\d{10,11}$"];
string wechat_code = 3;
}
string password = 4;
}
// ======== 验证规则(buf 插件)=======
message CreateUserRequest {
string username = 1 [
(validate.rules).string.min_len = 3,
(validate.rules).string.max_len = 50,
(validate.rules).string.pattern = "^[a-zA-Z][a-zA-Z0-9_]*$"
];
string email = 2 [
(validate.rules).string.email = true
];
int32 age = 3 [
(validate.rules).int32.gte = 0,
(validate.rules).int32.lte = 150
];
repeated string roles = 4 [
(validate.rules).repeated.min_items = 1,
(validate.rules).repeated.max_items = 10,
(validate.rules).repeated.items.string.in = {"values": ["admin", "user", "guest"]}
];
map<string, string> tags = 5 [
(validate.rules).map.min_pairs = 1,
(validate.rules).map.max_pairs = 20
];
}
// ======== 自定义选项 ========
extend google.protobuf.MessageOptions {
string message_doc_url = 50001;
int32 message_version = 50002;
}
message SensitiveData {
option (message_doc_url) = "https://docs.example.com/api/sensitive-data";
option (message_version) = 2;
string ssn = 1;
string credit_card = 2;
}
// ======== gRPC Service 定义 ========
service UserService {
// Unary RPC:标准请求-响应
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
// Server Streaming RPC:服务端流
rpc ListUsers(ListUsersRequest) returns (stream User);
rpc WatchUser(GetUserRequest) returns (stream User); // 实时更新
// Client Streaming RPC:客户端流
rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);
rpc UploadAvatar(stream AvatarChunk) returns (UploadResult);
// Bidirectional Streaming RPC:双向流
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
rpc QueryData(stream QueryRequest) returns (stream QueryResult);
}
// ======== 请求/响应消息 ========
message GetUserRequest {
string user_id = 1 [(validate.rules).string.min_len = 1];
}
message GetUserResponse {
User user = 1;
}
message ListUsersRequest {
int32 page_size = 1 [(validate.rules).int32.gte = 1, (validate.rules).int32.lte = 100];
string page_token = 2;
string filter = 3;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
int32 total_count = 3;
}
// ======== 流式消息 ========
message AvatarChunk {
string upload_id = 1;
bytes data = 2;
int32 offset = 3;
string mime_type = 4;
}
message UploadResult {
string file_id = 1;
string url = 2;
int64 size = 3;
}
message ChatMessage {
string room_id = 1;
string content = 2;
google.protobuf.Timestamp timestamp = 3;
}
message QueryRequest {
string query = 1;
repeated string dimensions = 2;
}
message QueryResult {
string dimension = 1;
repeated string labels = 2;
repeated double values = 3;
}
1.2 Buf 管理 Proto
yaml
# ======== buf.yaml(项目根目录)=======
version: v1
name: buf.build/example/api
lint:
use:
- DEFAULT
- COMMENTS
- FILE_LOWER_SNAKE_CASE
except:
- PACKAGE_VERSION_SUFFIX
disallow_comment_ignores: true
breaking:
use:
- FILE
ignore:
- api/v1/user.proto
---
# buf.gen.yaml(生成配置)
version: v1
managed:
enabled: true
override:
- file_option: go_package_prefix
value: github.com/example/api
plugins:
- remote: buf.build/protocolbuffers/go
out: gen/go
opt:
- paths=source_relative
- remote: buf.build/grpc/go
out: gen/go
opt:
- paths=source_relative
- remote: buf.build/grpc-ecosystem/gateway
out: gen/go
opt:
- paths=source_relative
- remote: buf.build/bufcommunity/protoc-gen-validate-go
out: gen/go
opt:
- paths=source_relative
- remote: buf.build/openapitools/openapiv2
out: gen/openapi
- remote: buf.build/community/ts-proto
out: gen/ts
opt:
- useDate=string
- esModuleInterop=true
bash
# ======== 构建命令 ========
# 安装 buf
# brew install buf (macOS)
# 或: scoop install buf (Windows)
# 初始化模块
buf mod init
# lint 检查
buf lint
# breaking change 检查
buf breaking --against '.git#branch=main'
# 生成代码
buf generate
# 完整 CI/CD
# .github/workflows/grpc.yml
# on: [push, pull_request]
# jobs:
# build:
# steps:
# - uses: actions/checkout@v4
# - uses: buf-build/buf-setup@v1
# - run: buf lint
# - run: buf breaking --against '.git#branch=main'
# - run: buf generate
二、四种 RPC 类型实现
2.1 Go gRPC 服务端
go
// ======== gRPC 服务端实现(Go)=======
package server
import (
"context"
"io"
"log"
"sync"
"time"
apiv1 "github.com/example/api/v1/gen/go"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
)
// UserServiceServer gRPC 服务实现
type UserServiceServer struct {
apiv1.UnimplementedUserServiceServer
users map[string]*apiv1.User
mu sync.RWMutex
}
func NewUserServiceServer() *UserServiceServer {
return &UserServiceServer{
users: make(map[string]*apiv1.User),
}
}
// ======== Unary RPC:获取用户 ========
func (s *UserServiceServer) GetUser(
ctx context.Context,
req *apiv1.GetUserRequest,
) (*apiv1.GetUserResponse, error) {
// 从 metadata 获取请求追踪 ID
md, _ := metadata.FromIncomingContext(ctx)
traceID := md.Get("x-trace-id")
log.Printf("[GetUser] user_id=%s trace_id=%v", req.UserId, traceID)
s.mu.RLock()
user, ok := s.users[req.UserId]
s.mu.RUnlock()
if !ok {
return nil, status.Errorf(codes.NotFound, "user %s not found", req.UserId)
}
return &apiv1.GetUserResponse{User: user}, nil
}
// ======== Unary RPC:创建用户 ========
func (s *UserServiceServer) CreateUser(
ctx context.Context,
req *apiv1.CreateUserRequest,
) (*apiv1.CreateUserResponse, error) {
// 获取客户端信息(用于审计)
if p, ok := peer.FromContext(ctx); ok {
log.Printf("[CreateUser] client=%s user=%s", p.Addr, req.Username)
}
user := &apiv1.User{
UserId: generateUUID(),
Username: req.Username,
Email: req.Email,
Status: apiv1.UserStatus_USER_STATUS_ACTIVE,
CreatedAt: timestampNow(),
}
s.mu.Lock()
s.users[user.UserId] = user
s.mu.Unlock()
log.Printf("[CreateUser] created user_id=%s", user.UserId)
return &apiv1.CreateUserResponse{User: user}, nil
}
// ======== Server Streaming RPC:列表用户 ========
func (s *UserServiceServer) ListUsers(
req *apiv1.ListUsersRequest,
stream apiv1.UserService_ListUsersServer,
) error {
s.mu.RLock()
allUsers := make([]*apiv1.User, 0, len(s.users))
for _, u := range s.users {
allUsers = append(allUsers, u)
}
s.mu.RUnlock()
// 分页
pageSize := int(req.PageSize)
if pageSize <= 0 {
pageSize = 10
}
start := 0
if req.PageToken != "" {
start = parsePageToken(req.PageToken)
}
for i := start; i < len(allUsers) && i < start+pageSize; i++ {
if err := stream.Send(allUsers[i]); err != nil {
return status.Errorf(codes.Internal, "send error: %v", err)
}
time.Sleep(100 * time.Millisecond) // 模拟延迟
}
return nil
}
// ======== Server Streaming RPC:实时监控用户变化 ========
func (s *UserServiceServer) WatchUser(
req *apiv1.GetUserRequest,
stream apiv1.UserService_WatchUserServer,
) error {
ctx := stream.Context()
s.mu.RLock()
user, ok := s.users[req.UserId]
s.mu.RUnlock()
if !ok {
return status.Errorf(codes.NotFound, "user %s not found", req.UserId)
}
// 立即发送当前状态
if err := stream.Send(user); err != nil {
return err
}
// 模拟实时更新(每 5 秒)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
s.mu.RLock()
user = s.users[req.UserId]
s.mu.RUnlock()
if err := stream.Send(user); err != nil {
return err
}
}
}
}
// ======== Client Streaming RPC:批量创建用户 ========
func (s *UserServiceServer) BatchCreateUsers(
stream apiv1.UserService_BatchCreateUsersServer,
) error {
created := []*apiv1.User{}
for {
req, err := stream.Recv()
if err == io.EOF {
// 客户端发送完毕
break
}
if err != nil {
return status.Errorf(codes.Internal, "recv error: %v", err)
}
user := &apiv1.User{
UserId: generateUUID(),
Username: req.Username,
Email: req.Email,
Status: apiv1.UserStatus_USER_STATUS_ACTIVE,
CreatedAt: timestampNow(),
}
s.mu.Lock()
s.users[user.UserId] = user
s.mu.Unlock()
created = append(created, user)
log.Printf("[BatchCreate] created user_id=%s", user.UserId)
}
// 发送汇总响应
return stream.SendAndClose(&apiv1.BatchCreateResponse{
CreatedCount: int32(len(created)),
Users: created,
})
}
// ======== Client Streaming RPC:文件上传 ========
func (s *UserServiceServer) UploadAvatar(
stream apiv1.UserService_UploadAvatarServer,
) error {
var uploadID string
var data []byte
var mimeType string
for {
req, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return status.Errorf(codes.Internal, "recv error: %v", err)
}
if uploadID == "" {
uploadID = req.UploadId
mimeType = req.MimeType
}
data = append(data, req.Data...)
log.Printf("[Upload] upload_id=%s offset=%d bytes=%d",
req.UploadId, req.Offset, len(req.Data))
}
// 保存文件(实际存储逻辑)
fileID := "avatar_" + uploadID
// 发送完成结果
return stream.SendAndClose(&apiv1.UploadResult{
FileId: fileID,
Url: "/uploads/" + fileID,
Size: int64(len(data)),
})
}
// ======== Bidirectional Streaming RPC:聊天 ========
func (s *UserServiceServer) Chat(
stream apiv1.UserService_ChatServer,
) error {
ctx := stream.Context()
userRooms := make(map[string]string) // clientID -> roomID
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
req, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return status.Errorf(codes.Internal, "recv error: %v", err)
}
roomID := req.RoomId
if _, ok := userRooms["client"]; !ok {
userRooms["client"] = roomID
}
log.Printf("[Chat] room=%s msg=%s", roomID, req.Content)
// 处理消息
response := &apiv1.ChatMessage{
RoomId: roomID,
Content: "Echo: " + req.Content,
Timestamp: timestampNow(),
}
if err := stream.Send(response); err != nil {
return status.Errorf(codes.Internal, "send error: %v", err)
}
}
}
// ======== Bidirectional Streaming RPC:查询引擎 ========
func (s *UserServiceServer) QueryData(
stream apiv1.UserService_QueryDataServer,
) error {
ctx := stream.Context()
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
req, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return status.Errorf(codes.Internal, "recv error: %v", err)
}
// 模拟查询(按维度分批返回)
results := simulateQuery(req.Query, req.Dimensions)
for _, r := range results {
if err := stream.Send(r); err != nil {
return status.Errorf(codes.Internal, "send error: %v", err)
}
}
}
}
2.2 gRPC 客户端
go
// ======== gRPC 客户端实现(Go)=======
package client
import (
"context"
"io"
"log"
"time"
apiv1 "github.com/example/api/v1/gen/go"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
)
// ======== 客户端配置 ========
type Config struct {
Address string
TLSEnabled bool
TLSCert string
Token string // Bearer token
DialOpts []grpc.DialOption
}
// ======== 创建连接 ========
func NewGRPCConnection(ctx context.Context, cfg Config) (*grpc.ClientConn, error) {
var opts []grpc.DialOption
// 传输层
if cfg.TLSEnabled {
creds, err := credentials.NewClientTLSFromFile(cfg.TLSCert, "")
if err != nil {
return nil, err
}
opts = append(opts, grpc.WithTransportCredentials(creds))
} else {
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
// 压缩
opts = append(opts, grpc.WithDefaultCallOptions(
grpc.UseCompressor(gzip.Name),
))
// 超时
opts = append(opts, grpc.WithBlock())
opts = append(opts, grpc.WithTimeout(10*time.Second))
// 连接
conn, err := grpc.NewClient(cfg.Address, opts...)
if err != nil {
return nil, err
}
return conn, nil
}
// ======== 带认证的客户端 ========
type AuthClient struct {
conn *grpc.ClientConn
client apiv1.UserServiceClient
token string
}
func NewAuthClient(ctx context.Context, address, token string) (*AuthClient, error) {
conn, err := grpc.NewClient(address,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return nil, err
}
return &AuthClient{
conn: conn,
client: apiv1.NewUserServiceClient(conn),
token: token,
}, nil
}
// ======== 带 metadata 的调用 ========
func (c *AuthClient) GetUserWithAuth(ctx context.Context, userID string) (*apiv1.User, error) {
// 注入认证 token
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
"authorization", "Bearer "+c.token,
"x-client-version", "1.0.0",
"x-request-id", generateUUID(),
))
return c.client.GetUser(ctx, &apiv1.GetUserRequest{UserId: userID})
}
// ======== Server Streaming 客户端 ========
func (c *AuthClient) ListUsersStream(ctx context.Context, pageSize int32) error {
stream, err := c.client.ListUsers(ctx, &apiv1.ListUsersRequest{
PageSize: pageSize,
})
if err != nil {
return err
}
for {
user, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
log.Printf("[ListUsers] user_id=%s username=%s", user.UserId, user.Username)
}
return nil
}
// ======== Client Streaming 客户端 ========
func (c *AuthClient) BatchCreate(ctx context.Context, users []*apiv1.CreateUserRequest) error {
stream, err := c.client.BatchCreateUsers(ctx)
if err != nil {
return err
}
// 发送所有用户
for _, req := range users {
if err := stream.Send(req); err != nil {
return status.Errorf(codes.Internal, "send error: %v", err)
}
}
// 关闭流并获取响应
resp, err := stream.CloseAndRecv()
if err != nil {
return err
}
log.Printf("[BatchCreate] created %d users", resp.CreatedCount)
return nil
}
// ======== Bidirectional Streaming 客户端 ========
func (c *AuthClient) Chat(ctx context.Context, roomID string, messages []string) error {
stream, err := c.client.Chat(ctx)
if err != nil {
return err
}
// Goroutine 用于接收响应
go func() {
for {
msg, err := stream.Recv()
if err == io.EOF {
return
}
if err != nil {
log.Printf("[Chat] recv error: %v", err)
return
}
log.Printf("[Chat] received: %s", msg.Content)
}
}()
// 发送消息
for _, msg := range messages {
if err := stream.Send(&apiv1.ChatMessage{
RoomId: roomID,
Content: msg,
}); err != nil {
return err
}
time.Sleep(500 * time.Millisecond)
}
return stream.CloseSend()
}
// ======== 负载均衡(客户端负载均衡)=======
func (c *AuthClient) WithLoadBalancing(ctx context.Context) error {
// 使用 DNS Resolver + Round Robin
// 或使用 grpclb(gRPC Load Balancer)
// grpclb 方案:
// conn, _ := grpc.Dial("passthrough:///grpclb:///backend-service",
// grpc.WithBalancerName("round_robin"))
// xds 方案(推荐,替代 grpclb):
conn, err := grpc.NewClient(
"xds:///user-service", // xds:///服务名,自动从控制面获取地址
grpc.WithResolvers(), // 启用 xDS
)
if err != nil {
return err
}
defer conn.Close()
client := apiv1.NewUserServiceClient(conn)
// 每次调用自动负载均衡
return nil
}
三、拦截器
3.1 服务端拦截器
go
// ======== 服务端拦截器 ========
package interceptor
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ======== 日志拦截器 ========
func LoggingInterceptor() grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
start := time.Now()
// 提取 metadata
md, _ := metadata.FromIncomingContext(ctx)
traceID := md.Get("x-trace-id")
log.Printf("[GRPC] method=%s trace_id=%v req=%T started_at=%s",
info.FullMethod, traceID, req, start.Format(time.RFC3339))
resp, err := handler(ctx, req)
duration := time.Since(start)
code := status.Code(err)
log.Printf("[GRPC] method=%s code=%s duration=%v err=%v",
info.FullMethod, code, duration, err)
return resp, err
}
}
// ======== 认证拦截器 ========
func AuthInterceptor(validators ...TokenValidator) grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
// 跳过健康检查和认证豁免的方法
if isExemptMethod(info.FullMethod) {
return handler(ctx, req)
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "missing metadata")
}
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, status.Errorf(codes.Unauthenticated, "missing token")
}
token := tokens[0]
if len(token) > 7 && token[:7] == "Bearer " {
token = token[7:]
}
// 验证 token
for _, v := range validators {
userID, err := v.Validate(token)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid token: %v", err)
}
ctx = context.WithValue(ctx, "user_id", userID)
}
return handler(ctx, req)
}
}
// ======== 限流拦截器(Token Bucket)=======
func RateLimitInterceptor(rate int, burst int) grpc.UnaryServerInterceptor {
limiter := NewTokenBucketLimiter(rate, burst)
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
if !limiter.Allow() {
return nil, status.Errorf(codes.ResourceExhausted,
"rate limit exceeded: %d req/s, burst %d", rate, burst)
}
return handler(ctx, req)
}
}
// ======== 链式拦截器(多个拦截器组合)=======
func ChainUnaryInterceptors(
interceptors ...grpc.UnaryServerInterceptor,
) grpc.ServerOption {
return grpc.ChainUnaryInterceptor(interceptors...)
}
// ======== 服务端注册(带拦截器)=======
func RegisterServer(grpcServer *grpc.Server) {
server := NewUserServiceServer()
grpcServer.RegisterService(&apiv1.UserService_ServiceDesc, server)
// 应用链式拦截器
// 注意:拦截器在 NewServer 时传入,而不是 RegisterService
}
3.2 客户端拦截器
go
// ======== 客户端拦截器 ========
package interceptor
import (
"context"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// ======== 重试拦截器 ========
func RetryUnaryInterceptor(maxRetries int, backoff BackoffStrategy) grpc.UnaryClientInterceptor {
return func(
ctx context.Context,
method string,
req, reply interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
var lastErr error
for attempt := 0; attempt <= maxRetries; attempt++ {
if attempt > 0 {
wait := backoff.Duration(attempt)
select {
case <-time.After(wait):
case <-ctx.Done():
return ctx.Err()
}
}
lastErr = invoker(ctx, method, req, reply, cc, opts...)
// 只对可重试的错误重试
if lastErr == nil {
return nil
}
s := status.Convert(lastErr)
if !isRetryableCode(s.Code()) {
return lastErr
}
// 指数退避
}
return lastErr
}
}
func isRetryableCode(code codes.Code) bool {
return code == codes.Unavailable ||
code == codes.ResourceExhausted ||
code == codes.Internal
}
// ======== 超时拦截器 ========
func TimeoutUnaryInterceptor(timeout time.Duration) grpc.UnaryClientInterceptor {
return func(
ctx context.Context,
method string,
req, reply interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return invoker(ctx, method, req, reply, cc, opts...)
}
}
// ======== 客户端链式拦截器 ========
func ChainUnaryClientInterceptors(
interceptors ...grpc.UnaryClientInterceptor,
) grpc.DialOption {
return grpc.WithUnaryInterceptor(
grpc.ChainUnaryInterceptor(interceptors...),
)
}
四、gRPC-Web 前端集成
4.1 Envoy 网关配置
yaml
# ======== Envoy gRPC-Web 配置 ========
# envoy-grpc-web.yaml
static_resources:
listeners:
- name: grpc_web_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: grpc_web
route_config:
name: grpc_web_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
# gRPC-Web 路由
- match: { prefix: "/api.v1.UserService" }
route:
cluster: grpc_backend
timeout: 300s
# gRPC-Web 必须设置
grpc: {}
# gRPC-JSON 转码路由(可选)
- match: { prefix: "/v1/users" }
route:
cluster: grpc_backend
timeout: 60s
grpc: {}
http_filters:
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
allow_origin_string_match:
- safe_regex:
regex: ".*"
allow_methods: GET, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-grpc-web,x-user-agent,authorization
exposed_headers: grpc-status,grpc-message
max_age: "86400"
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: grpc_backend
type: STRICT_DNS
lb_policy: ROUND_ROBIN
typed_dns_resolution_config:
dns_resolver_name: envoy.network.dns_resolver.cares
dns_lookup_family: V4_ONLY
targets:
- socket_address:
address: grpc-backend
port_value: 50051
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: grpc-backend.internal.com
4.2 TypeScript 客户端
typescript
// ======== TypeScript gRPC-Web 客户端 ========
// 安装:npm install @improbable-eng/grpc-web google-protobuf @types/google-protobuf
import { grpc } from '@improbable-eng/grpc-web';
import { UserServiceClient } from './generated/api/v1/UserServiceClientPb';
import {
GetUserRequest,
GetUserResponse,
ListUsersRequest,
User,
} from './generated/api/v1/user_pb';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
// 创建 gRPC-Web 客户端
const client = new UserServiceClient('http://localhost:8080', {
transport: grpc.CrossBrowserHttpTransport({ withCredentials: true }),
});
// ======== Unary 调用 ========
async function getUser(userId: string): Promise<User | null> {
const req = new GetUserRequest();
req.setUserId(userId);
return new Promise((resolve, reject) => {
client.getUser(
req,
grpc.metadataConstructor({
Authorization: `Bearer ${getAuthToken()}`,
'X-Request-ID': crypto.randomUUID(),
}),
(err: grpc.RpcError | null, response: GetUserResponse | null) => {
if (err) {
console.error('gRPC Error:', err.code, err.message);
reject(err);
return;
}
resolve(response?.getUser() || null);
}
);
});
}
// ======== Server Streaming 调用 ========
function listUsersStream(pageSize: number): void {
const req = new ListUsersRequest();
req.setPageSize(pageSize);
const stream = client.listUsers(req, grpc.metadataConstructor({
Authorization: `Bearer ${getAuthToken()}`,
}));
stream.on('data', (response: User) => {
console.log('User:', {
userId: response.getUserId(),
username: response.getUsername(),
email: response.getEmail(),
});
});
stream.on('status', (status: grpc.Status) => {
console.log('Stream status:', status.code, status.details);
});
stream.on('error', (err: grpc.RpcError) => {
console.error('Stream error:', err.code, err.message);
});
stream.on('end', () => {
console.log('Stream ended');
});
}
// ======== Client Streaming 调用(批量上传)=======
async function batchCreateUsers(users: Array<{username: string; email: string}>): Promise<void> {
const stream = client.batchCreateUsers(
grpc.metadataConstructor({ Authorization: `Bearer ${getAuthToken()}` })
);
// 发送所有请求
for (const user of users) {
const req = new CreateUserRequest();
req.setUsername(user.username);
req.setEmail(user.email);
stream.write(req);
}
return new Promise((resolve, reject) => {
stream.on('data', (response: BatchCreateResponse) => {
console.log('Batch created:', response.getCreatedCount());
});
stream.on('status', (status: grpc.Status) => {
if (status.code !== grpc.Code.OK) {
reject(new Error(status.details));
}
});
stream.on('error', reject);
stream.on('end', resolve);
// 关闭流
stream.end();
});
}
// ======== Bidirectional Streaming 调用(聊天)=======
function chatStream(roomId: string): {
send: (content: string) => void;
onMessage: (handler: (msg: string) => void) => void;
close: () => void;
} {
const stream = client.chat(grpc.metadataConstructor({
Authorization: `Bearer ${getAuthToken()}`,
}));
const handlers: Array<(msg: string) => void> = [];
stream.on('data', (response: ChatMessage) => {
const content = response.getContent();
handlers.forEach(h => h(content));
});
return {
send: (content: string) => {
const req = new ChatMessage();
req.setRoomId(roomId);
req.setContent(content);
stream.write(req);
},
onMessage: (handler: (msg: string) => void) => {
handlers.push(handler);
},
close: () => stream.end(),
};
}
// ======== React Hook 封装 ========
import { useState, useEffect, useCallback } from 'react';
function useGRPCUser(userId: string | null) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const fetchUser = useCallback(async () => {
if (!userId) return;
setLoading(true);
setError(null);
try {
const result = await getUser(userId);
setUser(result);
} catch (e) {
setError(e as Error);
} finally {
setLoading(false);
}
}, [userId]);
useEffect(() => {
fetchUser();
}, [fetchUser]);
return { user, loading, error, refetch: fetchUser };
}
// ======== Vue 3 Composition API 封装 ========
import { ref, onMounted } from 'vue';
function useGRPCUserVue(userId: Ref<string | null>) {
const user = ref<User | null>(null);
const loading = ref(false);
const error = ref<Error | null>(null);
const fetchUser = async () => {
if (!userId.value) return;
loading.value = true;
error.value = null;
try {
user.value = await getUser(userId.value);
} catch (e) {
error.value = e as Error;
} finally {
loading.value = false;
}
};
onMounted(fetchUser);
return { user, loading, error, refetch: fetchUser };
}
4.3 gRPC-Gateway(HTTP/JSON 转码)
yaml
# ======== gRPC-Gateway 生成配置 ========
# buf.gen.yaml 中添加:
# - remote: buf.build/grpc-ecosystem/gateway
# out: gen/go
# opt:
# - paths=source_relative
# ======== 生成的 HTTP 端点 ========
/*
gRPC-Gateway 根据 proto 注解自动生成 HTTP 路由:
// HTTP GET /api/v1/users/{user_id}
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (google.api.http) = {
get: "/api/v1/users/{user_id}"
};
}
// HTTP POST /api/v1/users
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
option (google.api.http) = {
post: "/api/v1/users"
body: "*"
};
}
// HTTP LIST /api/v1/users
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
option (google.api.http) = {
get: "/api/v1/users"
};
}
*/
# ======== HTTP 请求示例 ========
# curl http://localhost:8080/api/v1/users/user-123
curl -X GET http://localhost:8080/api/v1/users/user-123 \
-H "Authorization: Bearer $TOKEN"
# curl -X POST http://localhost:8080/api/v1/users
curl -X POST http://localhost:8080/api/v1/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "test@example.com"
}'
五、生产部署
5.1 Docker Compose 生产部署
yaml
# ======== docker-compose.grpc.yml ========
version: "3.9"
services:
# ======== gRPC 服务 ========
user-service:
image: example/user-service:v1.0.0
container_name: user-service
ports:
- "50051:50051"
- "9090:9090" # Prometheus metrics
environment:
- GRPC_PORT=50051
- DATABASE_URL=postgres://postgres:password@postgres:5432/users
- REDIS_URL=redis://redis:6379/0
- JAEGER_ENDPOINT=jaeger:4317
- LOG_LEVEL=info
healthcheck:
test: ["CMD-SHELL", "grpc_health_probe -addr=:50051"]
interval: 10s
timeout: 5s
retries: 3
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- grpc-network
restart: unless-stopped
# ======== Envoy 反向代理 ========
envoy:
image: envoyproxy/envoy:v1.29.0
container_name: envoy
ports:
- "8080:8080" # HTTP/gRPC-Web
- "9901:9901" # Admin
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml:ro
depends_on:
- user-service
networks:
- grpc-network
restart: unless-stopped
# ======== 数据库 ========
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: users
volumes:
- postgres-data:/var/lib/postgresql/data
command: >
-c max_connections=200
-c shared_buffers=256MB
-c effective_cache_size=768MB
-c work_mem=16MB
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 3
networks:
- grpc-network
restart: unless-stopped
# ======== Redis ========
redis:
image: redis:7.2-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
networks:
- grpc-network
restart: unless-stopped
# ======== Prometheus ========
prometheus:
image: prom/prometheus:v2.48
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
ports:
- "9091:9090"
networks:
- grpc-network
restart: unless-stopped
# ======== Grafana ========
grafana:
image: grafana/grafana:10.2
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
depends_on:
- prometheus
networks:
- grpc-network
restart: unless-stopped
networks:
grpc-network:
driver: bridge
volumes:
postgres-data:
redis-data:
grafana-data:
yaml
# ======== Prometheus 抓取 gRPC 指标 ========
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'grpc-services'
static_configs:
- targets:
- 'user-service:9090'
metrics_path: /metrics
scheme: http
# gRPC 指标需要配置
params:
service: ['user-service']
六、Checklist 总结
□ Proto3 语法
□ 消息定义(标量/枚举/Oneof/Map/嵌套)
□ 保留字段(reserved)防止误用
□ 验证规则(validate.proto/buf)
□ Buf 管理(lint/breaking/gen)
□ Buf CI/CD 配置
□ 四种 RPC 类型
□ Unary:请求-响应(同步)
□ Server Streaming:服务端流(实时推送)
□ Client Streaming:客户端流(批量上传)
□ Bidirectional Streaming:双向流(实时交互)
□ 服务端实现
□ Handler 实现(Go/Java/TS)
□ Context 传递(metadata/peer)
□ 流式处理(Send/Recv/CloseAndRecv)
□ 错误处理(gRPC status codes)
□ 客户端实现
□ 连接创建(TLS/insecure)
□ Unary/Streaming 调用
□ 重试与超时
□ 客户端负载均衡(xDS/RoundRobin)
□ 拦截器
□ 服务端:Logging/Auth/RateLimit/Recovery
□ 客户端:Retry/Timeout/Auth/Metrics
□ 链式拦截器组合
□ gRPC-Web 前端集成
□ Envoy gRPC-Web 网关
□ CORS 配置
□ TypeScript/JS 客户端
□ React Hook / Vue Composition API 封装
□ gRPC-Gateway HTTP/JSON 转码
□ 生产部署
□ TLS/mTLS 配置
□ 健康检查(grpc_health_probe)
□ Prometheus + Grafana 监控
□ Docker Compose 编排
□ 限流与熔断
总结
gRPC vs REST 全方位对比:
| 维度 | gRPC | REST (HTTP/JSON) |
|---|---|---|
| 协议 | HTTP/2 + Protobuf | HTTP/1.1 or HTTP/2 |
| 序列化 | Protobuf(二进制,高效) | JSON(文本,易读) |
| 传输效率 | 高(压缩率高,序列化快) | 低(JSON 体积大) |
| 类型安全 | 强(Proto 定义,代码生成) | 弱(无 schema) |
| 流式支持 | 原生(双向流) | 需 WebSocket/SSE |
| 浏览器支持 | 需 gRPC-Web + Envoy | 原生支持 |
| 调试 | 需工具(grpcurl/evans) | curl 即可 |
| 适用场景 | 微服务内部、服务间通信 | 浏览器/移动端 API |
gRPC 适用场景:
- 微服务间高性能通信
- 双向流式实时交互(聊天、监控推送)
- 跨语言服务调用(强类型代码生成)
- 低延迟系统(金融、游戏后端)
REST 适用场景:
- 公开 API(外部开发者)
- 需要浏览器直接调用
- 简单 CRUD 操作
- 需要人类可读的调试信息
下一步推荐:
- gRPC 负载均衡深度实战(xDS/LRS/Lua 扩展)
- gRPC 安全实战(mTLS + JWT + Certificate Policy)
- NestJS + gRPC + Microservices 全家桶