设计面试
设计面试
系统设计
【困难】如何设计一个秒杀系统?⭐⭐⭐
秒杀系统所要应对的场景就是:瞬时海量请求。
秒杀系统的难点
- 高并发:秒杀系统是极致的高并场景发自不用说。其高并发可以细分为二:
- 并发读:主要是读取剩余库存量以及商品信息
- 并发写:主要是下单后,系统写入订单记录
- 超卖:秒杀系统中售卖的商品一般都是性价比很高,不怎么赚钱,甚至赔钱赚哟喝的商品。一旦出现超卖现象,会给商家带来巨大的经济损失。从系统层面来看,比如某秒杀商品本来库存 100 件,但是在高并发场景下,瞬时下单量超过 100 件,处理不当,让这些下单都成功了,就会出现超卖。
- 恶意请求:有些人为了低价购入秒杀商品,通过在多台机器上跑脚本,模拟大量用户抢商品的请求(走自己的路,让别人无路可走)。
- 数据库崩溃:海量请求下,如果没有 MQ 削峰,没有过载保护,让所有请求都打到数据库,那么数据库基本就挂了。数据库如果挂了,也会波及其他业务,从而可能让整个系统、网站陷入瘫痪。
- 对现有业务造成冲击
秒杀系统设计目标
秒杀系统架构的思考角度可以概括为:稳、准、快
- 稳(高可用):系统架构要满足高可用,系统要能撑住活动。
- 准(一致性):商品减库存方式非常关键,不能出现超卖。
- 快(高性能):整个请求链路,从前端到后端,依赖组件都要做到协同优化。

前端优化-静态页面
把秒杀商品页面静态化,减少查数据库的 IO 开销。然后,可以将这些静态页面做 CDN 缓存,如果项目是前后端分离的,还可以在反向代理服务器侧设置静态缓存。
如每个商品都由 ID 来标识,那么 http://item.xxx.com/item.htm?id=xxxx 就可以作为唯一的 URL 标识。相应的页面可以提前做前端缓存,这样就不需要向后台查询商品信息。
前端优化-按钮控制
在秒杀活动开启时间前,下单按钮禁用。
此外,按钮一旦点击之后,禁用一段时间,防止有人疯狂输出。
后端优化-限流、熔断、降级、隔离
秒杀活动,本质上还是一个营销活动,性质和打折、促销一样。
秒杀系统设计底线原则,是不应该影响现有业务。所以,为了避免防不胜防,百密一疏的情况下,秒杀系统崩了。
- 隔离:将秒杀系统、数据与其他正常业务隔离。彼此隔离,自然互不影响。
- 限流:设置阈值,超过阈值,拒绝请求。防止数据库被打死。
- 降级:保证核心业务继续工作,非核心业务各安天命。
- 熔断:不要影响别的系统。
后端优化-多级缓存
缓存要预热,避免瞬间流量冲击。
此外,防止雪崩、穿透、击穿问题的常规处理要做好。
缓存也要保证高可用。
后端优化-流量削峰
削峰的思路:排队、答题、分层过滤。
- 排队:用消息队列来缓冲瞬时流量的方案。但是,消息队列自身也有上限,如果积压过多,也会处理不了。
- 答题(摇一摇):可以限制秒杀器并延缓请求。
- 分层过滤:采用漏斗式的设计尽可能拦截无效请求。

