Skip to content
Go
最佳实践与避坑指南

最佳实践与避坑指南

本章汇总了 go-zero 社区经过大量生产实践验证的工程化最佳实践、常见陷阱和避坑经验。


工程化最佳实践

1. 约定优于配置

  • 团队统一 .api 规范、错误码规范、响应体结构
  • 使用 goctl 自动生成代替手写,避免人为失误
  • 定制 goctl 模板,一键生成符合团队标准的代码

2. ServiceContext 是唯一的依赖注入入口

// ✅ 正确:所有依赖在 ServiceContext 中初始化
type ServiceContext struct {
    Config      config.Config
    UserModel   model.UserModel
    UserRpc     user.User
    Redis       *redis.Redis
    KqPusher    *kq.Pusher
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
        Config:    c,
        UserModel: model.NewUserModel(sqlx.NewMysql(c.DataSource), c.Cache),
        UserRpc:   user.NewUser(zrpc.MustNewClient(c.UserRpcConf)),
        Redis:     redis.NewRedis(c.Redis.Host, ""),
        KqPusher:  kq.NewPusher(c.KafkaConf.Brokers, c.KafkaConf.Topic),
    }
}

// ❌ 错误:在 logic 里临时创建连接
func (l *MyLogic) DoSomething() error {
    conn := sqlx.NewMysql(dsn)  // 不可以!
    // ...
}

3. 分层职责明确

Handler  →  只做参数解析和响应序列化(不写业务逻辑)
Logic    →  业务编排(调用 Model/RPC/缓存)
Model    →  单表 CRUD + 缓存(goctl 生成)
Repository → 复杂查询、多表关联、聚合业务语义

4. 微服务拆分原则

时机 触发条件
建议拆分 单一 service 目录超过 50 个文件
建议拆分 日活用户超过 100 万
建议拆分 接口 QPS 超过 1 万
不要过度拆分 一个接口一个服务
不要过早拆分 业务验证期就拆微服务

5. RPC 调用链设计

✅ 推荐:
  API → User RPC → Order RPC → Payment RPC (单向)

❌ 禁止:
  API → User RPC → Order RPC → User RPC (循环调用)
// 串行改并行 — 使用 core/mr 包
import "github.com/zeromicro/go-zero/core/mr"

func (l *OrderLogic) GetOrderDetail(id int64) (*OrderDetail, error) {
    var user *userRpc.GetUserResp
    var product *productRpc.GetProductResp

    err := mr.Finish(func() error {
        user, err = l.svcCtx.UserRpc.GetUser(ctx, &userRpc.GetUserReq{Id: userId})
        return err
    }, func() error {
        product, err = l.svcCtx.ProductRpc.GetProduct(ctx, &productRpc.GetProductReq{Id: productId})
        return err
    })
    // ...
}

6. 结构化日志是底线

// ✅ 正确:结构化日志 + trace_id
logx.WithContext(ctx).Infow("order created",
    logx.Field("orderId", orderId),
    logx.Field("userId", userId),
)

// ❌ 错误:普通日志,丢失 trace_id
log.Printf("order created: %d", orderId)

7. 没有度量就没有优化

必看指标 工具 关注点
QPS Prometheus + Grafana 流量趋势
P95/P99 延迟 Prometheus + Grafana 慢请求
错误率 Prometheus + Grafana 异常趋势
RPC 连接池 go-zero 内置 pool exhausted
熔断器状态 go-zero 内置 下游故障
消息堆积 Kafka/asynq 消费能力

常见陷阱全览

基础设施

陷阱 根因 解决
🚨 go mod init 短名称 import 链断裂 用完整路径 github.com/org/project
🚨 etcd 未启动但配置了 RPC 启动失败 先启动 etcd,或用直连模式
🚨 配置 YAML key 大小写 conf.MustLoad 映射失败 Go struct 字段名 = YAML key 名
🚨 API 和 RPC 共用 Config struct 字段不兼容 各自定义独立的 Config

RPC 调用

陷阱 根因 解决
🚨 RPC client 首次调用 2-3 秒 懒加载建连 main 函数中预热 client.Ping(ctx)
🚨 每个 logic 里 new client 反复建连 + 连接泄漏 client 注入 ServiceContext,单例使用
🚨 RPC 超时未设置 gRPC 默认无限等待 必须 context.WithTimeout
🚨 MaxPoolSize=10 不够 突发扩容连接耗尽 → 静默丢弃 升级 v1.8.3+,监控连接池指标
🚨 循环 RPC 调用 A→B→C→A core/mr 并行替代串行依赖

