当前位置: 首页 > news >正文

面试题之如何设计一个秒杀系统?

系统痛点:

痛点分析:
  • 瞬时并发量大:大量并发请求瞬间涌入,网站流量激增,可能压垮系统。
  • 库存少:访问请求远大于库存,只有少量用户可以秒杀成功
  • 架构设计原则:
    • 系统隔离:
      • 独立部署秒杀服务,避免影响主线业务流程
      • 独立数据库/缓存集群等
      • 独立域名/CDN等
    • 分层削峰:
      • 前端层:静态化、请求拦截
      • 服务层:队列缓冲、异步处理
      • 数据层:预减库存、最终一致
业务流程:
用户 → CDN → 负载均衡 → 网关(限流) → 秒杀服务 → 消息队列 → 订单服务↘ Redis集群 ↘ 数据库集群

实施与解决

系统设计图:

分层设计:

以上为秒杀系统的设计图:根据访问层、负债层、服务层、业务层支撑层、数据层阐述设计

访问层

当用户访问系统商品时,需要将商品从动态网页转为静态网页。减少动态请求到服务端的压力,服务端只需要处理秒杀即可

前端上做一些逻辑处理:

  • 静态资源分离:将活动页静态化,部署到CDN上
  • 活动前禁用按钮:减少不必要的资源请求
  • 按钮防重复点击:当用户点击提交后,立即灰显禁用,限制用户多次提交页面
  • 随机延迟请求:客户端随机添加微笑言辞
  • 验证码/答题:通过滑动验证码、图形验证码等拦截机器人及防止用户控制多个设备进行提交
  • 排队体验:增加等待中、抢购中的提示,防止 用户多次刷新

服务层

        前端校验通过后,通过多台NG的转发,到后端处理秒杀功能,单台ng可以处理2-3万的数据量,一旦NG集群了,就需要在NG上方进行部署网络及硬件级别相关的F5/LVS等。通过NG校验及转发后,就会到服务端的网关集成器,比如ribbon或者loadbalancer进行客户端的负债均衡的转发,通过上面四级的负债均衡大概能处理每秒10+万的qps请求并发量。

  • 网关限流操作:
    • 限流熔断:NG/Lua实现IP/用户限流
    • 请求过滤:恶意请求识别与拦截
    • URL动态化:活动开始前不暴露真实的接口
    • DNS轮询:让DNS服务商针对网站提供的域名绑定多个IP地址
  • 支撑层
    • 异步处理:请求先入队列,异步处理订单
    • 缓存预热:提前加载热点数据到redis
    • 分层校验:
      • 第一层:用户资格校验(是否登录、黑名单等)
      • 第二层:库存校验(redis预扣减库存)
      • 第三层:最终校验(数据库确认)
      • 接口防重校验:(防止绕过前端的恶意用户刷单,setNX保证同一个时间、同一个商品、同一用户只能下单成功一次)
    • 库存方案:
      • 库存分段:将库存拆分成多个段(如1000库存拆成10个100)
      • 预扣库存:redis预减库存(LUA脚本进行数据操作),异步(秒杀时客户端将请求放到MQ消息中发送给MQ服务端,消息者通过消费队列来进行下单操作并持久化到数据库)同步到DB
      • 库存回滚:支付超时后自动释放库存
  • 数据一致性:
    • 最终一致性:通过消息队列异步处理
    • 事务消息:通过rocketMQ事务宝恒下单与扣减库存
    • 对账系统:定期核对redis与数据库的库存告警监控
    • 日志监控:防止系统出现问题后处理
限流相关:
  • 核心要点:
    • NG需要配置限流,防止恶意绕过我们前端的一些D DOS攻击;
    • 网关sentinel对不同的服务节点限流和熔断机制;
    • 通过MQ的来削峰,通过MQ来减轻下游的服务压力
  • 降级方案
    • 服务降级:关闭非核心功能(如评论、推荐)
    • 限流策略:
      • 令牌桶/漏桶算法
      • 队列泄洪(固定长度队列)
    • 熔断机制:hystrix/sentiel实现

