如何设计一个投票系统?
这是球友的一个提问贴:

我整理了一下,参考答案,供大家参考。
面试回答:
这个系统的核心挑战:第一高并发,热门投票可能瞬间有几万人同时投票;第二是数据一致性,必须严格保证一人一票;第三是反作弊,防止刷票行为;第四是多活动。
首先是 API 网关,负责限流、鉴权和路由分发。这里我会配置针对投票接口的限流规则,比如单 IP 每秒最多 10 次请求。
其次是业务层,包括投票服务、活动管理服务和反作弊服务。
**然后是数据层,**采用 Redis + MySQL 的组合。Redis 主要承载高频的投票操作,MySQL 负责数据持久化和复杂查询。
消息层可以用 RocketMQ 或者 Kafka,进行数据同步等。
数据库我会设计三张核心表:
- 投票活动表存储活动的基本信息,包括标题、时间范围、状态等。这里我会加上状态字段和时间索引,便于快速查询当前进行中的活动。
- 投票选项表存储每个活动的候选项,通过外键关联到活动表。
- 投票记录表是关键,我会按月分表来避免单表过大。最重要的是在活动 ID 和用户 ID 上建立联合索引,从数据库层面保证一人一票。同时记录 IP、设备 ID、用户代理等信息,为反作弊提供数据支撑。
Redis 是整个系统的性能核心:
- 我会用 SET 存储每个活动已投票的用户列表,比如键名是"vote:users:活动ID",值是用户 ID。这样检查用户是否已投票就是一个 SISMEMBER 操作,时间复杂度是
O(1)。 - 我会用 HASH 存储每个活动各个选项的得票数,键名是"vote:count:活动ID",field 是选项 ID,value 是票数。
- 为了保证原子性,我会用 Lua 脚本把检查用户是否投票、记录投票、更新计数这几个操作包装成一个原子操作。脚本逻辑是先检查用户是否在已投票集合中,如果没有就添加用户到集合,同时对应选项计数加一。
用户发起投票时,我的处理流程是这样的:
- 第一步检查活动是否存在、是否在有效期内、选项是否合法等。
- 第二步是反作弊检测,我会从多个维度检查:IP 频率是否正常、设备指纹是否异常等。如果发现异常就直接拒绝。
- 第三步是并发控制,我会用 Redisson 分布式锁避免同一用户进行重复投票,锁的键是"vote
活动ID:用户ID"。 - 第四步,通过 Lua 脚本完成投票记录和计数更新。如果用户已经投过票,返回失败。
- 第五步是异步持久化,投票成功后,我会把详细的投票记录发送到消息队列,异步写入 MySQL。这样用户能第一时间得到反馈。
对于反作弊,我会通过以下几个维度来考量:
- IP,用滑动窗口算法限制单 IP 的投票频率,比如每分钟最多 10 票。我会用 Redis 的 INCR 命令配合过期时间来实现。
- 设备维度,要求客户端传递设备指纹,同一设备在同一活动中只能投一票。我会把设备 ID 和活动 ID 组合作为 Redis 的 key 来记录。
- 我会记录用户最近几次的投票时间戳,分析时间间隔是否过于规律。如果发现投票间隔非常固定,可能是脚本行为。
- 如果用户在极短时间内对多个活动投票,也可能是异常行为。
为了应对高并发,我会做几个优化:
第一,在活动开始前,我会把活动信息、选项信息预先加载到 Redis,并初始化计数器。
第二,查询统计结果主要走 Redis,只有在 Redis 没有数据时才去数据库同步。
第三,所有非核心的操作都通过消息队列异步处理,比如数据库持久化、统计报表等。
第四,投票记录表按时间分表,如果数据量特别大,还可以考虑按活动 ID 哈希分库。
对于数据一致性问题,首先要在投票记录表上建立联合索引,确保数据库层面不会有重复投票;其次,每个投票请求都要有唯一标识,确保幂等。
核心表结构:
-- 投票活动表
CREATE TABLE vote_activities (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200) NOT NULL COMMENT '活动标题',
description TEXT COMMENT '活动描述',
start_time DATETIME NOT NULL COMMENT '开始时间',
end_time DATETIME NOT NULL COMMENT '结束时间',
status TINYINT DEFAULT 1 COMMENT '状态:1-进行中 2-已结束 3-已暂停',
max_votes_per_user INT DEFAULT 1 COMMENT '每用户最大投票数',
total_votes BIGINT DEFAULT 0 COMMENT '总投票数',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status_time (status, start_time, end_time),
INDEX idx_created_at (created_at)
) COMMENT '投票活动表';
-- 投票选项表
CREATE TABLE vote_options (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
activity_id BIGINT NOT NULL COMMENT '活动ID',
option_text VARCHAR(500) NOT NULL COMMENT '选项内容',
vote_count BIGINT DEFAULT 0 COMMENT '得票数',
sort_order INT DEFAULT 0 COMMENT '排序',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_activity_id (activity_id),
FOREIGN KEY (activity_id) REFERENCES vote_activities(id)
) COMMENT '投票选项表';
-- 投票记录表(分表)
CREATE TABLE vote_records_202412 (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
activity_id BIGINT NOT NULL COMMENT '活动ID',
option_id BIGINT NOT NULL COMMENT '选项ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
client_ip VARCHAR(50) NOT NULL COMMENT '客户端IP',
user_agent VARCHAR(500) COMMENT '用户代理',
device_id VARCHAR(100) COMMENT '设备ID',
vote_time DATETIME DEFAULT CURRENT_TIMESTAMP,
...未能定位到原文的划线
企业级Agent工作流编排项目PaiFlow
Vibe Coding版本的PaiAgent
派聪明RAG AI知识库Java版本+Go版本
微服务 PmHub、技术派、MYDB
求职派JobClaw(OpenClaw/Hermes架构
PaiCLI(类似Claude Code的Agent
派简历(代码已完成)
等实战项目。
1. 微信扫右侧的优惠券加入知识星球
2. 解锁星球的实战项目教程和源码: 项目源码+教程获取
真诚点赞 诚不我欺
回复