系统设计与场景题总结:答题框架、秒杀、短链与数据一致性

推荐阅读
这篇突击版适合面试前快速查缺补漏。如果你希望系统准备这类开放题,推荐重点阅读 《后端面试高频系统设计&场景题》。
小册整理了 30+ 道高频系统设计和场景题,除了秒杀、短链和海量数据处理,还覆盖动态线程池、站内消息、Feed 流、排行榜、订单超时取消、大文件上传、认证风控与分布式一致性等问题,内容比本文展开得更完整。
这部分内容会综合用到 JavaGuide 下面几个专题的知识:
系统设计题通常没有唯一答案。面试官会关注你能否先问清需求,再结合业务量级、一致性和可用性要求给出方案,同时说明方案的代价和验证方法。回答时不需要一开始就设计出大厂级架构,很多系统用单体应用和单库就能满足需求;当数据和流量确实达到瓶颈后,再引入缓存、消息队列、分库分表等组件。
系统设计答题方法
系统设计题主要考察什么?
系统设计题通常会考察下面几项能力:
- 能否把模糊需求转成明确的功能和质量目标。
- 能否根据用户量、请求量和数据量估算系统规模。
- 能否围绕核心链路设计接口、数据模型和状态流转。
- 是否理解缓存、数据库、消息队列和分布式组件的适用场景。
- 能否发现单点、热点、并发冲突和数据一致性问题。
- 能否说清方案取舍,并通过监控、压测、灰度和演练验证设计。
面试官可能不断改变条件,例如把 QPS 提高十倍、要求跨地域容灾、增加严格顺序约束。方案随约束变化很正常,回答中应明确“当前设计基于什么假设”。
⭐️如何回答一道系统设计题?
可以按下面的顺序回答:
- 澄清需求:确认用户是谁、核心功能是什么、哪些需求暂时不做。
- 确认指标:估算峰值 QPS、数据量、响应时间、可用性和一致性要求。
- 设计核心模型:确定主要实体、状态机、唯一标识、关键索引和接口。
- 画出主链路:说明请求从入口到应用、缓存、数据库和消息队列的流转过程。
- 处理关键问题:围绕题目重点解决高并发、热点、幂等、一致性和容灾。
- 说明扩展方式:系统达到什么瓶颈后扩容,哪些组件可以水平拆分。
- 补充工程保障:监控哪些指标,如何压测、灰度、回滚和故障演练。
时间有限时,优先讲清核心写链路和最难的问题。比如秒杀系统应重点说明库存如何防止超卖、请求如何削峰、下单失败如何补偿;没必要把用户中心、商品管理后台也展开一遍。
拿到题目后应该先问哪些问题?
可以从功能、规模和质量要求三个方面确认:
功能范围:
- 谁使用这个系统?
- 核心读写操作有哪些?
- 是否需要搜索、排序、推送、审核或统计?
- 哪些功能是题目重点,哪些可以暂时忽略?
业务规模:
- 日活、总用户量和峰值并发大约是多少?
- 读写比例、单条数据大小和数据保留时间是多少?
- 流量是否有明显峰谷,热点是否集中?
质量要求:
- 延迟目标看平均值还是 P99?
- 数据需要强一致,还是允许短暂不一致?
- 系统不可用几分钟是否可以接受?允许丢失数据吗?
- 是否涉及支付、隐私、权限和审计?
面试官没有提供具体数字时,可以主动给出假设。例如:“先按读多写少、峰值 5000 QPS、允许秒级最终一致来设计;如果要求强一致,我会把对应读请求路由到主库。”
⭐️如何做容量估算?
容量估算不追求算到个位数,重点是把数量级算清楚,并让后续方案有依据。
常用估算方式如下:
- 平均 QPS:一天的请求量 ÷ 86400。
- 峰值 QPS:平均 QPS × 峰值系数。峰值系数要结合业务特点和历史数据确定。
- 日增存储:每天新增记录数 × 平均记录大小。
- 总存储:日增存储 × 保留天数,再考虑索引、副本、日志和压缩。
- 出口带宽:峰值 QPS × 平均响应大小。
假设某接口每天有 1 亿次请求,平均 QPS 约为:
100000000 / 86400 ≈ 1157 QPS如果业务峰值约为平均值的 5 倍,可以先按 6000 QPS 左右评估,再通过压测决定实例数和安全余量。
存储估算不能只算业务字段。数据库索引、行开销、binlog、备份和多副本都会占用空间。图片、视频和大文件通常放在对象存储中,数据库只保存元数据和访问地址。
如何设计接口和数据模型?
接口和数据模型应该围绕核心访问方式设计。
接口层需要考虑:
- 资源和动作如何命名。
- 分页、过滤和排序参数。
- 写接口是否需要幂等键。
- 如何返回异步任务 ID、状态和错误信息。
- 是否需要版本号、游标或条件请求。
数据模型需要考虑:
- 核心实体之间的关系。
- 哪些字段需要唯一约束和组合索引。
- 状态能否用状态机表达,哪些流转不允许发生。
- 数据按照什么维度查询、归档和分片。
- 是否需要操作日志、审计记录和版本字段。
例如,订单取消接口不能只执行一条无条件 UPDATE。更安全的写法是限定当前状态:
UPDATE orders
SET status = 'CANCELLED', version = version + 1
WHERE id = ?
AND status = 'PENDING_PAYMENT'
AND version = ?;受影响行数为 0 时,说明订单状态已经变化或版本冲突,调用方应重新读取状态,而不是继续覆盖。
如何做技术选型?
技术选型应从业务问题和团队约束出发:
- MySQL 适合事务、关系约束和结构化查询。
- Redis 适合缓存、计数、排行榜和部分临时状态,不应该默认承担最终数据来源。
- 消息队列适合异步处理、削峰和事件通知,但会引入重复、积压和最终一致性问题。
- Elasticsearch 适合全文检索和多条件搜索,写入链路通常还要处理与主存储的数据同步。
- 对象存储适合图片、视频、压缩包和导出文件。
还要考虑团队是否会部署、监控、升级和排查这个组件。为一个低流量系统同时引入 Redis、MQ、Elasticsearch、分库分表和微服务,会增加故障点和维护成本。
如何验证系统设计能否满足要求?
设计完成后,可以从下面几个方面验证:
- 功能测试:状态流转、异常分支和补偿逻辑是否正确。
- 基准测试:单接口、单组件的吞吐和延迟。
- 负载测试:目标业务量下能否满足 P99、错误率和资源使用要求。
- 压力测试:逐步增加流量,找到系统容量上限和第一个瓶颈。
- 稳定性测试:长时间运行,观察内存、连接池、消息积压和性能衰减。
- 故障演练:关闭实例、制造网络延迟或让依赖返回错误,验证超时、熔断和故障转移。
- 灰度发布:先让少量流量进入新方案,指标正常后再逐步放量。
验证时至少关注 QPS、P95/P99、错误率、超时率、CPU、内存、线程池队列、连接池、缓存命中率、慢 SQL 和消息积压。单看平均响应时间容易掩盖尾延迟问题。

