内容目录
Golang Gin + Redis 队列:轻松应对高并发写入数据库
为了应对高并发写入数据库场景下的性能瓶颈,可以采用先写入队列,再批量读取写入MySQL的方案。
近期工作上需要统计用户事件达成率、留存等数据,对此我选择了Golang Gin框架和Redis队列来实现,以降低数据库负载,提高并发写入性能。
方案
-
对于这种非核心业务的数据统计,简单起见直接用Redis队列写入元数据
-
使用Redis Bitmap记录已上报的用户ID,防止重复上报
-
定时刷新队列消息到数据库,根据业务需求选择合适的持久化频率
伪代码
下面以收集用户事件元数据为例,给出部分伪代码(为了简洁忽略错误处理)
主要字段有appcode
、date
、eventId
、userId
- 元数据写入队列
// 检查当日是否已经上报过
// 使用redis位图检查userId在当日是否上报了事件3
// 此外:为了更准确也可再次查询数据库
bitKey := appcode + ":user_reach:" + nowDate + ":e" + strconv.FormatInt(eventId, 10)
exist, _ := Redis.GetBit(ctx, bitKey, userId).Result()
// 请求转为Json字符串,写入队列
// 此外:写入队列之前,可以先对数据进行压缩,以减少队列占用空间
data, err := json.Marshal(request)
Redis.RPush(ctx, QueueKey, data).Result()
// Bitmap记录用户ID已上报
exist, err := Redis.Exists(ctx, bitKey).Result()
Redis.SetBit(ctx, bitKey, userId, 1)
// Bitmap缓存一天
if exists == 0 {
Redis.Expire(ctx, bitKey, 24*time.Hour)
}
- 定时持久化队列消息到MySQL
// 从队列中取出1000条
// 此外:应该定时检查队列长度,超长时取出消费
values, err := Redis.LRange(ctx, QueueKey, 0, 999).Result()
// 处理数据 转换为model结构体
var data []models.UserReachMeta
for _, value := range values {
var meta models.UserReachMeta
json.Unmarshal([]byte(value), &meta)
data = append(data, meta)
}
// 插入数据库
insertCount := DB.Create(&data).RowsAffected
// 从队列中删除已处理的元素
if insertCount > 0 {
Redis.LTrim(ctx, QueueKey, insertCount, -1)
}
源码
Github kaxiluo/gin-data-statistics-api
压测
工具 jmeter
666