gRPC 最佳实践
中间件模式
服务级中间件分发机制、App 认证示例与自定义中间件
中间件模式
bamboo-base-go 提供了灵活的服务级中间件机制,允许为不同的 gRPC 服务绑定不同的中间件链。
服务级中间件分发
工作原理
全局拦截器 xGrpcIUnary.Middleware() 作为分发器,根据请求的 FullMethod 解析服务名,查找并执行对应的中间件链。
请求进入 → Middleware 拦截器 → 解析服务名 → 查找中间件链 → 执行中间件 → Handler注册 API
import xGrpcMiddle "github.com/bamboo-services/bamboo-base-go/plugins/grpc/middleware"
// 注册一元中间件
func UseUnary(desc grpc.ServiceDesc, middlewares ...UnaryMiddlewareFunc)
// 注册流式中间件
func UseStream(desc grpc.ServiceDesc, middlewares ...StreamMiddlewareFunc)洋葱模型
中间件链遵循洋葱模型执行顺序:
注册顺序 [A, B, C] 执行顺序:
A-enter → B-enter → C-enter → handler → C-exit → B-exit → A-exitApp 认证中间件示例
以下是一个完整的 App 认证中间件实现,验证调用方的应用身份:
package middleware
import (
"context"
xError "github.com/bamboo-services/bamboo-base-go/common/error"
xLog "github.com/bamboo-services/bamboo-base-go/common/log"
xSnowflake "github.com/bamboo-services/bamboo-base-go/common/snowflake"
xGrpcConst "github.com/bamboo-services/bamboo-base-go/plugins/grpc/constant"
xGrpcUtil "github.com/bamboo-services/bamboo-base-go/plugins/grpc/utility"
"github.com/your-org/beacon-bucket/internal/logic"
"google.golang.org/grpc"
)
// UnaryAppVerify 创建用于验证应用身份的 gRPC 一元服务端拦截器
//
// 它从传入请求的 gRPC 元数据中提取应用 ID 和密钥,
// 并调用 Logic 层进行身份认证。认证失败将直接返回错误响应。
//
// 参数:
// - mainCtx: 用于初始化内部 AppLogic 和 Logger 的上下文
//
// 返回值:
// - 返回一个 grpc.UnaryServerInterceptor,用于拦截请求执行认证逻辑
func UnaryAppVerify(mainCtx context.Context) grpc.UnaryServerInterceptor {
log := xLog.WithName(xLog.NamedMIDE, "UnaryAppVerify")
appLogic := logic.NewAppLogic(mainCtx)
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
log.Info(ctx, "验证应用身份")
// 从 metadata 提取认证信息
appIDStr, xErr := xGrpcUtil.ExtractMetadata(ctx, xGrpcConst.MetadataAppAccessID)
if xErr != nil {
return nil, xErr
}
secretKey, xErr := xGrpcUtil.ExtractMetadata(ctx, xGrpcConst.MetadataAppSecretKey)
if xErr != nil {
return nil, xErr
}
// 校验必填性
if appIDStr == "" || secretKey == "" {
log.Warn(ctx, "缺少应用认证信息")
return nil, xError.NewError(ctx, xError.Unauthorized, "缺少应用认证信息", false)
}
// 解析雪花 ID
appID, err := xSnowflake.ParseSnowflakeID(appIDStr)
if err != nil {
log.Warn(ctx, "应用 ID 格式无效")
return nil, xError.NewError(ctx, xError.ValidationError, "应用 ID 格式无效", false, err)
}
// 调用 Logic 层认证
_, xErr = appLogic.Authenticate(ctx, appID, secretKey)
if xErr != nil {
log.Warn(ctx, "应用认证失败")
return nil, xErr
}
log.Debug(ctx, "应用认证成功")
// 将 AppID 存入 context,供后续逻辑使用
ctx = context.WithValue(ctx, "app_id", appID)
return handler(ctx, req)
}
}流式中间件版本
对于流式 RPC,需要实现流式拦截器:
// StreamAppVerify 用于 gRPC 流式请求的应用认证拦截器
func StreamAppVerify(mainCtx context.Context) grpc.StreamServerInterceptor {
log := xLog.WithName(xLog.NamedMIDE, "StreamAppVerify")
appLogic := logic.NewAppLogic(mainCtx)
return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := ss.Context()
// 从 metadata 提取认证信息
appIDStr, xErr := xGrpcUtil.ExtractMetadata(ctx, xGrpcConst.MetadataAppAccessID)
if xErr != nil {
return xErr
}
secretKey, xErr := xGrpcUtil.ExtractMetadata(ctx, xGrpcConst.MetadataAppSecretKey)
if xErr != nil {
return xErr
}
if appIDStr == "" || secretKey == "" {
log.Warn(ctx, "缺少应用认证信息")
return xError.NewError(ctx, xError.Unauthorized, "缺少应用认证信息", false)
}
appID, err := xSnowflake.ParseSnowflakeID(appIDStr)
if err != nil {
return xError.NewError(ctx, xError.ValidationError, "应用 ID 格式无效", false, err)
}
_, xErr = appLogic.Authenticate(ctx, appID, secretKey)
if xErr != nil {
return xErr
}
return handler(srv, ss)
}
}绑定中间件到服务
中间件在 Handler 构造函数中绑定:
func NewUploadHandler(ctx context.Context, server grpc.ServiceRegistrar) *UploadHandler {
handler := &UploadHandler{
// ...
}
// 1. 注册 gRPC 服务
gen.RegisterNormalUploadServiceServer(server, handler)
// 2. 绑定服务级中间件(只对此服务生效)
xGrpcMiddle.UseUnary(
gen.NormalUploadService_ServiceDesc,
middleware.UnaryAppVerify(ctx),
)
return handler
}多中间件链
可以绑定多个中间件,按注册顺序执行:
xGrpcMiddle.UseUnary(
gen.YourService_ServiceDesc,
middleware.UnaryRateLimit(ctx), // 1. 限流
middleware.UnaryAppVerify(ctx), // 2. App 认证
middleware.UnaryAccessLog(ctx), // 3. 访问日志
)自定义中间件模板
基础模板
package middleware
import (
"context"
xLog "github.com/bamboo-services/bamboo-base-go/common/log"
"google.golang.org/grpc"
)
// UnaryCustom 自定义一元中间件
func UnaryCustom(mainCtx context.Context) grpc.UnaryServerInterceptor {
log := xLog.WithName(xLog.NamedMIDE, "UnaryCustom")
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
// === 前置处理 ===
log.Debug(ctx, "中间件前置处理", "method", info.FullMethod)
// === 调用下一个处理器 ===
resp, err := handler(ctx, req)
// === 后置处理 ===
if err != nil {
log.Warn(ctx, "请求处理失败", "error", err)
}
return resp, err
}
}请求/响应拦截
func UnaryAccessLog(mainCtx context.Context) grpc.UnaryServerInterceptor {
log := xLog.WithName(xLog.NamedMIDE, "AccessLog")
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
start := time.Now()
// 记录请求
log.Info(ctx, "gRPC 请求开始",
"method", info.FullMethod,
"request_type", reflect.TypeOf(req).String(),
)
// 调用处理器
resp, err := handler(ctx, req)
// 记录响应
duration := time.Since(start)
if err != nil {
log.Warn(ctx, "gRPC 请求失败",
"method", info.FullMethod,
"duration", duration,
"error", err,
)
} else {
log.Info(ctx, "gRPC 请求成功",
"method", info.FullMethod,
"duration", duration,
)
}
return resp, err
}
}限流中间件
func UnaryRateLimit(mainCtx context.Context, limit int) grpc.UnaryServerInterceptor {
limiter := rate.NewLimiter(rate.Limit(limit), limit*2)
log := xLog.WithName(xLog.NamedMIDE, "RateLimit")
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
if !limiter.Allow() {
log.Warn(ctx, "请求被限流", "method", info.FullMethod)
return nil, xError.NewError(ctx, xError.TooManyRequests, "请求过于频繁,请稍后重试", false)
}
return handler(ctx, req)
}
}全局拦截器配置
在 main.go 中配置全局拦截器链:
grpcTask := xGrpcRunner.New(
xGrpcRunner.WithLogger(xLog.WithName(xLog.NamedGRPC)),
xGrpcRunner.WithRegisterService(registerGrpcService),
xGrpcRunner.WithUnaryInterceptors(
xGrpcIUnary.InitContext(reg.Init.Ctx), // 1. 注入上下文
xGrpcIUnary.Recover(), // 2. Panic 捕获
xGrpcIUnary.Middleware(), // 3. 服务级中间件分发
xGrpcIUnary.ResponseBuilder(), // 4. 响应构建(最后)
),
)拦截器链顺序
| 顺序 | 拦截器 | 作用 |
|---|---|---|
| 1 | Trace | 请求追踪 ID(Runner 默认) |
| 2 | InitContext | 注入主上下文到请求上下文 |
| 3 | Recover | Panic 恢复,防止服务崩溃 |
| 4 | Middleware | 服务级中间件分发 |
| 5 | 服务中间件 | App 认证、限流等 |
| 6 | ResponseBuilder | 错误转换、响应注入 |
| 7 | Handler | 业务处理 |
最佳实践
- 中间件粒度:每个中间件只做一件事
- 日志规范:使用命名日志器,便于过滤
- 错误处理:返回
*xError.Error,由 ResponseBuilder 统一处理 - Context 传递:通过 context 传递中间件提取的信息