数据库:
  • 读写分离、数据量大时需要涉及到分库分表操作
  • redis集群:
    • 库存预扣:DECR原子操作
    • 分布式锁:防止超卖
  • MQ队列:
    • 通过异步处理降低数据压力
  • 数据库优化:
    • 分库分表
    • 使用乐观锁(version字段)
    • 库存字段无符号设计(避免负数)

部署相关:

这种秒杀系统一般配合docker、 K8S相关的云服务器的动态伸缩部署,当秒杀开始时,自动扩容服务器节点,结束后,自动缩减节点,有效的利用服务器资源和成本。

面试相关:

Q:如何防止超卖?
  • redis原子操作,通过redis的decr操作来扣减库存,当不小于零则返回成功
  • 数据库乐观锁,通过mysql更新脚本时直接设置参数完成
  • 分布式锁:通过redisson实现,处理秒杀逻辑
Q:流量激增时,引发redis雪崩、击穿、穿透?
  • 缓存雪崩(Cache Avalanche)
    • 定义:大量缓存数据同时过期,导致所有请求直接打到数据库,引发数据库压力激增甚至崩溃。
    • 场景:双11零点,大量商品缓存设置相同TTL同时失效。
  • 缓存击穿(Cache Breakdown)
    • 定义:某个热点key突然失效,同时有大量并发请求访问这个key,直接冲击数据库。
    • 场景:明星离婚新闻的缓存key失效瞬间,千万请求同时涌入。
  • 缓存穿透(Cache Penetration)
    • 定义:查询根本不存在的数据,缓存和数据库都找不到,导致每次请求都穿透到数据库。
    • 场景:恶意攻击者伪造不存在的商品ID发起请求。

解决方案

问题类型核心原因解决方案技术实现示例
雪崩大量key同时失效1. 差异化过期时间
2. 永不过期+后台更新
3. 多级缓存
setex(key, 300 + random(60), value)
击穿热点key失效1. 互斥锁
2. 逻辑过期
3. 永不过期
RLock lock = redisson.getLock(key)
穿透查询不存在数据1. 布隆过滤器
2. 空值缓存
3. 参数校验

bloomFilter.mightContain(key)

  • 缓冲雪崩解决:
    • 过期时间随机化
    • 永不过期策略
    • 多级缓冲
      • 浏览器缓存 → CDN缓存 → Nginx缓存 → Redis集群 → JVM缓存 → DB
  • 缓冲击穿
    • 分布式锁互斥(获取key时锁定单线程获取)
    • 逻辑过期+异步刷新
  • 缓冲穿透:
    • 布隆过滤器
    • 空值缓存

防护组合方案

流程图:

  • 热点key探测+动态保护
    • 实时监控key访问频率
    • 自动将热点key迁移到本地缓冲
    • 对热点key实施特殊保护策略
  • 集群化解决方案
    • redis cluster 分片存储
    • 多副本读写分离
    • 异地多活架构
Q:布隆过滤器如何防止穿透?

布隆过滤器(Bloom Filter)是解决缓存穿透问题的经典方案。

  • 核心原理:
    • 空间换时间:使用位数组(bit array)和多个哈希函数,用极小空间存储数据存在性信息

    • 预检机制:在查询缓存/数据库前先检查布隆过滤器

    • 确定性回答

      • "可能存在"(可能有误判)

      • "绝对不存在"(100%准确)

  • 业务流程

  • 注意:
    • 误判率权衡

      • 误判率越低,需要的空间越大

      • 典型设置:0.1%-1%(1%误判率下,每个元素约需9.6 bits)

    • 不支持删除

      • 标准布隆过滤器不支持删除操作

      • 需要删除功能可使用变体(Counting Bloom Filter)

    • 数据预热

      • 系统启动时需要预加载有效key

      • 对于新增数据需要同步更新过滤器

    • 动态扩容

      • 当元素数量超过预期时,需要重建过滤器

      • 考虑使用分片布隆过滤器(Scalable Bloom Filter)

