直接回答:Go 业务开发的默认组合基本是 Gin 做 HTTP、GORM 做数据访问、zap 做日志,再按需加定时任务、依赖注入和协程池。选库的判断标准不是"star 多",而是维护是否活跃、依赖是否轻、出问题时能不能看懂源码——标准库能解决的,就别急着引第三方。

Web 框架:Gin

1
2
3
r := gin.Default()
r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) })
r.Run(":8080")
  • 适合:写 REST 接口,路由、参数绑定、中间件、校验都能覆盖,社区资料多,招人也好找。
  • 注意:gin.Context 不能跨 goroutine 使用;在协程里要用 c.Copy(),否则会有数据竞争。
  • 替代:标准库 net/http(1.22 之后增强了路由能力)、Fiber(基于 fasthttp,性能好但生态和标准库有差异)、Echo。

如果项目接口很少、依赖想尽量少,直接用 net/http 也完全够,不用为了框架而框架。

日志:zap

1
2
3
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("user login", zap.String("uid", uid), zap.Int("cost_ms", 12))

zap 的核心优势是结构化日志:日志是键值对而不是拼接好的字符串,方便采集到 ELK、Loki 之后按字段检索。相比 log.Printf,性能也高得多。

使用上的两个经验:

  • 开发环境用 NewDevelopment(可读),生产用 NewProduction(JSON 格式),通过配置切换;
  • 别把日志当业务数据源。需要统计的字段落到数据库或埋点系统里,日志只用于排查。

替代方案是 logrus(更直观,但性能弱于 zap)和 Go 1.21 起的标准库 log/slog(标准库、够用,新项目值得优先考虑)。

JSON:jsoniter

标准库 encoding/json 已经够稳,jsoniter 的价值在于大流量场景下的序列化开销。它的 API 和标准库兼容,替换成本很低:

1
2
3
import jsoniter "github.com/json-iterator/go"

var json = jsoniter.ConfigCompatibleWithStandardLibrary

经验是:先测再用。接口 QPS 不高、JSON 不大时,标准库和它的差距可以忽略,多一个依赖反而不划算。真到了 CPU 大部分花在序列化上的时候再换。

数据访问:GORM

1
db.Where("status = ?", 1).Order("id desc").Limit(20).Find(&orders)
  • 适合常规 CRUD,关联、钩子、自动迁移都能省不少代码;
  • 注意点:批量写入要用 CreateInBatches;Find 到结构体切片时记得初始化 slice;关联保存有隐式行为,容易多写或多查;复杂查询该写原生 SQL 就写,别硬凑。
  • 替代:sqlx(更贴近 SQL)、ent(代码生成、类型安全)、sqlc(写 SQL 生成代码)。

对 SQL 控制欲强的团队,sqlc 或 sqlx 往往比 ORM 更省心;但业务迭代快、表结构常变时,ORM 的开发效率优势明显。

定时任务:robfig/cron

1
2
3
c := cron.New()
c.AddFunc("0 3 * * *", func() { cleanup() })
c.Start()

单机定时任务用它足够,支持标准 cron 表达式(默认 5 段,秒级要用 cron.WithSeconds())。

两个必须注意的点:

  • 多实例部署会重复执行。要么用分布式锁(Redis、etcd)保证只有一个实例跑,要么干脆把定时任务拆成独立服务;
  • 任务要自己兜住 panic 和超时,否则一次卡死会影响后面的调度。

分布式的替代方案是 xxl-job、asynq(基于 Redis)这类带管理界面的调度系统。

依赖注入:wire

wire 用代码生成代替运行时反射:你写 wire.go 声明依赖关系,它生成组装代码。好处是启动快、依赖关系在编译期就能发现错误;代价是多人协作时要理解它的写法,增删依赖要重新生成。

1
2
3
4
func InitApp() (*App, error) {
wire.Build(NewDB, NewRepo, NewService, NewApp)
return nil, nil
}

项目依赖层次不深的时候,手动组装其实更直观。wire 的价值在依赖多、初始化流程长的中大型项目里。

协程池:ants

1
2
3
p, _ := ants.NewPool(200)
defer p.Release()
_ = p.Submit(func() { doWork() })

无限制地 go func() 会在任务量突增时把内存和下游连接数打满。协程池把并发度限制在可控范围内,适合"任务量不可预测、每个任务开销又不小"的场景。

如果任务是阻塞 IO 且数量固定,用带缓冲的 channel 做信号量往往更简单:

1
2
3
sem := make(chan struct{}, 200)
sem <- struct{}{}
go func() { defer func() { <-sem }(); doWork() }()

选型原则

  1. 标准库优先:net/http、log/slog、encoding/json 能覆盖就别引依赖;
  2. 看维护状态:最近一年有没有提交、issue 有没有人回,比 star 数重要;
  3. 依赖要少:一个库带进来十几个传递依赖时,先想想值不值;
  4. 可替换性:把第三方库挡在一层自己的接口后面,换库时才不用改遍全站。

本文整理自我自己早先收集的一份 Go 库清单,按用途重新分类并补上了选型判断和实践注意点,文字由 AI 协助改写后经我复核。各库的具体用法以官方文档为准。

这篇笔记整理自我自己的实践记录,如果做法有出入,或者你踩过别的坑,欢迎到留言板一起聊聊。

站内搜索

没有找到内容!