学习碎笔-关于gRPC

一:gRPC 概述与核心特性

gRPC 是 Google 开发的高性能、跨语言的远程过程调用(RPC)框架,基于 HTTP/2 和 Protocol Buffers(Protobuf)实现。其核心特性包括:

  1. 多语言支持:服务端与客户端可使用不同语言(如 Go、Java、Python)实现,通过 Protobuf 定义统一接口。
  2. 高效传输:HTTP/2 提供多路复用、头部压缩等优化,Protobuf 二进制编码减少数据体积。
  3. 流式通信:支持单向/双向流(Streaming),适用于实时数据传输场景。
  4. 强类型约束:接口通过 Protobuf 预先定义,避免传统 REST API 的松散类型问题。

Go 案例准备工作
在 Go 中使用 gRPC 需以下工具:

  • protoc:Protobuf 编译器(生成代码模板)
    下载地址:点这里,下载好解压后添加到环境变量里面。
    github示例
  • grpc-go 库:提供 gRPC 核心实现
    安装命令:
    1. 安装gRPC的核心库
    go install google.golang.org/grpc@latest
    
    1. 安装代码生成工具
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
    

Go项目中关于grcpde一般目录结构

G:.
├───api
├───grpc                  # 新建目录
│   ├───pb               # 存放 .proto 文件和生成的代码
│   ├───server           # gRPC server 实现
│   └───client           # gRPC client 代码(如果需要)
├───config
├───db
└───internal
  1. gRPC 相关代码最好独立管理,方便维护和扩展
  2. pb 目录存放 protocol buffers 定义和生成的代码
  3. server 目录包含具体的服务实现
  4. client 用来存放调用其他grpc服务端的代码
    因为一个项目中一般不止会被调用,也可能调用其他的grpc服务端,所以serverclient一般都会存在

二:定义服务接口与代码生成

要构建 gRPC 服务,首先需用 Protobuf 定义服务接口。以下是一个简单的 .proto 文件示例:

// service.proto
syntax = "proto3";
package example;

service CalculatorService {
  rpc Add (AddRequest) returns (AddResponse);
}

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

message AddResponse {
  int32 result = 1;
}

关键元素解析:

  • service:声明服务名称与方法(Add 为 RPC 方法名)
  • message:定义请求/响应数据结构(字段编号影响二进制序列化顺序)

生成 Go 代码
使用 protoc 编译器生成对应 Go 代码:

protoc --proto_path=. --go_out=./gen service.proto

生成的 service.pb.go 文件包含:

  • AddRequestAddResponse 结构体
  • CalculatorServiceClientCalculatorServiceServer 接口

服务端实现(Go 代码)
创建服务端需实现 Protobuf 生成的接口:

package main

import (
  "gen/example"
  "grpc"
  "net"
)

type CalculatorServer struct{}

func (s *CalculatorServer) Add(ctx context.Context, req *example.AddRequest) (*example.AddResponse, error) {
  result := req.A + req.B
  return &example.AddResponse{Result: result}, nil
}

func main() {
  lis, _ := net.Listen("tcp", ":5000")
  server := grpc.NewServer()
  example.RegisterCalculatorServiceServer(server, &CalculatorServer{})
  server.Serve(lis)
}

