高并发下设计秒杀系统

高并发下设计秒杀系统(类似疫情期间定时抢购)

现象:前几分钟,用户并发量才真正突增,达到秒杀时间点时,并发量会达到顶峰。
只有极少部分用户可以完成请求,实现下单。瞬时并发
用户往往不停刷新页面,但只有到了时间点才会变成可点击的按钮。

核心逻辑

1. 减少不必要的请求
	1. 避免非关键请求
		1. 页面静态化:用户浏览商品等常规操作,并不会请求到服务端。只有到了秒杀时间点,并且用户主动点了秒杀按钮才允许访问服务端。
			1.  CDN加速:使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
	2. 避免关键请求的随意触发
		1. 秒杀按钮:js文件控制,未到时间置灰
			1. CDN上的是如何更新的?
			2. 当秒杀开始的时候系统会生成一个新的js文件,此时标志为true,并且随机参数生成一个新值,然后同步给CDN。由于有了这个随机参数,CDN不会缓存数据,每次都能从CDN中获取最新的js代码。
			3. 前端还可以加一个定时器,控制比如:10秒之内,只允许发起一次请求。如果用户点击了一次秒杀按钮,则在10秒之内置灰,不允许再次点击,等到过了时间限制,又允许重新点击该按钮。
		2. 限流(防羊毛党、爬虫
			1. 限制同一用户:限制id、ip(容易误伤)
			2. 接口本身限流
				1. 如何实现?
					1.  基于nginx限流
					2.  基于redis限流
			3. 验证码机制(移动滑块 优于 数字验证
			4. 提高业务门槛(只有指定会员等级才能发出请求
2.  加速请求的完成
		1. 缓存(redis 集群
			1. 场景本身读多写少:下单前需要查询剩余量,够才能下单。
			2. 缓存带来的问题
				1. 缓存击穿(第一次秒杀,查询值都不存在缓存
					1. 分布式锁(保险
					2. 缓存预热(防击穿,同时查缓存未命中后查询数据库,数据库挂掉,但仍怕过期
				2. 缓存穿透(有大量的请求传入的商品id,在缓存中和数据库中都不存在
					1. 有锁问题不大,但性能差
					2. 布隆过滤器(加在访问缓存之前
						1. 布隆过滤器中的数据如何更缓存中的数据保持一致?
							1. 绝大部分使用在缓存数据更新很少的场景
							2. 需要同步,同步失败需要重试,跨数据源如何保证实时一致性?(布隆过滤器只适合数据更新少的场景)
					3. 不存在的商品id缓存:先访问布隆过滤器,然后再检查缓存,最后在布隆过滤器和缓存都未命中时将不存在的商品ID缓存。
		2.  异步处理下单
			1. 介绍:秒杀、下单、付款三个步骤,下单异步,即秒杀后发送msg 到mq,下单服务消费消息处理请求。
			3. 问题:
				1. 消息丢失问题:消息发送记录表
					1. 秒杀后加消息发送记录表,下单后标记已处理。
					2. 定时查询发送记录表,失败则重新发送mq消息
					3. 如果已经下单,但回调失败,是否会导致重复下单?
				2. 重复消费问题:加消息处理表
					1. 消费者读到消息之后,先判断一下消息处理表,是否存在该消息,如果存在,表示是重复消费,则直接返回。如果不存在,则进行下单操作,接着将该消息写入消息处理表中,再返回。
					2. 有个比较关键的点是:下单和写消息处理表,要放在同一个事务中,保证原子操作。
				3. 垃圾消息问题
					1. 消费者下单一直失败,一直不能回调状态变更接口,这样job会不停的重试发消息。最后,会产生大量的垃圾消息。
					2. 每次在job重试时,需要先判断一下消息发送表中该消息的发送次数是否达到最大限制,如果达到了,则直接返回。如果没有达到,则将次数加1,然后发送消息。
					3. 这样如果出现异常,只会产生少量的垃圾消息,不会影响到正常的业务。
				4. 延迟消费问题
					1. 在15分钟内未完成支付,订单被自动取消
					2. job 实时性不好
					3. 延迟队列
						1. 下单时消息生产者会先生成订单,此时状态为待支付,然后会向延迟队列中发一条消息。
						2. 达到了延迟时间,消息消费者读取消息之后,会查询该订单的状态是否为待支付。
						3. 如果是待支付状态,则会更新订单状态为取消状态。
						4. 如果不是待支付状态,说明该订单已经支付过了,则直接返回。
						5. 还有个关键点,用户完成支付之后,会修改订单状态为已支付。
3. 请求有效性保障
	1. 库存超卖问题
		1. 库存的预扣和回退:用户在一段时间内,还没完成支付,需要回退库存。
		2. 数据库扣减库存
			1. update set
			2. 如何控制库存不足的情况下,不让用户操作呢?
			3. update之前,先查一下库存
			4. 查询操作和更新操作不是原子性的,会导致在并发的场景下,出现库存超卖的情况
			5. update product set stock=stock-1 where id=product and stock > 0;高并发竞争行锁,容易死锁。
		3. redis扣减库存
			1. redis的incr是原子性的
			2. 加锁,synchronized性能差
			3. 库存出现负数,不会出现超卖的问题
		4. lua脚本扣减库存

背景知识补充

什么是布隆过滤器?

布隆过滤器是一种概率型数据结构,用于快速判断某个元素是否属于一个集合,它可以高效地判断一个元素是否一定不存在于集合中,但存在一定的误判率。

如何用 redis扣减库存?

boolean exist = redisClient.query(productId,userId);
if(exist) {
  return -1;
}
if(redisClient.incrby(productId, -1)%3C0) {
  return 0;
}
redisClient.add(productId,userId);
return 1;

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

该方案咋一看,好像没问题。

但如果在高并发场景中,有多个请求同时扣减库存,大多数请求的incrby操作之后,结果都会小于0。虽说,库存出现负数,不会出现超卖的问题。

但由于这里是预减库存,如果负数值负的太多的话,后面万一要回退库存时,就会导致库存不准。

缓存击穿和缓存穿透的区别?

  • 缓存击穿是指请求一个缓存中不存在的数据,导致请求直接访问底层存储系统,增加负载压力。
  • 缓存穿透是指恶意请求大量不存在于缓存中的数据,导致请求无法在缓存中命中,增加系统负载,可能成为拒绝服务攻击。

为什么要将下单设置成异步请求?

将下单过程设计为异步的,在秒杀、下单、付款三个环节中可以带来以下好处:

  1. 减少响应时间:在秒杀活动中,由于用户的抢购行为会导致短时间内大量的请求涌入系统。如果下单过程是同步的,即需要等待每个下单请求完成后才能继续处理下一个请求,系统可能会因为请求的堆积而响应变慢,影响用户体验。而将下单过程设计为异步后,系统可以立即响应下单请求并返回成功响应,然后在后台异步处理下单逻辑,使得系统能够更快地响应用户的请求。
  2. 提高并发处理能力:异步处理下单可以减少请求的串行处理,提高系统的并发处理能力。在秒杀活动期间,可能会有大量用户同时提交下单请求,如果下单是同步的,系统需要一个一个地处理这些请求,导致处理速度较慢。而异步处理下单可以将请求放入消息队列或任务队列中,由后台的工作线程或消费者异步处理,极大地提高了系统的并发处理能力,能够更好地应对高并发的情况。
  3. 增加系统的稳定性和可靠性:异步下单可以将下单请求与后续的付款过程解耦,即使在下单后出现问题(如库存不足、支付失败等),也不会影响其他用户的下单行为。系统可以在后台异步处理下单请求的同时,进行库存检查、支付状态验证等操作,以保证订单的准确性和完整性。这种解耦和异步处理方式可以增加系统的稳定性和可靠性,降低单点故障的风险。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值