秒杀系统设计与实现

将从11 个点来进行秒杀系统得设计与实现分析。

1、哪些地方存在瞬时高并发?

  • 1. 预抢购业务:活动未正式开始前,先进行活动预约。在真正秒杀的时间点,很多数据都是预处理好的了,可以很大程度削减系统压力。比如:活动预约、订金预约、火车票预约等
  • 2. 分批抢购业务:分时段多场次抢购,比如我们熟悉的京东满减优惠券就是分场次开放的,整点抢购。
  • 3. 实时秒杀:这是最有难度的秒杀场景,比如双11晚上0点秒杀,在这个时间点前后会涌入高并发流量:频繁刷新页面、疯狂点击抢购按钮、甚至利用机器模拟请求。

2、活动页面得设计

因为用户浏览商品等常规操作,并不会请求到服务端。只有到了秒杀时间点,并且用户主动点了秒杀按钮才允许访问服务端。所以为了减少不必要的服务端请求,通常情况下,会对活动页面做静态化处理

CDN,它的全称是Content Delivery Network,即内容分发网络。用于用户分布在全国各地,有些人在北京,有些人在上海,有些人在深圳,地域相差很远,网速各不相同得情况。

3、秒杀按钮

使用js文件控制秒杀活动在活动开始前得秒杀按钮,可以通过在js中设置标记的方式来设置按钮的状态,比如isBegin=true代表活动开始,isBegin=false代表活动未开始。秒杀开始之前,js标志为false,秒杀活动开始时设置为true。为了达到这个效果,我们另外还需要一个随机参数用来主动刷新CDN。

当秒杀开始的时候系统会生成一个新的js文件,此时标志为true,并且随机参数生成一个新值,然后同步给CDN。由于有了这个随机参数,CDN不会缓存数据,每次都能从CDN中获取最新的js代码。

4、多读写少

这种情况下就要考虑做缓存,使用 redis对访问读得数据进行缓存。

5、缓存怎么做?

缓存雪崩:大量缓存数据同时过期、redis宕机故障。解决方法:给key添加随机过期时间。

缓存击穿:热点数据缓存过期,一个或几个高并发使用得Key失效。 解决方法:分布式锁,redis里面没有的时候,查数据库得时候要先获取分布式锁。 预热,事先把参与秒杀的所有商品,同步到缓存中,这样商品基本都能直接从缓存中获取到,就不会出现缓存击穿的问题了。双保险

缓存穿透:数据不存在,既不在redis里面,也不在数据库里面。解决方法:布隆过滤器,布隆过滤器(BloomFilter)是一种数据结构。特点是存在性检测如果布隆过滤器中不存在,那么实际数据一定不存在;如果布隆过滤器中存在,实际数据不一定存在。相比于传统数据结构(如:List、Set、Map等)来说,它更高效,占用空间更少。缺点是它对于存在的判断是具有概率性。但是有一个数据一致性问题,所以布隆过滤器建议使用在缓存数据更新很少的场景中。

可以巧妙设计一下,把不存在的商品id也缓存起来。

6、库存问题

为了防止超卖或负数得情况,就要考虑 预扣库存。

数据库扣减库存:基于数据库的乐观锁,这样会少一次数据库查询,而且能够天然的保证数据操作的原子性。

 redis扣减库存:redis的incr方法是原子性的,可用于扣减库存 

  1. 先判断该用户有没有秒杀过该商品,如果已经秒杀过,则直接返回-1。
  2. 扣减库存,判断返回值是否小于0,如果小于0,则直接返回0,表示库存不足。
  3. 如果扣减库存后,返回值大于或等于0,则将本次秒杀记录保存起来。然后返回1,表示成功。

Lua脚本扣减库存:使用Lua脚本的好处如下:

  1. 减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延
  2. 原子操作:redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本执行过程中无需担心会出现竞态条件,无需使用事务
  3. 复用:客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑

7、分布式锁

使用redis的set命令:result,err := redis.set(lockKey, requestId, "NX", "PX", expireTime);

  1. lockKey:锁的标识
  2. requestId:请求id。在释放锁得时候,使用记录得requestId。
  3. NX:只在键不存在时,才对键进行设置操作。
  4. PX:设置键的过期时间为 millisecond 毫秒。
  5. expireTime:过期时间

