什么是秒杀系统?
在同一时刻有大量请求争抢购买同一个商品,并完成交易的过程,其间涉及大量的并发读和并发写,并要求高可靠和高性能的系统支持。
对比京东与淘宝对商品的定义, 淘宝采用SPU,京东采用SKU(选择京东某一个商品型号后,整体界面刷新,而淘宝则不会刷新下面的商品详情),这个差别很重要。
秒杀的关键点在于从"下单" 到"付款“的过程中如何解决”并发写“的问题。
是下单扣库存? 还是付款扣库存?
如果选择下单扣库存,则无法避免恶意用户恶意下单;
如果选择付款扣库存,则部分用户因晚付款,发现拍下的订单,库存变没了,从而导致用户体验差。
补充一个中台的概念: 当年阿里想在上层拆分没成功,在下层拆分也没成功,最后在中间拆分(比如:订单系统,库存系统)做出来了。实属因业务场景导致中台架构的产生,而现在又到了需要考虑拆中台的思路上来了。
我们谈的高可用,本质是要求不要单点,要采用负载均衡。
采用锁的思路,其实是在减库存前,先查询库存是否>0,然后在扣减库存:
我们以10个人秒杀2个小米手机为例,如果是单机环境下,可采用JVM锁实现秒杀,如下所示:
通过Jmeter进行压测:
查看数据库订单于库存表,可以实现秒杀:
而高可用的系统要求4要1不要,即数据要尽量少,请求数要尽量少,路径要尽量短,依赖要尽量少,而不要就是:不要单点!!!避免单点故障。
四高的架构包括:高可用,高扩展,高性能,高安全。
本例中,我们用Ribbon做负载均衡,代码不改,增加一个服务。
重启负载均衡,保证从eureka能够拉取两个服务。
在集群环境下,恢复原始数据后,
运行压测,结果超卖了,库存表变成了-1。。。
原因是JVM锁是各个机器上的锁,运行在各个机器上的JVM锁是不一样的。因此各自拿着一把锁,进去创建订单还减库存了。解决的办法是想办法将锁统一为唯一的一个。
集群环境下分布式锁的解决方案包括:MySQL方案,Redis方案,Zookeeper方案等。
方案一:MySQL锁
采用MySQL锁,对lock和unlock里的代码,其实是一条insert和一条delete语句,用到了数据记录的唯一索引作为锁。其代码实现细节如下所示:
通过insertPrimaryKey和deleteByPrimaryKey,借助唯一键值实现加解锁。最终实现分布式锁功能。
但MySQL受限于连接数和硬盘IO,秒杀极限只能做到万级QPS。还是在16U64G的配置基础上
方案二 Redis分布式锁方案
Redis分布式锁主要借助SETNX实现,即:Set if Not Exists。如果key值不存在,则set;否则不做任何动作。
这里涉及过期时间问题,比如将key:Value 设置为10S过期。如果程序需要14S才能执行完,则另一个程序在10S时就能进来获取锁。
这里,为了防止释放别人的锁,在del key释放锁前,判断获取的key值是否等于自己加的值,如果相等才释放锁,否则就不做 *** 作,避免释放别人的锁。
但锁仍然会过期,怎么办呢? 采用看门狗的方法,比如锁的持有时间默认时1分钟,看门狗在30秒时就再将时间延迟到1分钟,一直这样,直到主人从门里出来,释放锁。
代码里一开始默认让程序睡眠五分钟:
将数据库中的库存恢复为2:
代码里的看门口实现细节如下,通过scheduleExpirationRenewal方法,delay:internalLockLeaseTime/3,在设定时间(比如30秒走到20秒),将时间重新设为原来的时间。
最后还剩下一个问题,即Redis单节点故障问题。我们通常采用一主,二从,三哨兵的结构。
但是,当Redis从master同步到slave过程中,如果Redis master挂了怎么办?锁可能没有同步到slave上,这时,另外一个服务成功访问slave并且获得锁。从而造成两个服务都能访问。
解决的办法是,在系统里设置5个Redis,某个业务只有成功设置3个Redis锁,才能真正获得锁,否则无法获得锁。并且在代码里设定5台Redis相关配置信息。即使有一台挂了,仍然是剩余的四台里设置至少三台才能获得锁。
但当运维人员发现有一台Redis挂了,迅速补充一台Redis会怎么样呢?这样就有可能使另一个业务获得3个redis锁,从而导致两个服务都能访问到。
解决的办法是,在运维手册里写清楚,如果Redis挂了,24小时后运维人员才可以增加新的Redis服务,不要急于增加一台Redis,这样就可以避免两个线程拿到同一把锁。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)