高并发与热点场景
⭐️流量突然增加 10 倍,系统应该如何处理?
先保护系统,再定位瓶颈:
- 入口限流:按接口、用户、租户或业务优先级限制流量,避免所有请求一起进入下游。
- 服务降级:关闭推荐、统计、非核心查询等功能,把资源留给登录、下单和支付等核心链路。
- 削峰排队:允许异步处理的请求进入消息队列,消费者按处理能力消费。
- 扩容服务:应用无状态时可以增加实例,但要确认数据库、缓存和下游依赖是否还能承受扩容后的流量。
- 处理热点:预热热点缓存,合并同一 Key 的回源请求,必要时使用本地缓存或拆分热点 Key。
- 持续观察:监控限流量、队列深度、P99、错误率、数据库连接数和下游超时。
自动扩容需要时间,不能作为突发流量的唯一保护。扩容应用还可能把更多并发传给数据库,导致数据库连接池和存储层先被打满。

⭐️如何设计一个秒杀系统?
秒杀系统要重点处理突发流量、库存超卖、重复下单和下单失败后的补偿。
可以沿着下面的链路设计:
- 活动准备:提前把活动和库存信息加载到缓存,检查库存、限购和活动时间配置。
- 静态资源分离:页面、图片和脚本交给 CDN,减少源站请求。
- 入口控制:使用验证码、动态路径、用户限购和网关限流过滤无效请求。
- 库存预扣:通过 Redis Lua、数据库条件更新或库存服务的原子操作避免超卖。
- 异步下单:预扣成功后写入消息队列,由消费者创建订单,平滑数据库写入峰值。
- 幂等处理:用户 ID + 活动 ID 建立唯一约束,防止重复下单和重复消费。
- 失败补偿:订单创建失败、消息发送失败或超时未支付时,按状态机恢复库存。
数据库直接扣库存时,可以使用条件更新:
UPDATE sku_stock
SET available = available - 1
WHERE sku_id = ?
AND available > 0;受影响行数为 1 才表示扣减成功。使用 Redis 预扣库存时,数据库仍然需要保存最终库存和订单结果;还要解决 Redis 扣减成功但消息没有可靠发送、订单创建失败后库存如何回补等问题。
如何解决缓存热 Key 问题?
热 Key 会让请求集中到单个 Redis 分片、网络连接或服务实例。处理前应先通过监控、采样和业务指标确认热点对象,而不是猜测。
常见方案如下:
- 在应用进程内增加短时间本地缓存,减少远程访问。
- 对同一个 Key 的并发回源做请求合并,只让一个请求加载数据。
- 只读热点可以复制成多个 Key,按照随机或哈希规则分散读取。
- 把特别大的热点对象拆分,但要承担聚合和一致性成本。
- 提前预热热点数据,避免活动开始后大量请求同时回源。
- 对热点接口单独限流和隔离,避免影响其他业务。
本地缓存和多副本 Key 会增加数据更新难度。更新频率高、要求强一致的数据不适合长期使用多级缓存;可以缩短 TTL、通过消息通知失效,并在核心读取时回到数据库或权威服务。
如何设计一个排行榜?
实时排行榜可以使用 Redis Sorted Set:成员保存用户或对象 ID,Score 保存分数,通过 ZADD 更新,通过 ZREVRANGE 查询前 N 名和指定区间。
设计时还要处理:
- 分数相同时如何确定顺序,可以把更新时间或其他稳定字段作为二级排序依据。
- 是否按日、周、赛季和地区拆分榜单,避免一个 Key 无限增长。
- Redis 故障后如何恢复,可以把积分流水或数据库记录作为权威数据。
- 是否需要历史榜单,可以在结算时生成快照并归档。
- 超大榜单是否需要分片。分片后查询全局 Top N,需要先取各分片候选集再归并。
Redis 适合提供实时排名,但分数变更的业务事实最好保存在数据库或事件日志中,便于对账和重建。
数据一致性与异步任务
⭐️如何保证缓存和数据库数据的一致性?
常见做法是 Cache Aside:
- 读请求先查缓存,未命中时查询数据库,再把结果写入缓存。
- 写请求先更新数据库,成功后删除缓存。

