六种手段:
1.页面静态化
商品秒杀页面做静态化处理,常规请求不会到服务端。
2.cdn内容分发
将前端资源缓存到cdn上,就近分发给不同区域的客户端;
秒杀开始后将新的js文件同步到cdn上;
前端加一个控制器,控制同一用户短时间内的请求次数;
3.缓存
用多个缓存节点来抗住请求压力;
缓存击穿:事先做好缓存预热;加分布式锁,避免同一id的商品秒杀请求短时间内都进入到数据库内;
缓存穿透:加布隆过滤器过滤无效请求,但是要保证布隆过滤器的数据与缓存数据一致,只适合缓存读多写少的情形,可以把不存在的商品id也缓存起来;
4.mq异步队列
消息丢失问题:
通过消息发送表来记录消息发送情况,每隔一段时间通过job来重新发送消息。
消息重复问题:
通过维护消息处理表来规避重复消息。
消费延迟问题:
延迟队列:延迟队列是一种特殊类型的队列,它允许消息在指定的时间点被消费。例如,用户下单后,系统可能需要等待一段时间来检查订单的支付状态,如果未支付则关闭订单。
5.限流
同一ip地址限流
同一用户限流
6.分布式锁
分布式锁的过期时间:设置过期时间,即使在锁的持有者出现异常或者忘记释放锁的情况下,锁也会在到达预设的过期时间后自动释放,这样其他线程或进程就有机会获取到锁,继续执行任务。
redis setnx命令加锁,但是加锁和设置过期时间不是原子的,可能会导致锁的过期时间设置失败,造成死锁等问题。
redis set命令加锁可以保证原子性加锁和设置过期时间:使用SET
命令将一个键值对写入Redis,其中键表示锁的名称,值表示锁的持有者(通常是线程或进程的唯一标识)。同时,设置一个过期时间,以防止锁一直被持有而无法释放。
SET lock_key lock_value NX PX expire_time
其中,lock_key
是锁的名称,lock_value
是锁的持有者(这里一般为request_id,保证在释放锁时不会释放错),NX
表示只有当键不存在时才进行设置,PX
表示设置键的过期时间,expire_time
是过期时间(单位为毫秒)。
问题:
库存超卖:
数据库扣减库存:
通过加锁来保证数据库读库存和更新库存的原子性,但是对性能有影响,可以用数据库乐观锁来提高性能:UPDATE product SET stock = stock - 2, version = version + 1 WHERE product_id = 1 AND version = 1;(乐观锁:在读取数据时并不加锁,而是在数据更新时检查在此期间是否有其他事务对数据进行了修改)。
缓存扣减库存:
缓存库存,减少mysql压力,redis的incr方法是原子性的,可以通过该方法来扣减库存。(incr用来更新数据,只能保证原子写,想要保证原子读写,可以通过加锁的方式来实现,同样的性能会有损耗)
通过lua脚本来修改库存,lua脚本本身保证原子性。