数据库与缓存

陷阱 根因 解决
🚨 认为 go-zero 自有事务 默认无事务 手动 TransactCtx
🚨 事务内用全局 DB 无法回滚 事务内全用 session
🚨 事务内做 RPC 调用 长事务 + 锁竞争 事务只做 DB,RPC 提前执行
🚨 -table 大小写 MySQL 表名小写 -table "user",不是 "User"
🚨 修改 usermodel_gen.go goctl 会覆盖 自定义逻辑写到 usermodel.go
🚨 批量查询走缓存 sqlc 不支持 QueryRowsNoCacheCtx

中间件与安全

陷阱 根因 解决
🚨 JWT leeway=0 NTP 偏移 > 1s 拒绝合法 token jwt.WithLeeway(5s)
🚨 CORS * 上生产 安全漏洞 明确指定域名
🚨 中间件未在 ServiceContext 初始化 编译错误 DSL 声明后需手动注册
🚨 panic 恢复跳过了限流 DeferFunc 令牌未归还 在 recover handler 中手动清理

消息队列

陷阱 根因 解决
🚨 消费无幂等 重试导致重复处理 消息 ID + 去重表
🚨 消费失败不返回 error kq 依赖 error 判断重试 处理失败 return err
🚨 延迟队列用 Kafka Kafka 不支持 用 asynq 或 Beanstalkd
🚨 定时任务多实例重复 无分布式锁 Redis SetNxEx
🚨 asynq/payload 过大 Redis 性能下降 仅传关键 ID,查库获取详情

运维部署

陷阱 根因 解决
🚨 生产镜像含源代码 安全风险 多阶段构建
🚨 terminationGracePeriod 太短 强制中断 K8s 设置 ≥ 30s
🚨 缺少 ReadinessProbe 未就绪就接收流量 配置 TCP/HTTP 探测
🚨 日志写满容器磁盘 容器 rootfs 空间不足 emptydir / sidecar 采集
🚨 无优雅停止监听器 资源未清理 proc.AddShutdownListener()

生产环境检查清单

上线前必检

  • 配置检查:所有敏感信息使用环境变量或 Secret,禁止硬编码
  • 超时设置:HTTP/RPC/DB/Redis 都设置了合理的超时值
  • 连接池大小:根据负载测试调整 MaxPoolSizeMaxConns
  • 熔断降载:确认 Breaker: trueShedding.CpuThreshold 合理
  • 日志格式:生产环境使用 Mode: file, Encoding: json
  • 链路追踪:配置 Telemetry,采样率根据 QPS 设置
  • 健康检查:K8s 配置 ReadinessProbe 和 LivenessProbe
  • 优雅停止terminationGracePeriodSeconds ≥ 30s
  • 持久化存储:日志和临时文件使用 emptydir 或 PVC

上线后必看

  • Prometheus 指标:QPS、延迟、错误率是否正常
  • Jaeger 链路:是否有异常的耗时 span
  • 熔断器:是否有异常打开(下游故障)
  • 数据库连接池:是否有 wait_count > 0
  • Redis 缓存命中率:是否有高 miss rate
  • 消息队列积压:消费速度是否匹配产生速度

代码审查要点

关注点 检查内容
ServiceContext 所有依赖是否通过它注入?有没有在 Logic 里直接 new?
Context 传递 所有方法是否正确传递 ctx?logx.WithContext(ctx)
错误处理 是否用 CodeError + errors.Wrapf?是否区分业务/系统错误?
事务边界 多 DB 操作是否在同一个事务内?是否避免了长事务?
RPC 超时 每个 RPC 调用是否设置了 context.WithTimeout
幂等性 消息消费、支付回调等是否做了幂等处理?
硬编码 超时时间、重试次数、地址等是否从配置读取?

社区标杆项目

项目 GitHub 推荐学习点
go-zero-looklook Mikaelemmmm/go-zero-looklook 完整微服务体系(API+RPC+MQ+asynq)
zero-examples zeromicro/zero-examples 官方示例集
go-zero-admin lawrenceshao/go-zero-admin 后台管理系统
bookstore zeromicro/bookstore 最小完整示例

💡 建议深入研究 go-zero-looklook 的代码结构,它是社区公认的 go-zero 最佳实践标杆项目。