必须为日志写入单独创建MongoDB客户端并配置WriteConcern.W(0)、RetryWrites(false)及小连接池,否则高频日志会耗尽连接拖垮服务。

直接用 Echo + mongo-go-driver 存用户行为日志,不隔离连接池、不批量、不设 WriteConcern,上线三天就可能因连接耗尽或写入延迟拖垮整个 HTTP 服务。
mongo.Connect() 必须配带超时的 context,不能用 context.TODO()
很多人在 Echo 的 main() 里直接写 mongo.Connect(context.TODO(), opts),看似能连上,但一旦 MongoDB 网络抖动或启动慢,Connect() 就会无限阻塞——HTTP 服务器根本起不来。
- 必须用
context.WithTimeout(context.Background(), 10 * time.Second)包一层,超时后 panic 或 log 并退出 -
ClientOptions要显式设置SetConnectTimeout(5 * time.Second)和SetSocketTimeout(3 * time.Second),前者控建连,后者控单次读写 - 别复用同一个 context 去
Ping():每次调用client.Ping()都得新起一个带超时的 context,例如ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
日志写入必须用独立 Client,禁用 WriteConcern 和 RetryWrites
用户行为日志(如页面访问、按钮点击)写入频次高、可丢弃、不参与事务。如果和业务数据库共用一个 *mongo.Client,日志刷屏时会把连接池占满,导致 FindOne 返回 context deadline exceeded —— 你查 MongoDB 日志发现一切正常,其实是自己卡死了。
- 初始化第二个 client:用
mongo.Connect()单独创建,ClientOptions中调用SetMaxPoolSize(8)(5–10 足够) - 写入时强制设
WriteConcern: writeconcern.New(writeconcern.W(0)),跳过确认,降低延迟 - 显式关掉重试:
SetRetryWrites(false),否则失败日志会卡在 retry loop 里,goroutine 不释放 - 不要在
echo.Context的生命周期内做同步写入;应发到 channel 或用 worker goroutine 异步消费
bson struct tag 写错会导致 Find/Insert 看似成功实则丢数据
行为日志结构体字段没加 bson tag,或拼错成 bsom、漏掉 _id 下划线,InsertOne() 不报错但数据库里字段为空;Find() 返回空 struct 也不提示,排查起来像幽灵 Bug。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- ID 字段必须是
primitive.ObjectID `bson:"_id,omitempty"`,_id的下划线不能少 - 时间字段类型必须是
time.Time,tag 写成bson:"timestamp",不能是string或int64 - 嵌套结构(如
User子对象)每个字段都要有 bson tag,否则只存空对象 - 避免混用
bson.M和 struct:比如用bson.M{"url": c.Request().URL.String()}插入,就别指望它能被 struct 的Url string `bson:"url"`正确解码出来
游标不 Close、批量不分片、时间戳不校验,三者任一都会引发泄漏或堆积
日志查询接口(比如后台看某用户最近 50 条操作)若用 Find() 后忘了 defer cursor.Close(ctx),每请求一次就泄露一个 goroutine;若用 InsertMany() 一把塞 10 万条,MongoDB 直接拒绝并返回 write concern error;若日志时间戳是前端传的、未校验,就会写入大量未来时间或 Unix 零值,索引失效、TTL 清理失灵。
-
Find()后必须defer cursor.Close(ctx),且 ctx 应来自当前 HTTP 请求,不是全局 context - 批量插入按数量分片:建议
200条 / 批,用time.AfterFunc()或 ticker 控制间隔,避免内存暴涨 - 插入前校验时间戳:
if t.Before(time.Now().Add(-24*time.Hour)) || t.After(time.Now().Add(10*time.Minute)) { continue } - 别依赖
$currentDate:行为日志必须用客户端生成的time.Now().UTC(),确保时序准确、可比、可 TTL
最常被跳过的点是:日志 client 的 Disconnect() 没走 shutdown hook,进程重启时连接没释放;还有人把 UpdateOne 的 filter 和 update 参数顺序写反,结果日志更新永远不生效——这两个问题在线上静默发生,直到磁盘爆满或监控报警才暴露。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