“更新数据库后删除缓存”仍然存在短暂不一致。例如,旧读请求在数据库更新前查到旧值,却在缓存删除后才把旧值写回缓存。常见缓解方式包括:
- 给缓存设置合理 TTL,让错误数据不会长期存在。
- 热点 Key 回源时使用互斥或请求合并,减少并发覆盖。
- 根据 binlog/CDC 异步删除或更新缓存。
- 缓存值携带版本号,拒绝旧版本覆盖新版本。
- 强一致读直接访问数据库或权威服务,不依赖缓存。
延迟双删可以降低部分并发窗口,但延迟时间难以准确选择,第二次删除也可能失败,不能把它当成严格一致性保证。支付、库存等核心数据应先明确数据库是权威来源,再决定缓存允许多长时间的不一致。
⭐️如何保证接口幂等?
幂等表示同一个业务请求执行一次和执行多次,最终业务结果一致。
常见方案包括:
- 幂等键:客户端为一次业务操作生成唯一请求号,服务端保存处理结果。
- 唯一索引:订单号、支付流水号等业务键建立唯一约束。
- 状态机:只允许状态沿合法方向变化,例如待支付 → 已支付,不能从已支付再次扣款。
- 条件更新:SQL 中带上旧状态或版本号。
- 去重记录:消费消息前检查消息对应的业务动作是否已经完成。

