这可能是我在博客园的第一篇认真写的文章,由于之前的公司工作太忙,一直没有时间管理,平时登录博客也只是把不常见问题的解决办法记录一下,现在离职了,时间较为富裕,在准备新面试之前将去年遇到的难点一一梳理一下。
高并发业务场景在电商系统中经常出现,尤其是库存方面,搞不好就要超卖,给公司造成直接的经济损失,虽然解释权在公司,但这也对用户的体验不好,下面我会将去年遇到的高并发抢红包解决方案与代码写下来。
3万会员均摊20万元现金红包,并假设流量点在同一时间,同时涌入,如果按照正常的业务逻辑的话,发生超卖的情况是必须的,如果从代码层面限制,比如引入synchronize、lock一类的锁机制控制的话,首先在分布式系统中是不支持的,仅适合单机,单机又容易引发单点故障,性能等问题,所以此种做法不推荐。
1、悲观锁
这里我们使用数据库内部机制提供的一种锁的办法,在多线程并发竞争期间,如果有一条线程占有了控制权,那么其他线程将无法获取直到线程释放控制权再次竞争,这种做法能完全解决超卖问题的发生,但随之而来的是:
CPU资源,使得性能下降,在高并发环境中,会带来非常恐怖的后果。
但这种做法不是不可以用,需要考虑实际业务以及流量,例如大额交易,通过风险控制系统引导,超过上千万的单笔交易,通知人工监控是一个方案,引入悲观锁来加强安全也是可行的。
实现方法:
select xxx from xxx where xxx = xxx for update
如果条件为主键索引,那么此次获得的锁将是一个行级锁,如果是非主键字段,那么锁机制可能会把整张表锁定mysql数据库就会自己根据实际情况选择哪一种更适合,有可能你是主键索引,应该是得到行锁,但如果mysql认为表锁更合适,你获得的将是表锁。具体业务具体分析。
2、乐观锁
CAS原理,在Java语言中concurrent包就是建立在CAS基础上的。
CAS?
CAS,compare and swap的缩写,中文翻译成比较并交换。
概述:对多线程共享资源,先取得旧值保存,在提交时进行取现有值与原有值进行比较
ABA?
CAS原理,就会引出另一个问题,那就是经典ABA问题,这个东西介绍起来篇幅太大,具体的请网上搜索资料,简单来讲就是由于多线程之间业务逻辑问题会导致取到的旧值发生改变,存在回退的可能性,解决办法是在数据表中加入一个非关键的version字段,强制递增,没有回退操作,ABA问题就解决了。
实现方法:
update xxx set xxx = xxx, version = version + 1
经过测试,这种做法跟一开始不用任何锁的性能是基本一致的,但是这种做法会大大的提高失败率,如果业务要求不严格的话,到这基本就可以解决我们的问题,那么接下来该怎么解决失败率的问题?
可重入锁:
XXX路停下,那么下次我怎么才能再找到这辆单车?用最原始的方法,我在这个地方画个圈圈什么的标记一下,那么可重入锁也是这样实现。
zookeeper的子节点来实现,我们只是单独实现重入机制
for循环来重试即可,此操作幂等的,所以数据不会错乱,一般情况下只要上一步实现就可以了,为了保险可以加入重入机制