秒杀系统设计重点

秒杀系统设计重点

系统特点

1. 瞬时高并发

仅在秒杀时间点的前后几分钟,流量突增。

2. 读多写少

大量用户抢少量商品,大部分用户的访问不与修改库存的操作关联。

高性能

1. 动静分离

将动态页面改造成静态页面,可以将静态数据放在CDN中进行缓存。

2. 热点操作优化

刷新、下单、添加购物车一类的操作,是用户的行为,在秒杀时间点大量发生,可以做一些限制保护。如用户频繁刷新页面时提示、添加验证码。

3. 热点数据优化

3.1 热点识别

能够提前预测出的热点数据,如商家数据、活动促销等的数据,比较容易做到提前准备;

但也存在随着实际业务变化的突发热点,可能集中在某个平时比较冷门的商品上。需要动态的热点发现能力。

  • 采集热点信息:nginx采集URL日志,提前识别潜在的热点实际;
  • 热点发送:通过一定规则将热点数据推送给各个系统,每个系统决定不同的处理策略。

3.2 热点隔离

将识别到的热点请求和普通请求区分开。可以为秒杀系统部署单独的域名和集群(系统隔离),也可以为热点数据启用单独的缓存或数据库(数据隔离)。

3.3 热点优化

缓存和限流。使用redis作为缓存。

一致性

1. 库存问题

核心问题是避免超卖。将购买过程分为两步:下单和付款。

采用预扣库存的方式。下单后,先减库存,一段时间没有付款,库存自动释放。

  • 避免恶意下单:用户打标、限制最大购买数;可以对用户或者ip限流,但都会存在一些问题:恶意请求可以使用多个用户,多个普通用户也可能共享IP。更好的方式是验证码。
  • 避免超卖:如果是普通商品,允许一定量的超卖,可以通过补货来解决。但有些商品库存不允许为负,可以通过数据库事务来避免,出现负数回滚。
  • 自动释放:采用延迟队列,下单后向延迟队列发送一条消息,消息超时后,消费者会读取消息并查询订单状态。

2. 高并发读

“分层校验”:读请求只做业务检查(如用户是否有秒杀资格、商品状态等),不做一致性校验,因为会影响性能。在写时才对库存做一致性检查。缓存中允许一定的脏数据,这样只会导致少量下单请求被认为有库存,在写数据时在保证最终一致性。

3. 高并发写

如果逻辑简单,不依赖事务,可以将商品信息直接放在Redis中操作。如果要放在DB中,可以使用分布式锁控制操作并发度。

  • 添加锁

    • setNx命令(set if not exist):和设置超时时间的操作是分开的,不保证原子性。
    • set命令:同时设置[lockKey, requestId]的键值对,以及过期时间。
  • 释放锁:判断lockKey是否存在,然后比较requestId是否相等,已避免释放其他请求添加的锁(自己的可能已经过期了)。采用lua脚本保证原子性。

  • 自旋锁:用于避免大量请求同时竞争锁导致问题。获取锁失败时,阻塞一段时间,然后重复获取锁。

高可用

核心目标:控制对资源的瞬时消耗

1. 流量削峰

1.1 入口削峰

采用答题或验证码,在入口的层面拉长请求的峰值。

或者增加门槛,如用户等级,来限制用户数。

也可以分批秒杀,将购买周期拉长。

1.2 消息队列

将同步的直接调用转化为异步的间接推送。

  • 消息丢失问题:消费者消费mq中的消息时,可能因为网络或其他问题造成消息丢失。解决办法是添加一张消息发送表。生产者向mq推送消息前,先写入表中。消费者消费完消息,再修改消息状态为已处理;增加一个job,查询消息发送表中待处理数据,重新推送进mq。
  • 重复消费问题:因为增加了重试机制,可能导致重复消费。解决办法是增加一个消息处理表,消费者读取消息后,先检查是否存在表中,不存在才消费;消费消息后,将消息写入表中,要保证消费和写表是原子操作。
  • 垃圾消息问题:存在某些消息无法被成功消费,导致job一直重试。解决办法是限制消息的最大重试次数。

2. 缓存击穿

采用缓存预热,实现将商品放在缓存中;采用分布式锁,如果缓存中不存在对应商品,先获取锁,然后再去数据库查询,查完将商品放入缓存并释放锁。

3. 缓存穿透

如果大量请求传入了不存在的商品id,每次都加锁会导致性能问题。可以在查询缓存前使用布隆过滤器(布隆过滤器通过多个哈希函数将元素映射到位数组中的多个位置,判断时检查这些位置是否全为1,以此实现高效的“可能存在”或“一定不存在”的查询),提前过滤掉不存在的商品。


秒杀系统设计重点
https://buttering.github.io/EasyBlog/2024/11/25/秒杀系统设计/
作者
Buttering
发布于
2024年11月25日
许可协议