方案内存占用查询时间复杂度误判率实现复杂度
原生布隆过滤器O(k)*可配置
Redis布隆过滤器O(k)可配置
布谷鸟过滤器较低O(1)更低
空值缓存O(1)
Q:限流中的算法:滑动窗口算法、令牌桶算法、漏桶算法?只有获取令牌才能操作?

缺点:令牌如果太少了 会导致用户取消购买

三大算法核心对比

算法核心原理特点适用场景是否需获取令牌
滑动窗口统计单位时间内实际请求量精确控制瞬时流量,需存储时间戳数据API限流、秒杀系统❌ 直接计数
令牌桶以恒定速率生成令牌,请求消耗令牌允许突发流量,平滑限流网络流量控制、接口平滑限流✅ 必须获取令牌
漏桶以恒定速率处理请求,超量请求排队/丢弃严格限制流出速率,无法应对突发流量保护下游系统、流量整形❌ 排队机制
  • 滑动窗口:
    • 精确控制任意时间窗口内的请求量

    • 需要存储时间戳数据(内存开销较大)

    • Redis可通过ZSET+Lua脚本实现分布式版本

    • 通过计数控制,没有真正令牌概念

  • 令牌算法

    • 突发流量处理:当桶中有足够令牌时,允许短时间内超过平均速率

    • 平滑限流:长期来看平均速率被严格控制

    • 必须显式获取令牌才能执行业务逻辑

    • 令牌桶的必须性:

      • 显式获取:业务逻辑必须显式调用acquire()获取令牌

      • 阻塞与非阻塞

        • 非阻塞:tryAcquire()立即返回成功/失败

        • 阻塞:acquire()可能等待知道令牌可用

      • 预热模式(Guava rateLimiter)

  • 漏桶算法:

    • 严格输出控制:无论输入多快,输出速率恒定

    • 请求排队:超限请求可以选择排队而非直接拒绝

    • 无需获取令牌,但需要等待漏桶处理

    • 通过水位控制,请求相当于水滴

  • 实践建议方案:

    • redis+lua分布式令牌桶

    • 自适应限流(结合多个算法)

      • 请求入口 → 滑动窗口(防突发) → 令牌桶(平滑控制) → 漏桶(保护下游)
                              ↘ 动态调整限流阈值 ↗

    • 其他建议:

      • 需要允许突发流量:令牌桶算法

      • 严格限制瞬时流量:滑动窗口算法

      • 保护脆弱下游系统:漏桶算法

      • 分布式环境:redis实现+定期同步

      • 超高并发场景:本地限流+全局分层设计

Q:幂等接口的实现方式?
幂等接口方案

幂等接口是指对同一操作的多次执行与一次执行的效果相同。在消息队列场景下,由于网络抖动、消费者重试等原因可能导致消息重复消费,必须实现幂等处理。

  • 基础实现方案:
    • 唯一标识法:使用所有的幂等接口
    • 数据库唯一标识:根据数据库主键ID创建
    • 乐观锁实现:在更新数据库标识时确定
  • 高级实现方案:
    • 状态机幂等:所有状态流转的业务
    • token机制:防止表单重复提交
    • 分布式锁实现
    • 消息队列幂等消费
  • 幂等接口注意:
    • 方案选择建议:
      • 创建操作:唯一约束法
      • 更新操作:乐观锁/状态机
      • 删除操作:标记删除+状态检查
      • 复杂操作:分布式锁+唯一ID
    • 设计要点:
      • 前端防重:提交按钮禁用、Token机制
      • 网络层:合理设置HTTP方法(PUT/DELETE天然幂等)
      • 超时处理:设置合理的请求过期时间
      • 结果查询:为异步操作提供查询接口
    • 性能优化:
      • redis代替数据库做幂等校验
      • 本地缓冲+分布式缓冲多级校验
      • 非关键业务可适当降低幂等要求