后端优化-减库存
恶意下单
恶意下单的解决方案还是要结合安全和反作弊措施来制止:
- 识别频繁下单不付款或重复下单不付款的卖家,阻断其下单。
- 限制个人购买数
避免超卖
减库存在数据一致性上,主要就是保证大并发请求时库存数据不能为负数,也就是要保证数据库中的库存字段值不能为负数,一般我们有多种解决方案:一种是在应用程序中通过事务来判断,即保证减后库存不能为负数,否则就回滚;另一种办法是直接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行 SQL 语句来报错;再有一种就是使用 CASE WHEN 判断语句,例如这样的 SQL 语句:
UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END在交易环节中,“库存”是个关键数据,也是个热点数据,因为交易的各个环节中都可能涉及对库存的查询。但是,我在前面介绍分层过滤时提到过,秒杀中并不需要对库存有精确的一致性读,把库存数据放到缓存(Cache)中,可以大大提升读性能。
后端优化-URL 动态化
通过 MD5 之类的加密算法加密随机的字符串去做 url,然后通过前端代码获取 url 后台校验才能通过。
【困难】如何设计一个 RPC 框架?⭐⭐
设计一个 RPC 框架,可以自下而上梳理一下所需要的能力:
- 通信传输模块:RPC 本质上就是一个远程调用,那肯定就需要通过网络来传输数据。
- 协议模块:传输的数据如何定义,就需要通过协议和序列化方式来确定。此外,为了减少传输数据的大小,可以加入压缩功能。
- 代理模块:为了屏蔽用户的感知,让用户更聚焦于自身业务,需要引入动态代理来托管远程调用。
以上,是一个 RPC 框架的基础能力,使用于 P2P 场景。
但是,如果面对集群模式,以上能力就不够了。同一个服务可能有多个提供者。消费者选择调用哪个提供者?消费者怎么找到提供者的访问地址?请求提供者失败了如何处理?这些都依赖于服务治理的能力。
服务治理,需要很多个模块的能力:服务发现、负载均衡、路由、容错、配置挂历等。

具备了这些能力就万事大吉了吗?RPC 框架很难一开始就面面俱到,但作为基础能力,在实际应用中,难免会有定制化的要求。这就要求 RPC 框架具备良好的扩展性。
通常来说,框架软件可以通过 SPI 技术来实现微内核+插件架构。根据依赖倒置原则,框架应该先将每个功能点都抽象成接口,并提供默认实现。然后,利用 SPI 机制,可以动态地为某个接口寻找服务实现。
加上了插件功能之后,我们的 RPC 框架就包含了两大核心体系——核心功能体系与插件体系,如下图所示:

