问题场景
想象一下,你的应用每次用户访问都要更新 last_active_at 时间戳:
go
func UpdateUserTimestamp(ctx context.Context, userID int) error {
// UPDATE users SET last_active_at = NOW() WHERE id = userID
}
问题来了:1000 个并发请求 = 1000 次数据库更新。数据库:😫
核心思路
把单个更新变成批量更新:
sql
-- 1000 次单独更新 ❌
UPDATE users SET last_active_at = NOW() WHERE id = 1
UPDATE users SET last_active_at = NOW() WHERE id = 2
...
-- 1 次批量更新 ✅
UPDATE users SET last_active_at = NOW() WHERE id IN (1, 2, 3, ..., 1000)
实现方案
1. 请求结构体
每个请求带一个专属回复通道:
go
type updateUserTimestampRequest struct {
userID int
ReplyTo chan error // 专属回复通道
}
var updateUserTimestampQueue = make(chan updateUserTimestampRequest)
2. 公开 API
go
func UpdateUserTimestamp(ctx context.Context, userID int) error {
req := updateUserTimestampRequest{
userID: userID,
ReplyTo: make(chan error, 1), // 必须缓冲!
}
// 发送请求(支持上下文取消)
select {
case <-ctx.Done():
return ctx.Err()
case updateUserTimestampQueue <- req:
}
// 等待结果
select {
case <-ctx.Done():
return ctx.Err()
case err := <-req.ReplyTo:
return err
}
}
3. 批处理工作流程
┌─────────────┐
│ 调用方 1 │──┐
└─────────────┘ │
│ ┌──────────┐ ┌─────────┐
┌─────────────┐ └────>│ Channel │────>│ Worker │
│ 调用方 2 │──┐ └──────────┘ └─────────┘
└─────────────┘ │ │
│ ▼
┌─────────────┐ │ ┌──────────┐
│ 调用方 3 │──┘ │ 数据库批量 │
└─────────────┘ └──────────┘
4. Worker 实现
使用 rill 库简化批处理:
go
func updateUserTimestampWorker(batchSize int, batchTimeout time.Duration) {
requests := rill.FromChan(updateUserTimestampQueue, nil)
requestBatches := rill.Batch(requests, batchSize, batchTimeout)
_ = rill.ForEach(requestBatches, concurrency, func(batch []updateUserTimestampRequest) error {
// 提取用户 ID 列表
ids := make([]int, len(batch))
for i, req := range batch {
ids[i] = req.userID
}
// 执行批量更新
err := sendQueryToDB(ctx, "UPDATE users SET ... WHERE id IN (?)", ids)
// 回复所有调用方
for _, req := range batch {
req.ReplyTo <- err
close(req.ReplyTo)
}
return nil
})
}
关键技巧
| 技巧 | 作用 |
|---|---|
| 缓冲通道 | 防止调用方取消后 Worker 阻塞 |
| 批处理超时 | 低流量时也能及时更新(如 10ms) |
| select 语句 | 优雅支持上下文取消 |
完整示例
go
func main() {
// 启动 Worker
go updateUserTimestampWorker(7, 10*time.Millisecond, 2, 10*time.Second)
// 模拟 100 个并发请求
var wg sync.WaitGroup
for i := 1; i <= 100; i++ {
wg.Add(1)
go func(userID int) {
defer wg.Done()
UpdateUserTimestamp(ctx, userID)
}(i)
}
wg.Wait()
}
总结
性能提升 ✅
代码简洁 ✅
实时响应 ✅
这个模式同样适用于:
- Redis 批量操作
- 外部 API 批量调用
- 日志批量写入
数据库:谢谢你,终于能喘口气了 😄