投票与限量抽签
→ 返回 高并发
综艺投票、年报评选、限量名额抽签(演唱会候补、内测资格)介于「点赞可近似」与「秒杀强一致」之间:通常要求 一人一票 / 一人一次,名额 不能超发,但读压力往往小于写。
典型问题
| 问题 | 后果 |
|---|---|
| 重复点击投票 | 票数虚高、规则纠纷 |
| 最后一百名额并发 | 超发资格 |
| 实时榜刷新 | 热 key、DB 压力 |
| 刷票脚本 | 业务公平性崩塌 |
一致性要求
| 类型 | 要求 |
|---|---|
| 普通投票(可隔天再投) | 周期内唯一:user + activity + day |
| 单次投票 | 全局唯一:user + activity |
| 限量抽签 | 强一致:剩余名额 ≥ 1 才能成功 |
推荐架构
普通投票(最终一致可接受)
投票 API
▼
Redis SADD vote:{activity}:{option} {userId} → 0 表示已投过
▼
INCR vote:count:{activity}:{option}
▼
MQ → 异步写 DB(审计、防作弊分析)
▼
读榜:Redis ZSET 或 HASH,定时与 DB 对账
与 点赞与播放计数 类似,但 SADD 返回值 必须作为业务成功依据,不能只看 INCR。
限量抽签(强一致)
抽签 API
▼
频控 / 风控
▼
Redis Lua:
if GET remain > 0 then DECR remain; SADD winners userId; return OK
else return FAIL
▼
异步写 DB:winners 表 UNIQUE(user_id, activity_id)
▼
失败则 DB 拒绝,回滚 Redis(或以对账为准)
DB 唯一约束是最后防线,防止 Redis 与 DB 短暂不一致导致超发。
UNIQUE KEY uk_user_activity (user_id, activity_id)
-- INSERT 失败 → 已中签或重复,向用户返回明确文案防刷与公平
| 手段 | 说明 |
|---|---|
| 登录态 + 实名 | 一票一人 |
| 设备指纹 / 行为分 | 拦截机器号 |
| 投票间隔 | SET vote:cd:{user} NX EX |
| 结果公示延迟 | 截止后 MQ 汇总再发榜,削峰读 |
与相邻场景对照
| 场景 | 重复提交 | 数量准确性 |
|---|---|---|
| 点赞 | 可合并 | 可近似 |
| 投票 | 必须拒绝 | 票数要准 |
| 限量抽签 | 必须拒绝 | 名额 不能超 |
| 秒杀 | 幂等订单 | 库存不能超 |
读优化
- 榜单位:
ZREVRANGE+ 本地缓存 1~3s - 活动结束后切静态结果页(CDN),避免长期热 key
检查清单
- 是否
SADD/ 唯一键保证一人一票? - 限量是否 Lua 或 DB 原子扣减 + 唯一约束?
- 榜是否读 Redis、写是否异步?
- 截止后是否降读压力(静态页)?
- 是否有防刷与审计日志?