先占用幂等键再处理业务时,还要处理“状态一直停留在处理中”的情况。幂等记录可以保存 PROCESSING、SUCCESS、FAILED 和过期时间;重复请求到达后,成功状态直接返回原结果,处理中状态返回处理中或稍后查询,失败状态根据错误类型决定能否重试。
⭐️订单超时自动取消如何实现?
常见方案如下:
| 方案 | 优点 | 主要问题 |
|---|---|---|
| 定时扫描数据库 | 实现简单,容易补偿 | 时效性取决于扫描周期,扫描压力需要控制 |
| Redis ZSet | 可以按到期时间查找任务 | 要处理持久化、故障恢复和并发领取 |
| 消息队列延时消息 | 业务链路清晰,适合较大规模 | 依赖 MQ 的延时能力和消息可靠性 |
| 专用调度平台 | 管理、重试和监控能力更完整 | 系统建设和运维成本更高 |
不论使用哪种方案,取消逻辑都必须检查订单状态:
UPDATE orders
SET status = 'CANCELLED'
WHERE id = ?
AND status = 'PENDING_PAYMENT'
AND expire_at <= NOW();订单已经支付时,超时消息应直接忽略。取消成功后再释放库存、优惠券和其他资源,每一步都要幂等。延时消息可能重复或晚到,定时扫描可以作为兜底补偿。
如何在不停机的情况下迁移数据?
可以分阶段完成:
- 准备目标存储:先创建兼容的表结构、索引和权限。
- 全量回填:按主键或时间分批复制历史数据,限制速度,避免影响线上库。
- 同步增量:通过 binlog/CDC 把迁移期间的新增和更新同步到目标存储。
- 数据校验:比较记录数、主键范围、关键字段摘要,并抽样检查业务结果。
- 影子读或双读:线上仍返回旧系统结果,同时在后台读取新系统并比较差异。
- 逐步切流:按租户、用户或流量比例把读写切到新系统。
- 保留回滚:旧系统继续保留一段时间,确认稳定后再停止同步和清理数据。
应用双写并不能自动保证一致。一次请求可能只写成功一边,重试又可能造成重复。可以使用事务 Outbox、CDC、重试和对账补偿降低风险,并在迁移期间明确哪个系统是权威来源。
大数据量导出如何设计?
大数据导出不适合让一个 HTTP 请求长时间占用线程和数据库连接。可以改成异步任务:
- 用户提交导出条件,服务创建任务并返回任务 ID。
- 后台 Worker 分页读取数据,使用游标或稳定主键推进,避免深度分页。
- 使用流式写入或分片文件,控制内存占用。
- 文件生成后上传到对象存储,任务状态改为成功。
- 用户通过任务列表下载文件,下载地址设置有效期和权限校验。
还要限制单次导出范围、同一用户并发任务数和系统总导出并发,避免导出任务拖慢在线查询。数据在导出过程中持续变化时,要明确快照语义:接受不同分页看到不同时间的数据,还是通过数据库快照、时间边界或离线数仓生成一致结果。
常见业务场景题
⭐️如何设计一个短链系统?
短链系统的核心流程是把长 URL 映射为较短的 code,用户访问短链时,根据 code 查询长 URL 并跳转。