自旋锁:思路如下:

  1. 在规定的时间,比如500毫秒内,自旋不断尝试加锁
  2. 如果成功则直接返回
  3. 如果失败,则休眠50毫秒,再发起新一轮的尝试。
  4. 如果到了超时时间,还未加锁成功,则直接返回失败。

8、mq异步处理

如果使用mq,需要关注以下几个问题:

  1. 消息丢失问题
  2. 消息重复消费问题
  3. 垃圾消息问题
  4. 延迟消费问题

8.1消息丢失问题:加一张消息发送表、进行消息重发。

其流程如下:

  1. 在生产者发送mq消息之前,先把该条消息写入消息发送表,初始状态是待处理
  2. 然后再发送mq消息。
  3. 消费者消费消息时,回调生产者的一个接口,处理完业务逻辑之后,修改消息状态为已处理。

使用job,增加重试机制。用job每隔一段时间去查询消息发送表中状态为待处理的数据,然后重新发送mq消息。

8.2、重复消费问题加一张消息处理表

8.3、 垃圾消息问题:限制重试次数

8.4、 延迟消费问题使用延迟队列,比如:RocketMQ,自带了延迟队列的功能。

  1. 下单时消息生产者首先生成订单,此时为待支付状态。
  2. 然后向延迟队列中发一条消息。
  3. 当达到了延迟时间,消息消费者读取消息之后,会查询该订单的状态是否为待支付。
  4. 如果是待支付状态,则会更新订单状态为取消状态。
  5. 如果不是待支付状态,说明该订单已经支付过了,则直接返回。

9、限流

对同一用户限流对同一ip限流 对接口限流 加验证码提高业务门槛

提高业务门槛流程是仿12306进行业务设计:

  1. 我们可以通过提高业务门槛,比如只有会员才能参与秒杀活动,普通注册用户没有权限。
  2. 或者只有等级到达3级以上的用户,才有资格参加该活动。
  3. 或者分时间段获得秒杀资格,比如9点、10、11点、参加活动获得秒杀资格,获得资格的朋友12点集中参与秒杀。

10、数据库层隔离

分库分表:先从代码、SQL语句、索引这几个方面着手优化,不行了在进行分库分表。

数据隔离:用专门的表来存放数据,不建议使用业务系统正在使用的表来存放秒杀相关的数据。

数据合并:如果秒杀系统是用的专用表存储,在秒杀活动结束后,需要将其和现有数据进行合并。

11、压力测试

正压力测试:在保证服务器资源不变的情况下,网络请求不断做加法。

负压力测试:在系统正常运行的情况下,逐渐减少支撑系统的服务器资源,观察什么时候系统无法在支撑正常的业务请求。

压力测试步骤:

1.确定测试目标

压力测试和性能测试不同,压力测试的目标是什么时候系统会接近崩溃,比如需要支持100万的访问量,测试出性能阈值。

2. 确定关键问题

二八原则大家一定要知道,压力测试也是有重点的,系统中只有20%的功能是最常用的,比如秒杀接口、下单、扣减库存。要集中火力测试常用的功能,高度还原真实场景。

3. 确定负载

和上面观点一样,不是每个服务都有高负载,测试时要重点关注高负载的服务,真实场景中服务的负载一定是波动的,并且不是均匀分布的。

4. 搭建环境

搭建环境要和生产环境保持一致。

5. 确定监测指标

提前确定好要重点监测的参数指标,比如:CPU负载、内存使用率、系统吞吐量、带宽阈值等

6. 产生负载

  1. 建议优先使用往期的秒杀数据,或者从生产环境中同步数据进行测试
  2. 根据目标系统的承受要求由脚本驱动测试
  3. 模拟不同网络环境,对硬件条件有规律的进行测试

7. 执行测试

根据目标系统、关键组件、用负载进行测试、返回监测点的数据。

8. 分析数据

针对测试的目的,对关键服务的压力测试数据进行分析,得出这些服务的承受上限在哪里?

对有波动的负载或者大负载的的服务进行数据分析,明确优化的方向。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值