Skip to content

场景题:100w用户对几个主题投票,每人只能投一次,要实时显示票数排行。设计redis

约 1009 字大约 3 分钟

Redis阿里

2025-4-7

⭐ 题目日期:

阿里 - 2024/8/21

📝 题解:

针对 100万用户对多个主题投票,每人仅限一次投票,并实时显示票数排行 的需求,以下是基于 Redis 的高效设计方案:


1. 数据结构设计

(1) 用户投票记录

  • 目标:确保每个用户对每个主题只能投一票。
  • 结构:使用 Bitmap(位图) 存储每个主题的已投票用户。
    • Key 格式vote:topic:{topic_id}
    • Value:位图,每个用户 ID 对应一个位偏移(如用户 ID 为整数)。
    • 内存优化:100 万用户仅需 1,000,000 / 8 ≈ 125 KB 内存(每主题)。

(2) 票数统计与排行

  • 目标:实时统计各主题票数并排序。
  • 结构:使用 Sorted Set(有序集合)
    • Keytopic_rank
    • 成员(Member):主题 ID(如 topic_1)。
    • 分数(Score):当前票数,通过 ZINCRBY 动态更新。

2. 核心操作流程

(1) 用户投票

  • 步骤

    1. 检查是否已投票:使用 GETBIT 查询用户是否在对应主题的位图中标记为已投票。
    2. 执行投票:若未投票,通过原子操作(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 需为整数或可映射为整数,避免位图稀疏导致内存浪费。