写入瓶颈到削峰填谷:基于 Redis 与 MySQL 的高并发写入工程化方案
1. 方案背景与目标
1.1 为什么需要优化高并发写入?
在高并发场景下(如 注册、登录、提交订单、IoT 设备上报),直接写入数据库会导致:
- 数据库连接池耗尽(如 MySQL 的
max_connections被打满)。 - 事务锁竞争(如行锁、表锁导致请求阻塞)。
- 磁盘 I/O 和 CPU 压力激增(如 InnoDB 日志刷盘、索引维护)。
- 接口响应变慢(P99 延迟飙升,用户体验下降)。
目标:通过 Redis 缓冲 + 批量入库 + 最终一致性,将瞬时高并发写入 平滑削峰,提升系统吞吐量(QPS),同时保证数据最终不丢失。
2. 整体架构设计
2.1 核心流程
- 客户端 提交写入请求(如注册信息)。
- Gin 接口层:
- 幂等校验(防止重复提交,如用 Redis SETNX 对
phone加锁)。 - 快速写入 Redis 队列(如
LPUSH reg_queue,JSON 格式存储请求数据)。 - 立即返回(代表请求已接收,后续异步处理)。
- 幂等校验(防止重复提交,如用 Redis SETNX 对
- 批处理 Worker(独立 Goroutine):
- 定时/定量触发(如每 100ms 或每 500 条数据)。
- 从 Redis 拉取批量数据(如
BRPOP reg_queue 50ms)。 - 去重 & 合并(按业务唯一键,如
phone去重)。 - 批量写入 MySQL(使用
INSERT ... ON DUPLICATE KEY UPDATE)。 - 成功后清理 Redis 消息(或标记为已处理)。
- 结果查询(可选):客户端通过
request_id轮询或回调获取最终状态。
2.2 关键组件
| 组件 | 作用 | 技术选型 |
|---|---|---|
| Redis | 缓冲队列、幂等去重、结果缓存 | Go-Redis(官方库) |
| MySQL | 最终数据存储 | GORM / database/sql |
| 批处理 Worker | 定时/定量拉取 Redis 数据并入库 | Goroutine + Channel |
| Gin | HTTP 接口层 | Gin Framework |
3. 详细实现(Go + Gin 代码示例)
3.1 1. 幂等校验(防止重复提交)
目标:同一 phone在 10 秒内 只能提交一次,避免重复写入 Redis 和数据库。
// redis_dedup.go
package mainimport ("context""fmt""github.com/redis/go-redis/v9""time"
)var rdb *redis.Clientfunc initRedis() {rdb = redis.NewClient(&redis.Options{Addr: "localhost:6379", // Redis 地址Password: "", // 无密码DB: 0, // 默认 DB})
}// TryAcquireDedup 尝试获取幂等锁(10 秒过期)
func TryAcquireDedup(ctx context.Context, phone string) bool {key := fmt.Sprintf("reg:dedup:%s", phone)// SET key "1" NX EX 10 → 如果 key 不存在则设置,并设置 10 秒过期ok, err := rdb.SetNX(ctx, key, "1", 10*time.Second).Result()if err != nil {fmt.Printf("Redis SETNX 失败: %v\n", err)return false}return ok
}
在 Gin 接口层调用:
// main.go
r := gin.Default()
initRedis() // 初始化