某商品库存10,A想买6,B想买5。
A先买走6,库存剩4,此时B应该无法购买5,给出数量不足提示
AB获取到商品都剩10,A买走6,在A更新库存前,B又买走5,此时B更新库存,商品还剩5。
给共享资源或对共享资源的操作加锁,来保证对资源的访问互斥。利用ReentrantLcok
或者synchronized
即可。
但是在分布式系统中,由于分布式系统的分布性,这两种锁将失去原有锁的效果。 必须使用分布式锁。
一、数据库锁
二、缓存锁 这里主要是几种基于redis的
setnx
、expire
基于setnx(set if not exist)的特点,当缓存里key不存在时,才会去set,否则直接返回false
如果返回true则获取到锁,否则获取锁失败,为了防止死锁,我们再用expire命令对这个key设置一个超时时间来避免。
但是这里看似完美,实则有缺陷,当我们setnx成功后,线程发生异常中断,expire还没来的及设置,那么就会产生死锁。解决上述问题有两种方案
EX seconds
– 设置键key的过期时间,单位时秒
PX milliseconds
– 设置键key的过期时间,单位时毫秒
NX
– 只有键key不存在的时候才会设置key的值
XX
– 只有键key存在的时候才会设置key的值
redlock算法是redis作者推荐的一种分布式锁实现方式 (1) 获取当前时间; (2) 尝试从5个相互独立redis客户端获取锁 (3) 计算获取所有锁消耗的时间,当且仅当客户端从多数节点获取锁,并且获取锁的时间小于锁的有效时间,认为获得锁 (4) 重新计算有效期时间,原有效时间减去获取锁消耗的时间 (5) 删除所有实例的锁 redlock算法相对于单节点redis锁可靠性要更高,但是实现起来条件也较为苛刻 (1) 必须部署5个节点才能让Redlock的可靠性更强 (2) 需要请求5个节点才能获取到锁,通过Future的方式,先并发向5个节点请求,再一起获得响应结果,能缩短响应时间,不过还是比单节点redis锁要耗费更多时间 然后由于必须获取到5个节点中的3个以上,所以可能出现获取锁冲突,即大家都获得了1-2把锁,结果谁也不能获取到锁,这个问题,redis作者借鉴了raft算法的精髓,通过冲突后在随机时间开始,可以大大降低冲突时间,但是这问题并不能很好的避免,特别是在第一次获取锁的时候,所以获取锁的时间成本增加了 如果5个节点有2个宕机,此时锁的可用性会极大降低,首先必须等待这两个宕机节点的结果超时才能返回,另外只有3个节点,客户端必须获取到这全部3个节点的锁才能拥有锁,难度也加大了 如果出现网络分区,那么可能出现客户端永远也无法获取锁的情况 介于这种情况,下面我们来看一种更可靠的分布式锁zookeeper锁
zookeeper是一个为分布式应用提供一致性服务的软件,它内部是一个分层的文件系统目录树结构,规定统一个目录下只能有一个唯一文件名
根据zookeeper的这些特性来实现分布式锁
在分布式系统中,共享资源互斥访问问题非常普遍,而针对访问共享资源的互斥问题,常用的解决方案就是使用分布式锁,这里只介绍了几种常用的分布式锁,分布式锁的实现方式还有有很多种,根据业务选择合适的分布式锁 下面对上述几种锁进行一下比较:
优点:直接使用数据库,使用简单。 缺点:分布式系统大多数瓶颈都在数据库,使用数据库锁会增加数据库负担。
优点:性能高,实现起来较为方便,在允许偶发的锁失效情况,不影响系统正常使用,建议采用缓存锁。 缺点:通过锁超时机制不是十分可靠,当线程获得锁后,处理时间过长导致锁超时,就失效了锁的作用。
优点:不依靠超时时间释放锁;可靠性高;系统要求高可靠性时,建议采用zookeeper锁。 缺点:性能比不上缓存锁,因为要频繁的创建节点删除节点。