‹ 全部面经
DoorDash 系统设计高频原题:Review & Reward System(投票聚合 + 奖励只触发一次)
DoorDash 系统设计高频原题复盘:外卖评价与奖励系统,覆盖 Review 读多写少的缓存设计、投票去重与异步聚合(Kafka + 物化视图)、奖励任务 exactly-once(at-least-once + 幂等状态机)与故障处理。
VO
DoorDash 的系统设计题库不大,今天聊聊最高频的原题之一:设计一个类似 DoorDash 的 Review & Reward System。
一、题目要求
功能需求
- 提交评价:用户完成外卖订单后,可以对餐厅提交 review,包括 1–5 星评分、文字评价和可选的图片。
- 投票:其他用户可以对 review 点 upvote / downvote,表示是否 helpful。同一用户对同一条 review 最多投一次票。
- 聚合统计:维护 upvote count、downvote count 和 helpfulness score,浏览餐厅评价时一起展示。
- 奖励:review 到达指定的 evaluation window 后,可靠地触发 reward calculation,并保证每条符合条件的 review 只触发一次。
不需要设计的部分
| 部分 | 说明 |
|---|---|
| 用户认证、订单管理 | 已存在,直接调用 |
| 完整的下单和配送流程 | 不在范围内 |
| Reward Scoring Service | 已有黑盒服务,根据 review 质量和投票统计计算奖励金额 |
| Reward payout | 由第三方 payment provider 负责 |
非功能需求
- High availability、low latency、horizontal scalability
- 餐厅 review 查询 < 200ms(典型的 read-heavy 场景,高 QPS)
- 投票统计可以接受 eventual consistency,不要求投票后立刻反映最新数字
- 所有写接口都要考虑客户端 retry,需要 idempotency guarantee
二、整体架构
拆成四个核心模块:
| 模块 | 职责 |
|---|---|
| Review Service | 创建和查询 review |
| Vote Service | 处理投票写入,保证一人一票 |
| Vote Aggregation Pipeline | 异步计算投票统计 |
| Reward Orchestrator | 到期触发奖励计算,保证只触发一次 |
User --> Review Service --verify--> Order Service
| | write images --> S3
| v
| Review DB <--cache-- Redis <-- read reviews
|
+--> Vote Service --VoteChanged--> Kafka
|
v
Vote Aggregator
|
v
ReviewStats Store
(materialized view)
due reviews --> Reward Orchestrator --> Reward Scoring
(unique job + Service
state machine) (black box)
三、Review Service:写入 + 读多场景
创建 review
- 先调用 Order Service 验证订单已完成,并检查该订单是否已经写过 review。
- review metadata 存在 DynamoDB 或 Cassandra;图片放 S3,记录里只保存 object URL。
- 防客户端重试重复提交:用 idempotency key,或者在
(order_id, user_id)上建唯一约束。
查询 review(主要的 read-heavy 路径)
- 按
restaurant_id建索引,按created_at或 helpfulness score 排序。 - 用 cursor pagination 分页,比 offset 分页更稳定,翻页性能也更好。
- 热门餐厅的 review summary 和第一页结果缓存在 Redis,Review DB 作为 source of truth,降低数据库压力,把 P99 latency 控制在 200ms 以内。
四、Vote Service:一人一票
- Vote table 以
(review_id, user_id)作为唯一 key,从数据模型层面保证一个用户对一条 review 最多只有一个有效投票。 - 允许改票(比如从 upvote 改成 downvote)时,用 conditional update 更新原记录。
- 写入成功后发布
VoteChanged事件到 Kafka,不同步更新 review 上的聚合字段,避免热门 review 的写竞争。
五、Vote Aggregation:异步物化视图
- Vote Aggregator 消费 Kafka 事件,根据每次 vote change 计算增量(比如改票就是 upvote −1、downvote +1),更新 ReviewStats Store。
- upvote count、downvote count、helpfulness score 都是异步维护的 materialized view。
- 读 review 时直接读预聚合结果,而不是对 Vote table 做实时 COUNT,读延迟低,也更容易水平扩展。
- 题目允许 eventual consistency,所以短暂的统计延迟可以接受。
六、Reward Workflow:如何保证只触发一次
到期调度
- 创建 review 时记录
evaluation_at,比如发布 7 天后进入 reward evaluation。 - 找出到期 review 的方式:delayed queue、scheduled job,或者按时间分桶的任务表。
- 到期后交给 Reward Orchestrator 调用 Reward Scoring Service。
Exactly-once = At-least-once + 幂等处理
分布式系统里很难真正做到 exactly-once delivery,实际做法是把问题转化成:消息至少投递一次 + 处理逻辑幂等。
- 为
(review_id, evaluation_window)建立唯一的 reward job,重复消息无法创建第二个任务。 - Worker 领取任务时,用 conditional write 把状态从
PENDING改成PROCESSING,只有一个 worker 能成功。 - 成功调用 Reward Scoring Service 后,再写成
COMPLETED。 - 如果 Reward Scoring Service 支持 idempotency key,就用
review_id + evaluation_window作为 key,下游调用也能安全重试。
PENDING --(only one worker wins)--> PROCESSING
|
(scored)
v
COMPLETED
状态机说明:只有一个 worker 能把任务从
PENDING改成PROCESSING;评分成功后改成COMPLETED。worker 崩溃或超时时,任务会被重新领取,并用相同的 idempotency key 重试。
下面用 SQL 演示「唯一约束 + 条件更新」这两个关键点(以 SQLite 语法为例):
-- 1. 每个 (review_id, evaluation_window) 只能有一个 reward job
CREATE TABLE reward_jobs (
review_id TEXT NOT NULL,
evaluation_window TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'PENDING',
worker_id TEXT,
PRIMARY KEY (review_id, evaluation_window)
);
-- 到期调度可能重复投递:重复插入会被忽略,不会产生第二个任务
INSERT OR IGNORE INTO reward_jobs (review_id, evaluation_window) VALUES ('r1', '7d');
INSERT OR IGNORE INTO reward_jobs (review_id, evaluation_window) VALUES ('r1', '7d');
-- 2. Worker 抢任务:只有状态仍是 PENDING 时才更新成功(受影响行数 = 1 才算抢到)
UPDATE reward_jobs
SET status = 'PROCESSING', worker_id = 'worker-A'
WHERE review_id = 'r1' AND evaluation_window = '7d' AND status = 'PENDING';
-- 3. 评分成功后标记完成
UPDATE reward_jobs
SET status = 'COMPLETED'
WHERE review_id = 'r1' AND evaluation_window = '7d' AND status = 'PROCESSING' AND worker_id = 'worker-A';
七、Failure Handling
| 故障场景 | 处理方式 |
|---|---|
| Vote Aggregator 更新数据库成功,但 Kafka offset commit 失败 | 消息会被重复消费:用 event_id 去重,或保证聚合更新本身幂等 |
| Reward Worker 调用评分服务后 crash | 任务超时后重新领取,用相同的 idempotency key 重新调用 |
| Redis 不可用 | fallback 到 persistent store(Review DB) |
小结
整体通过 cache、materialized aggregation、event-driven architecture,把读评价、投票、发奖励三条链路解耦,同时满足高可用、低延迟、最终一致和水平扩展的要求。
面试时的几个加分点:
- 主动区分 read-heavy 的查询路径和写路径,分别优化。
- 讲清楚为什么投票统计用异步聚合而不是实时 COUNT。
- 能把 exactly-once 拆解成「至少一次投递 + 幂等处理」,并讲出唯一约束和状态机的具体做法。
看完有收获?欢迎交流。
交流面经、互相 mock、内推信息,都可以找我。
相关面经
Amazon VO 面经:BQ 深挖 + 有限算力的文档处理设计 + Alexa 对话切分会话Amazon · 2026-10-02›
- Apple SWE VO 四轮面经:Swift 继承体系排查 + 专注模式 Schedule API + 遥测监控系统设计Apple · 2026-10-02›
- Meta E4 Phone Screen + VO 过经:括号最大深度、叶到叶最大路径和、Dropbox 设计Meta · 2026-10-02›
- 八月 Anthropic 四轮 VO 拿 Offer:调用栈采集器 + 流式 LLM 推理 API 设计 + Lock-free 优先队列Anthropic · 2026-08-01›