要点说明:

  • 服务端结构体需实现所有 RPC 方法(此处仅 Add
  • RegisterCalculatorServiceServer 将实现绑定到 gRPC 服务器

三:客户端实现与流式通信案例

客户端调用示例(Go 代码)
客户端通过生成的 CalculatorServiceClient 调用远程方法:

package main

import (
  "context"
  "gen/example"
  "grpc"
  "log"
)

func main() {
  conn, err := grpc.Dial("localhost:5000", grpc.WithInsecure())
  if err != nil {
    log.Fatal(err)
  }
  defer conn.Close()

  client := example.NewCalculatorServiceClient(conn)
  req := &example.AddRequest{A: 10, B: 20}
  res, err := client.Add(context.Background(), req)
  if err != nil {
    log.Fatal(err)
  }
  log.Printf("Result: %d", res.Result) // 输出 30
}

关键步骤:

  • grpc.Dial 建立连接(生产环境应启用 TLS 替代 WithInsecure
  • 客户端调用方法需传递 context 对象(用于超时控制等)

流式通信实战:斐波那契数列生成
gRPC 流式通信适合实时数据场景。以下为双向流示例:

  1. Proto 定义(追加流方法)
service CalculatorService {
  // ...原有 Add 方法
  rpc GenerateFibonacci (FibonacciRequest) returns (stream FibonacciResponse);
}

message FibonacciRequest {
  int32 max_count = 1;
}

message FibonacciResponse {
  int32 value = 1;
}
  • stream 关键字标记响应为流式(服务端持续发送)
  1. 服务端流实现(Go 代码)
func (s *CalculatorServer) GenerateFibonacci(req *example.FibonacciRequest, stream example.CalculatorService_GenerateFibonacciServer) error {
  a, b := 0, 1
  for count := 0; count < req.MaxCount; count++ {
    if err := stream.Send(&example.FibonacciResponse{Value: a}); err != nil {
      return err
    }
    a, b = b, a+b
  }
  return nil
}
  • 通过 stream.Send 循环发送数列值
  1. 客户端流接收(Go 代码)
stream, err := client.GenerateFibonacci(context.Background(), &example.FibonacciRequest{MaxCount: 5})
if err != nil {
  log.Fatal(err)
}
for {
  res, err := stream.Recv()
  if err == io.EOF {
    break
  }
  log.Printf("Fibonacci: %d", res.Value) // 依次接收 0,1,1,2,3
}
  • Recv() 循环读取直到流结束(io.EOF 信号)

流式应用场景

  • 实时日志推送
  • 大规模数据集分块传输
  • 双向交互(如聊天协议)

四:实现 gRPC TLS 加密传输与 Token 认证

1. TLS 加密传输实现(传输层安全)

步骤 1: 生成 TLS 证书(示例使用自签名证书)

# 生成 CA 证书
openssl req -x509 -newkey rsa:2048 -days 365 -out ca.crt -keyout ca.key -subj "/CN=MyCA"

# 生成服务端证书
openssl req -x509 -newkey rsa:2048 -days 365 -out server.crt -keyout server.key -subj "/CN=localhost" -CA ca.crt -CAkey ca.key

步骤 2: 服务端 TLS 配置(Go 代码)

import (
  "grpc"
  "grpc/credentials"
)

func main() {
  // 加载服务端证书
  cert, err := credentials.NewServerCertFromFile("server.crt", "server.key")
  if err != nil {
    log.Fatal(err)
  }

  // 创建 gRPC 服务器并启用 TLS
  server := grpc.NewServer(
    grpc.WithTransportCredentials(cert),
  )

  // 注册服务逻辑...
  server.Start(":5000")
}

步骤 3: 客户端 TLS 连接(Go 代码)

import (
  "grpc"
  "grpc/credentials"
)

func main() {
  // 加载 CA 证书(验证服务端身份)
  creds, err := credentials.NewClientCertFromCAFile("ca.crt")
  if err != nil {
    log.Fatal(err)
  }

  // 建立 TLS 加密连接
  conn, err := grpc.Dial("localhost:5000", 
    grpc.WithTransportCredentials(creds),
  )
  defer conn.Close()

  // 调用服务方法...
}

关键点

  • 生产环境应使用正式 CA 颁发的证书(如 Let’s Encrypt)
  • 客户端需信任服务端证书的 CA(此处通过 ca.crt 验证)

2. Token 认证实现(应用层安全)

步骤 1: 客户端添加 Token(拦截器注入元数据)

import (
  "grpc"
  "grpc/metadata"
)

// 客户端拦截器:注入 Authorization 头
func authInterceptor(ctx context.Context, method string, req interface{}, reply interface{}, conn *grpc.ClientConn, opts ...grpc.CallOption) error {
  // 创建元数据并添加 Token
  md := metadata.New()
  md.Append("authorization", "Bearer my-secret-token")

  // 将元数据绑定到上下文
  ctx = metadata.NewOutgoingContext(ctx, md)

  // 执行原始调用
  return conn.Invoke(ctx, method, req, reply, opts...)
}

// 使用拦截器连接
conn := grpc.Dial("localhost:5000", 
  grpc.WithTransportCredentials(creds),
  grpc.WithUnaryInterceptor(authInterceptor),
)

步骤 2: 服务端 Token 验证(拦截器检查元数据)

import (
  "grpc"
  "grpc/metadata"
  "context"
)

// 服务端拦截器:校验 Token
func authCheckInterceptor(ctx context.Context, req interface{}, info *grpc.ServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  // 从上下文提取元数据
  md, ok := metadata.FromContext(ctx)
  if !ok {
    return nil, grpc.Errorf(grpc.StatusUnauthenticated, "缺少元数据")
  }

  // 获取 Authorization 头
  tokens := md.Get("authorization")
  if len(tokens) == 0 {
    return nil, grpc.Errorf(grpc.StatusUnauthenticated, "未提供 Token")
  }

  // 验证 Token 有效性(示例简化逻辑)
  if tokens[0] != "Bearer my-secret-token" {
    return nil, grpc.Errorf(grpc.StatusUnauthenticated, "无效 Token")
  }

  // 调用原始处理逻辑
  return handler(ctx, req)
}

// 注册拦截器
server := grpc.NewServer(
  grpc.WithTransportCredentials(cert),
  grpc.WithUnaryInterceptor(authCheckInterceptor),
)

验证失败处理

  • 客户端将收到 grpc.StatusUnauthenticated 错误(代码 401)
  • 可扩展拦截器实现更复杂的鉴权逻辑(如 JWT 解析、过期检查)

3. 双重安全组合(TLS + Token)

  • 传输层:TLS 确保数据加密与服务器身份可信
  • 应用层:Token 认证实现业务级访问控制
  • 拦截器顺序:服务端应先执行 Token 校验,再处理业务逻辑

补充建议

  1. Token 管理
    • 使用 JWT 替代静态 Token,添加过期时间与签名校验
    • 定期轮换 Token 降低泄露风险
  2. 证书动态加载
    • 监听文件变化(如 fsnotify)实现证书热更新
    • 避免服务重启中断现有连接

通过上述实现,可在 Go 语言构建同时具备传输加密应用认证的 gRPC 安全通信体系。

五:高级特性与部署实践

1. 拦截器(Interceptor)实战
gRPC 拦截器可用于实现日志记录、身份验证等中间件逻辑。

服务端拦截器示例(Go 代码)

// 日志拦截器
func logInterceptor(ctx context.Context, req interface{}, info *grpc.ServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  log.Printf("Received request: %+v", req)
  start := time.Now()
  resp, err = handler(ctx, req)
  log.Printf("Handler duration: %s", time.Since(start))
  return resp, err
}

// 注册拦截器
server := grpc.NewServer(
  grpc.WithUnaryInterceptor(logInterceptor),
)

客户端拦截器示例

// 请求头注入拦截器
func authInterceptor(ctx context.Context, method string, req interface{}, reply interface{}, conn *grpc.ClientConn, opts ...grpc.CallOption) error {
  md := metadata.New()
  md.Append("authorization", "Bearer token123")
  ctx = metadata.NewOutgoingContext(ctx, md)
  return conn.Invoke(ctx, method, req, reply, opts...)
}

// 使用拦截器创建连接
conn := grpc.Dial("localhost:5000", 
  grpc.WithUnaryInterceptor(authInterceptor),
)

拦截器类型

  • UnaryInterceptor:处理普通 RPC 请求
  • StreamInterceptor:处理流式 RPC

2. 负载均衡策略
gRPC 客户端支持多种内置负载均衡模式:

conn := grpc.Dial("service.example.com",
  grpc.WithLBPolicy(grpc.RoundRobinBalancer), // 轮询策略
  grpc.WithResolver(dnsResolver), // DNS 解析器
)

常用策略

  • RoundRobin:轮询可用服务端点
  • WeightedRandom:按权重随机选择
  • 自定义策略:实现 grpc.Balancer 接口

5. 部署注意事项

  • 端口规划:gRPC 通常使用 5000-10000 范围端口,避免与 HTTP 服务冲突
  • 资源限制:设置 grpc.MaxConnections(100) 防止服务过载
  • 健康检查:集成 Prometheus 或自定义健康检查端点
  • 错误重试:客户端配置 grpc.WithRetryPolicy 处理瞬态故障

6.性能优化技巧

  • 启用 Protobuf 序列化缓存:减少重复结构编码开销
  • 压缩头部:配置 grpc.WithCompression(headers) 降低传输体积
  • 连接池复用:避免频繁建立新连接

gRPC 技术总结:核心优势与最佳实践

一、gRPC 核心优势

  1. 高性能通信
    • 基于 Protobuf 二进制序列化,相比 JSON 体积更小、解析更快
    • 内置 HTTP/2 多路复用,减少连接开销,提升并发吞吐量
  2. 强类型化与跨语言支持
    • .proto 文件定义 明确的接口契约,避免客户端/服务端语义不一致
    • 自动生成 多语言代码(Go、Java、Python 等),简化跨团队协作
  3. 流式通信能力
    • 支持 单向/双向流式数据传输,适用于实时推送、分批处理等场景
  4. 丰富的生态系统
    • 内置 拦截器、负载均衡、TLS 等模块,降低基础设施开发成本
    • 与 Kubernetes、Prometheus 等现代运维工具深度集成

二、最佳实践指南

  1. 安全优先
    • 生产环境 强制启用 TLS,避免明文传输敏感数据
    • 使用 拦截器统一实现鉴权、日志,确保全局安全策略
  2. 协议设计原则
    • 精简消息字段,避免冗余数据(Protobuf 字段删除不向后兼容)
    • 明确区分普通 RPC 与流式方法,防止误用导致性能问题
  3. 运维与性能优化
    • 客户端配置 负载均衡与重试策略,提升服务可用性
    • 服务端设置 连接数限制与资源监控,防止过载崩溃
    • 启用 Protobuf 序列化缓存,减少重复结构编码开销
  4. 代码管理规范
    • 集中管理 .proto 文件,确保客户端/服务端代码版本同步
    • 生成代码 禁止手动修改,变更需重新编译协议文件
  5. 监控与调试
    • 集成 OpenTelemetry 或 Prometheus,捕获 RPC 延迟、错误率等指标
    • 使用 gRPC 反射接口 或工具(如 grpcurl)进行端点调试

三、适用场景推荐

  • 微服务间高速通信(替代传统 REST API)
  • 实时数据管道(如金融行情推送、IoT 传感器流)
  • 大规模分布式系统(利用负载均衡与健康检查机制)

gRPC 凭借其性能与契约优先的设计,已成为现代分布式系统的核心通信框架。遵循上述实践可最大化其价值,构建高效、可靠的服务网格。


学习碎笔-关于gRPC
http://localhost:8090//archives/F0c4qNDy
作者
EnderKC
发布于
2025年02月20日
许可协议