Golang+Redis:轻松应对百万级数据统计场景下的高并发写入

Golang+Redis:轻松应对百万级数据统计场景下的高并发写入

内容纲要

Golang Gin + Redis 队列:轻松应对高并发写入数据库

为了应对高并发写入数据库场景下的性能瓶颈,可以采用先写入队列,再批量读取写入MySQL的方案。

近期工作上需要统计用户事件达成率、留存等数据,对此我选择了Golang Gin框架和Redis队列来实现,以降低数据库负载,提高并发写入性能。

方案

  1. 对于这种非核心业务的数据统计,简单起见直接用Redis队列写入元数据

  2. 使用Redis Bitmap记录已上报的用户ID,防止重复上报

  3. 定时刷新队列消息到数据库,根据业务需求选择合适的持久化频率

伪代码

下面以收集用户事件元数据为例,给出部分伪代码(为了简洁忽略错误处理)

主要字段有appcodedateeventIduserId

  1. 元数据写入队列
// 检查当日是否已经上报过
// 使用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)
}
  1. 定时持久化队列消息到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

一条评论

发表回复

您的电子邮箱地址不会被公开。