【困难】如何设计一个 MQ?⭐⭐
消息模型设计——存什么?
| 模型 | 特点 | 适用场景 | 代表产品 |
|---|---|---|---|
| 点对点(Queue) | 一条消息只被一个消费者消费 | 任务队列 | Kafka、RabbitMQ |
| 发布订阅(Topic) | 一条消息被所有订阅者消费 | 事件广播 | Kafka、RocketMQ |
存储设计——放哪儿?
记忆点: 顺序写 + 分段存储 + 索引 + 刷盘策略
核心策略:
- 顺序写(性能关键):一般采用日志文件存储,所有消息顺序追加,不分 Topic。
- 分段存储:每个文件固定大小(如 1GB),写满后建新文件
- 索引机制:一般记录消息在日志中的 Offset。
- 刷盘策略
- 同步刷盘:消息落盘才返回成功(安全)
- 异步刷盘:先写内存,批量刷盘(性能)
推拉模型设计——怎么取?
| 模式 | 特点 | 实现方式 |
|---|---|---|
| Push | Broker 主动推,实时性高 | 长连接推送(易压垮消费端) |
| Pull | 消费端主动拉,控制性强 | 定时拉取(延迟可调) |
| Long Polling | 拉不到就等一会儿 | 综合两者优点 |
高可用设计——挂了怎么办?
记忆点: 主从复制 + 故障转移 + 多副本
- 复制(Kafka/RocketMQ)
- Master: 处理读写
- Slave: 从 Master 同步数据,Master 挂掉后接管
- 多副本(Kafka)
- 每个 Partition 有多个 Replica,Leader 负责读写
- ISR 机制保证数据一致性
可靠性设计——消息不丢不重
记忆点: ACK 机制 + 重试 + 死信队列
关键机制:
- 生产者 ACK:发送后等待 Broker 确认
- 消费者 ACK:处理完才提交 offset
- 重试队列:消费失败的消息重试 N 次
- 死信队列:超过重试次数,人工介入
性能设计——如何快
| 优化项 | 做法 | 效果 |
|---|---|---|
| 分区 | 分而治之+负载均衡 | 使负载在集群中尽量均衡 |
| 零拷贝 | 使用 mmap 或 sendfile | 减少数据拷贝次数 |
| 页缓存 | 充分利用 OS Page Cache | 读写性能提升 |
| 批处理 | 批量发送、批量刷盘 | TPS 大幅提升 |
| 压缩 | 消息体压缩(gzip/lz4) | 网络带宽节省 |
【中等】如何设计一个短链服务?⭐⭐
功能设计
要点:生成、存储、重定向
生成:长链变短码
生成短链的方案:
- 哈希:对长链接进行哈希(如
MD5),得到一个32位字符串。 - 自增 ID:用一个中心服务(如 Redis)生成自增数字 ID(如:100001, 100002)。
短链不够短,可以采用转码:把数值转成 62 进制(用光所有数字+大小写字母)。例如 ID 100001 -> 短码 aB3。
存储:写 DB,读 Cache
- 写路径:
长链接 -> 发号器拿 ID -> 转短码 -> 存 MySQL(落盘) - 读路径(关键):
- 第一步:查 Redis(缓存),有就直接用。
- 第二步:Redis 没有,查 MySQL(数据库)。
- 第三步:查到后,回写 Redis,下次就快了。
重定向:301 还是 302?
- 301(永久):
- 特点:浏览器会记住,下次不访问短链服务器了。
- 缺点:没法统计点击次数。
- 302(临时):
- 特点:每次都先找短链服务器,再跳转。
- 优点:可以精确计数(点了几次,谁点的)。
- 结论:推荐 302,为了数据和灵活性。
性能设计
- 缓存:缓存挡住 99%的读请求,不让流量打垮 DB。
- 预发号:短链生成服务不要每次都找发号器,而是一次性生成一批短链(比如 1000 个)放本地内存,用完了再拿。减少对发号器的压力。
- 布隆过滤器:
- 如果有人恶意输入不存在的短码(如
aaaaaa),系统会先去查 Redis 和 DB,造成缓存穿透。 - 布隆过滤器像一个前置的“筛查员”,快速判断这个短码肯定不存在,直接拦截掉,保护后端。
- 如果有人恶意输入不存在的短码(如
【中等】如何实现一个订单超时取消功能?
本质是一个延迟任务调度问题。主流实现方案有:
- 定时任务扫描
- 延迟消息队列:底层采用时间轮实现。
如何实现一个分布式单例对象?
要让一个对象在分布式环境下全局唯一,需要满足两个条件:
- 进程内单例:在每台机器上,这个对象只初始化一次(本地单例)。
- 进程间互斥:在整个集群中,只允许一台机器的这个对象真正工作,其他机器的对象处于**“待命”或“禁用”**状态。
实现思路分为两步:
- 用分布式锁控制创建过程,保证同一时刻只有一个进程能创建。
- 把对象存到外部存储,让所有进程都能访问到。
【困难】如何设计一个分布式锁?
参考:分布式锁
【中等】如何实现接口每分钟调用统计功能?
要点:记、存、看
记(数据埋点)
拦截接口调用(拦截器、过滤器或AOP),记录每次请求。
- 关键信息:
- 接口名:
/api/user - 时间桶:当前时间的分钟级窗口,例如
2026-02-26 14:00
- 接口名:
- 操作:每来一次请求,就给对应的“接口+分钟”计数器加1。
存(数据存储)
统计的核心是:写入极其频繁(每次请求都要写),读取相对低频(每分钟/每小时看一次)。
推荐方案:Redis Hash:
- 数据结构:用 Redis 的 Hash。
- Key:统计日期+分钟,如
stats:20260226:1400 - Field:接口名,如
/api/user - Value:调用次数(整数)
- Key:统计日期+分钟,如
- 操作:每次请求执行
HINCRBY stats:20260226:1400 /api/user 1 - 优点:
- 极高性能:内存操作,原子递增。
- 结构清晰:一个 Key 存一分钟的所有接口数据。
- 自动过期:可以给 Key 设置过期时间(比如保留 7 天),自动清理旧数据。
看(数据展示)
从存储中读取数据并展示出来。
- 查询实时分钟数据:直接
HGETALL stats:20260226:1400,拿到这一分钟所有接口的计数。 - 查询历史趋势:遍历多个分钟 Key,聚合出接口的调用趋势。
- 可视化:可以对接 Grafana,或自己写一个简单的接口返回 JSON 数据供前端图表展示。
记忆点:“Redis Hash 来计数,每分钟一个 Key,Field 是接口,Value 是次数。”
如何设计一个文件上传系统?
如何设计一个分布式 ID 发号器?
什么是限流?限流算法有哪些?怎么实现的?
如何设计一个点赞系统?
即时通讯项目中怎么实现历史消息的下拉分页加载?
HashMap 是不是线程安全的?如果如何来实现一个线程安全的 HashMap 你要怎么设计?如果不用加锁你要怎么设计?
如何设计一个购物车功能?
怎么设计电商系统的订单数据同步方案(同步到数仓)?要求数据准确、性能高
设计一个实时数据同步系统,将 MySQL 数据实时同步到数据仓库?
如何设计一个高可用的数据同步系统?需要考虑哪些容错机制?
如何设计一个 API 网关,需要考虑哪些核心功能?如何实现动态路由和限流?
API 网关如何实现接口版本管理?如果要同时支持 v1、v2、v3 多个版本的接口,你会怎么设计?
网关层如何防止接口重放攻击?如果攻击者抓取了请求包进行重放,你如何识别和拦截?
微服务架构中,如何设计一个配置中心?配置变更后如何实时通知各个服务?
如果配置中心挂了,各个微服务应该如何保证可用性?你会设计什么降级策略?
电商系统中,用户下单后如何保证订单、库存、支付三个服务的数据一致性?请设计一个分布式事务方案
如果如何实现一个分布式事务的补偿机制,应该注意哪些问题?补偿失败了怎么办?
分布式缓存中,如何实现一致性 Hash 算法?相比普通 Hash,它解决了什么问题?
如何设计一个灰度发布系统?如何控制流量逐步切换到新版本?
如何实现一个单点登录(SSO)系统?如果有多个子系统,用户只需登录一次即可访问所有系统
SSO 系统中,如果认证中心挂了怎么办?如何保证已登录用户不受影响?
如何设计一个接口签名验证机制?防止参数被篡改和重放攻击?
接口中的敏感数据(如身份证号、手机号)应该如何加密传输和存储?
加密后的数据怎么支持模糊搜索?
如何防止接口被恶意刷量?除了限流,还有哪些防护手段?
如何实现一个滑动验证码功能?如何防止被机器识别破解?
短信验证码如何防止被恶意轰炸?如果有人不断请求发送验证码,你会如何限制?
什么是死信队列?在什么场景下会用到?如何设计死信队列的处理机制?
如何实现微信扫码登录?
如何设计一个排行榜功能?
可以使用 Redis 的 zset 数据类型来实现。
如何设计一个点赞功能?
点赞功能的核心操作是:
- 点赞
- 取消点赞
- 查看点赞列表
本质是一个按时间排序的去重集合,可以使用 Redis 的 zset 数据类型来实现。
如何在 10 亿个数据中找到最大的 1 万个?
构建容量大小为 1 万的堆,每次从 10 亿数据中读 1 万条数据,写入最小堆,循环直至读完所有数据。最终,还留存在最小堆中的数据就是 TOP 10000