需要设计下面几个部分:
- 短码生成:可以使用全局 ID 转 Base62,也可以对长 URL 做哈希。使用哈希时必须处理碰撞。
- 数据模型:保存短码、长 URL、创建者、有效期、状态和统计配置,短码建立唯一索引。
- 读取链路:热点短码放入缓存;缓存未命中时查询数据库,并防止恶意随机短码持续打到数据库。
- 跳转策略:
301可能被浏览器和中间缓存长期缓存,后续统计和修改目标地址较难控制;302更方便服务端继续统计和控制跳转。 - 安全治理:限制创建频率,检查恶意链接和钓鱼地址,支持封禁、过期和审计。
- 高可用:跳转是核心读链路,应使用多实例、缓存和数据库冗余,并监控跳转成功率和延迟。
如果同一个长 URL 允许生成多个短码,短码可以直接使用唯一 ID;如果要求同一长 URL 始终得到同一个短码,需要额外保存去重映射,并处理参数顺序、协议和 URL 规范化。
如何设计一个站内消息和推送系统?
可以把系统拆成消息生产、消息存储、分发通道和用户状态几个部分:
- 业务服务把订单、评论、系统公告等事件写入消息队列。
- 消息服务根据模板生成用户消息,并写入收件箱或消息表。
- 在线用户通过 WebSocket 或 SSE 接收实时通知;离线用户下次上线后主动拉取。
- App 推送、短信和邮件作为不同通道,各自处理失败重试、频控和退订。
- 用户读取消息时更新已读状态,未读数可以按用户和消息分类维护。

