我的原则,只有三个字,看心情。
看到这类秒杀,估计很多开发者都头疼,因为你很少真正能在项目接触到,不过没关系,该了解的我们也要了解,以备不时之需。
高并发问题和超卖问题是秒杀活动中常见的2个问题,也是需要面临解决的问题
- 高并发:比较火热的秒杀在线人数都是10w起的,如此之高的在线人数对于网站架构从前到后都是一种考验;
- 超卖:任何商品都会有数量上限,如何避免成功下订单买到商品的人数不超过商品数量的上限,这是每个抢购活动都要面临的难题;
问题:如何解决此问题?
答:前端三板斧(扩容、限流、静态化),后端两条路(内存加排队)
- 前端:
扩容:加机器,这是最简单的方法,通过增加前端池的整体承载量来扛峰值。
限流:一般都会采用IP级别的限流,即针对某一个IP限制单位时间内发起的请求数量,或者活动入口的时候增加游戏或者问问题环节进行销峰操作。
静态化:将活动页面中所有的可静态化元素全部静态化,并尽量减少动态元素,通过CNN(加速服务器)来扛峰值。 - 后端:
内存:将库存从MySQL前移到Redis中,所有的读写操作放到内存中,由于Redis中没有锁,所以不会存在相互等待,并且Redis的读写性能都远高于MySQL,这就解决了性能问题。
排队:引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。
场景:比如我们的商品表 goods , 对应的字段 num 表示当前库存数
超卖具体的实现方法如下:
方法一:mysql 排它锁原理
通过mysql 语句 update goods set num = num - 1 WHERE id = 1001 and num > 0;
假设现在商品只剩下一件了,此时数据库中 num = 1;
但有100个线程同时读取到了这个 num = 1,所以100个线程都开始减库存了。
但最终结果,只有一个线程减库存成功,其他99个线程全部失败。
为何?
这就是MySQL中的排他锁起了作用。
排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
就是类似于我在执行update操作的时候,这一行是一个事务(默认加了排他锁)。这一行不能被任何其他线程修改和读写。
方法二:cas原理
select version from goods WHERE id= 1001
update goods set num = num - 1, version = version + 1 WHERE id= 1001 AND num > 0 AND version = @version(上面查到的version);
这种方式采用了版本号的方式,其实也就是CAS的原理。
假设此时version = 100, num = 1; 100个线程进入到了这里,同时他们select出来版本号都是version = 100。
然后直接update的时候,只有其中一个先update了,同时更新了版本号。
那么其他99个在更新的时候,会发觉version并不等于上次select的version,就说明version被其他线程修改过了。那么放弃这次update
方法三:redis 单线程方法
利用redis的单线程预减库存。比如商品有100件。那么我在redis存储一个k,v。例如 <gs_1001, 100>
每一个用户线程进来,key值就减1,等减到0的时候,全部拒绝剩下的请求。
那么也就是只有100个线程会进入到后续操作。所以一定不会出现超卖的现象。