消息队列幂等方案对比
方案实现复杂度适用场景优缺点
唯一消息ID所有消息场景简单但需存储ID状态
业务状态机有状态流转的业务精准但实现复杂
数据库唯一约束数据库操作场景依赖数据库能力
乐观锁更新操作场景需设计version字段
分布式锁高并发敏感场景强一致但性能有损耗
  • 具体实现:
    • 唯一消息ID+去重表
      • 检查消息ID是否已处理
      • 开始处理(在事务中标记消息为处理中)
      • 业务处理
      • 标记处理成功
      • 处理失败回滚
    • 业务状态机实现幂等
      • 根据业务数据的状态机进行检查,判断是否处理及处理成功,在进入处理
    • redis原子操作
      • setNX数据锁定在进行操作
    • 分布式锁+幂等表
      • 通过redis等生成唯一业务ID
      • 获取分布式锁
      • 检查幂等记录后进行业务处理
      • 创建处理中的记录
      • 执行业务逻辑
      • 更新幂等记录
    • kafka exactly once语义
      • 生产者配置
      • 消费者配置
      • 消费者幂等处理
      • 使用业务唯一键保证幂等
  • 最佳实践
    • 消息ID生成规则
      • 生产者生成全局唯一ID(UUID、Snowflake)
      • 建议格式:业务前缀:唯一标识
    • 设计去重表,添加唯一ID 业务类型 状态 和存储业务结果等
    • 过期策略
      • redis设置合理TTL(通常为2-72)小时
      • 数据库定期清理一完成记录(如一个月前的数据)
    • 异常处理:
      • 网络异常时做好状态回滚
      • 设置最大重试次数避免死循环
      • 对关键业务实现步长机制
    • 监控指标
      • 消息重复率
      • 幂等拦截次数
      • 平均处理延时
      • 存储空间使用量

不同消息队列实现对比

消息系统幂等特点推荐方案
Kafka支持Exactly-Once语义消息ID+生产者幂等+事务ID
RabbitMQ需自行实现幂等去重表+消费者确认
RocketMQ支持消息去重(商业版)唯一KEY+状态检查
Pulsar支持消息去重(基于序列号)序列号检查+业务状态机

Q:批量消费,入库?

Q:库存拆分?

相关文章:

  • LRU Java实现
  • 移动自动化测试-appium
  • jQuery — 动画和事件
  • kimi+deepseek制作PPT
  • 【java实现+4种变体完整例子】排序算法中【桶排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
  • spring-batch批处理框架(2)
  • 已注册商标如何防止被不使用撤销!
  • UDS中功能寻址可以请求多帧数据嘛?当ECU响应首帧后,诊断仪是通过物理寻址发送流控帧嘛?
  • 如何给云开发生成的智能体增加权限判断
  • 【排队论】Probabilistic Forecasts of Bike-Sharing Systems for Journey Planning
  • NestJS——使用TypeORM连接MySQL数据库(Docker拉取镜像、多环境适配)
  • 文献×材料 | 基于ANSYS的刹车片环保材料分析研究
  • CRC实战宝典:从原理到代码,全面攻克循环冗余校验
  • Transformer 进阶:拥抱预训练模型,迈向实际应用
  • DDPM(diffusion)原理
  • opencv练习
  • 16、堆基础知识点和priority_queue的模拟实现
  • opencv(双线性插值原理)
  • 解决 Vue3 项目中使用 pdfjs-dist 在旧版浏览器中的兼容性问题
  • 智能座舱架构与芯片 - 背景篇
  • 著名作家、中国艺术研究院原常务副院长曲润海逝世
  • 钱理群|直面衰老与死亡
  • 阿塞拜疆总统阿利耶夫将访华
  • 美国多地举行抗议活动,特朗普经济政策支持率创新低
  • 南华期货递表港交所,冲刺第二家“A+H”股上市期货公司
  • 租到“甲醛房”,租客可以解约吗?租金能要回来吗?