需要重点处理:
- 同一业务事件重复投递时,消息生成必须幂等。
- 广播消息不能简单地一次性为所有用户插入完整记录,可以保存一份公告,再按用户记录阅读状态。
- 未读数更新失败时,要能通过消息记录重新计算或定期校准。
- WebSocket 断线不等于消息丢失,客户端重连后应根据游标或最后消息 ID 补拉。
大文件上传如何设计?
常见流程如下:
- 客户端向服务端申请上传任务,获得上传 ID、分片大小和授权信息。
- 客户端把文件切成多个分片,并发上传到对象存储或上传服务。
- 服务端记录已完成分片,失败后只重传缺失分片。
- 所有分片完成后,校验分片列表、文件大小和摘要,再合并或完成 Multipart Upload。
- 文件元数据写入数据库,异步执行病毒扫描、转码和内容审核。
还要考虑:
- 相同分片重复上传时保证幂等。
- 上传凭证只能访问指定路径,并且具有较短有效期。
- 限制用户配额、文件类型、单文件大小和分片数量。
- 定期清理长时间未完成的上传任务和孤立分片。
- “秒传”需要确认用户是否有权引用已经存在的文件,不能只根据文件哈希直接返回他人的资源。
⭐️第三方接口不稳定怎么办?
可以从调用保护、异步解耦和事后补偿三个方向处理:
- 为连接、读取和整个请求设置超时,并让下游超时小于上游剩余时间预算。
- 只对网络抖动、临时 5xx 等瞬态错误重试,使用指数退避和随机抖动。
- 写请求重试前先确认接口是否支持幂等键或状态查询。
- 使用熔断和隔离,防止一个第三方接口占满所有业务线程和连接。
- 非实时动作通过消息队列异步执行,失败后进入重试和补偿任务。
- 准备缓存结果、默认值、备用供应商或人工处理等降级方案。
- 保存第三方请求号、业务流水和响应状态,通过主动查询、回调和对账确认最终结果。
支付场景中,请求超时不能直接判定支付失败。第三方可能已经扣款,只是响应丢失;系统应记录“处理中”,随后通过查询接口、签名回调和对账任务确认结果。
验证码登录如何防刷?
可以从多个维度限流:手机号、IP、设备、账号和业务场景。只按手机号限流,攻击者可以批量使用不同号码;只按 IP 限流,又可能误伤共享出口的正常用户。
其他常见措施包括:
- 验证码设置较短有效期,限制错误尝试次数。
- 服务端保存验证码摘要或受保护的值,不把验证码明文写入日志。
- 校验成功后原子地标记验证码已使用,防止并发重复验证。
- 发送前增加图形验证码、滑块或风险评估。
- 对号码是否注册返回相近的响应,减少账号枚举风险。
- 监控发送成功率、验证成功率、单设备号码数和供应商费用异常。
验证码通过不等于用户行为可信。高风险操作还可以继续要求密码、设备确认或其他二次认证。
海量数据场景
⭐️40 亿个整数,限制 1 GB 内存,如何判断是否重复?
先确认数据范围和准确性要求。
如果整数都在 32 位无符号范围内,可以使用位图:每个可能的整数对应 1 bit,第一次出现时把对应 bit 置为 1;再次遇到 bit 已经为 1,就说明该整数重复。
2^32 bit = 512 MiB这可以在 1 GB 内存限制下完成精确的“是否出现过”判断。位图依赖整数范围连续且已知;如果数据是 64 位整数、字符串或取值空间极大,可以考虑:
- 按哈希把数据分到多个文件,每个文件单独放入内存去重。
- 使用外部排序,排序后比较相邻记录。
- 只要求近似判断时使用布隆过滤器,以一定误判率换取更低内存。
布隆过滤器判断“不存在”时结果可靠,判断“可能存在”时仍需回到权威数据确认。它不适合直接给出精确重复集合。
海量数据如何找 Top K?
数据能顺序扫描时,可以维护一个大小为 K 的小顶堆:
- 前 K 个元素进入堆。
- 后续元素如果小于等于堆顶,直接跳过。
- 元素大于堆顶时,替换堆顶并重新调整。
时间复杂度约为 O(n log k),额外空间为 O(k)。
数据分布在多台机器或多个分片时,可以先在每个分片计算本地 Top K,再把各分片候选集合并,计算全局 Top K。需要统计“出现频率最高的 K 个值”时,各分片还要先聚合同一个值的计数;如果同一个值可能出现在多个分片,不能直接合并本地排名结果。
如何设计一个支持亿级用户的去重推荐记录?
先确认去重时间范围:只要求一天内不重复、最近 N 条不重复,还是永久不重复。时间范围不同,存储成本差异很大。
常见组合如下:
- 最近少量记录保存在 Redis Set 或有过期时间的集合中,提供精确判断。
- 较长时间范围使用按天或按周期分桶的布隆过滤器,降低存储占用。
- 推荐候选不足时,允许放宽到更早看过的内容,避免系统完全没有结果。
- 用户点击、播放完成和曝光要区分,不同事件对应不同去重规则。
- 定期归档或淘汰过期桶,避免状态无限增长。
布隆过滤器的误判会把少量没看过的内容当成看过,影响召回率;它不会把已经看过的内容判断为没看过。误判率、容量和哈希函数数量需要按用户规模和时间范围估算。
系统设计回答检查清单
回答结束前,可以快速检查:
- 是否明确了核心功能和暂不考虑的功能?
- 是否说明了用户量、峰值 QPS、数据量和读写比例?
- 是否讲清了核心读写链路、状态机和数据来源?
- 是否处理了并发冲突、重复请求和失败重试?
- 是否说明缓存、消息队列和分库分表带来的新问题?
- 是否识别了单点、热点和下游依赖故障?
- 是否给出了监控、压测、灰度、回滚和补偿方案?
- 是否说明哪些地方要求强一致,哪些地方允许最终一致?
系统设计回答不需要把所有组件都用上。方案能够满足当前约束、关键风险有处理方式,并且后续可以按瓶颈演进,就已经形成了一条完整的回答链路。
更多系统设计与场景题
本文只保留了适合快速复习的高频题。想继续准备动态线程池、Feed 流、点赞、优惠券、红包、第三方授权登录、敏感信息处理、网站 UV 统计等问题,可以继续阅读 《后端面试高频系统设计&场景题》。
小册按照系统设计案例、高频场景题、认证安全与风控、大数据量场景、并发控制与分布式一致性等专题整理了 30+ 道题,适合在突击版之后进行系统复习。
写在最后
感谢你能看到这里,也希望这篇文章对你有点用。
JavaGuide 坚持更新 6 年多,近 6000 次提交、600+ 位贡献者一起打磨。如果这些内容对你有帮助,非常欢迎点个免费的 Star 支持下(完全自愿,觉得有收获再点就好):GitHub | Gitee。
如果你想要付费支持/面试辅导(比如简历优化、一对一提问、高频考点突击资料等)的话,欢迎了解我的知识星球。已经坚持维护六年,内容持续更新,虽白菜价(0.4元/天)但质量很高,主打一个良心!

