外观
场景题:100w用户对几个主题投票,每人只能投一次,要实时显示票数排行。设计redis
⭐ 题目日期:
阿里 - 2024/8/21
📝 题解:
针对 100万用户对多个主题投票,每人仅限一次投票,并实时显示票数排行 的需求,以下是基于 Redis 的高效设计方案:
1. 数据结构设计
(1) 用户投票记录
- 目标:确保每个用户对每个主题只能投一票。
- 结构:使用 Bitmap(位图) 存储每个主题的已投票用户。
- Key 格式:
vote:topic:{topic_id}
- Value:位图,每个用户 ID 对应一个位偏移(如用户 ID 为整数)。
- 内存优化:100 万用户仅需
1,000,000 / 8 ≈ 125 KB
内存(每主题)。
- Key 格式:
(2) 票数统计与排行
- 目标:实时统计各主题票数并排序。
- 结构:使用 Sorted Set(有序集合)。
- Key:
topic_rank
- 成员(Member):主题 ID(如
topic_1
)。 - 分数(Score):当前票数,通过
ZINCRBY
动态更新。
- Key:
2. 核心操作流程
(1) 用户投票
步骤:
- 检查是否已投票:使用
GETBIT
查询用户是否在对应主题的位图中标记为已投票。 - 执行投票:若未投票,通过原子操作(Lua 脚本)完成以下两步:
- 在位图中标记用户已投票(
SETBIT
)。 - 在有序集合中增加该主题的票数(
ZINCRBY
)。
- 在位图中标记用户已投票(
- 检查是否已投票:使用
Lua 脚本(原子性保障):
local topicKey = KEYS[1] -- vote:topic:{topic_id} local userId = tonumber(ARGV[1]) -- 用户 ID(需为整数) local rankKey = KEYS[2] -- topic_rank local topicId = ARGV[2] -- 主题 ID if redis.call('GETBIT', topicKey, userId) == 1 then return 0 -- 已投票,拒绝 else redis.call('SETBIT', topicKey, userId, 1) redis.call('ZINCRBY', rankKey, 1, topicId) return 1 -- 投票成功 end
(2) 实时获取票数排行
- 命令:
ZREVRANGE topic_rank 0 9 WITHSCORES
- 功能:获取票数前 10 的主题及其票数。
- 性能:时间复杂度 O(log(N)),高效支持实时查询。
3. 性能优化策略
(1) 内存压缩
- Bitmap 压缩:Redis 自动对稀疏位图进行压缩存储(RLE 编码),适合用户 ID 稀疏的场景。
- 用户 ID 映射:若用户 ID 非整数,需设计映射机制(如哈希后取模),确保偏移量为整数。
(2) 分片存储
- 场景:主题数量极多(如数千个),单个 Redis 实例内存不足。
- 方案:按主题 ID 分片到多个 Redis 实例,如
vote:topic:{topic_id % shard_num}
。
(3) 异步持久化
- 风险:Redis 宕机可能导致位图和排行数据丢失。
- 方案:定期异步持久化到数据库(如 MySQL),或启用 AOF 日志(
appendfsync everysec
)。
4. 扩展性设计
(1) 分布式锁(可选)
- 场景:高并发下需严格防止重复投票。
- 方案:对用户 ID 加锁(如
INCR
锁),确保同一用户并发请求串行处理。-- 在 Lua 脚本中增加锁检查 local lockKey = 'lock:user:' .. userId if redis.call('SET', lockKey, '1', 'NX', 'EX', 5) then -- 执行投票逻辑 redis.call('DEL', lockKey) else return 0 -- 请求冲突 end
(2) 缓存预热
- 场景:重启后需快速恢复票数排行。
- 方案:定期将有序集合数据备份到数据库,启动时加载到 Redis。
5. 复杂度分析
操作 | 时间复杂度 | 说明 |
---|---|---|
用户投票 | O(1) | 位图操作和有序集合自增均为常数时间 |
获取排行榜 | O(log(N) + M) | N 为主题数,M 为返回数量 |
内存占用 | O(M + K*N) | M 为位图大小,K 为主题数,N 为用户数 |
6. 异常处理
- 重复投票:通过 Lua 脚本的原子性避免。
- 数据不一致:持久化失败时,采用定期校验修复机制(如对比数据库与 Redis 数据)。
总结
该方案通过 Bitmap + Sorted Set 组合,以低内存占用和高并发性能满足需求。
- 优势:精确投票控制、实时排行、高吞吐量。
- 适用场景:秒杀投票、热点话题排行、活动实时统计等。
- 注意事项:用户 ID 需为整数或可映射为整数,避免位图稀疏导致内存浪费。