分布式锁的实现核心思路:找一个公共区来生产和存放锁。
Because:既然是分布式,在单机上创建的实例对象(包括锁对象),也只能作用于本机上,无法保证分布式中多台服务器的一致性,所以此时的锁一定要放在一个公共的区域去创建,各个分布式服务都去访问这个公共区域。
最常见的公共区域有:数据库,redis和zookepper。具体分析如下:
一:数据库
方法1:
1:在数据库中创建一张存储分布式锁的表,例如:lock_record. 主要的两个字段:第一个主键id,第二个锁名lock_name(唯一类型)
2:在程序中实现上锁的时候,先去创建这个锁。如果创建失败,说明此锁已经存在(因为lock_name是唯一的),被占用,其他的请求阻塞等待。
3:如果创建成功,拿到锁了,就可以执行请求。待执行完毕,释放锁(删除此条记录),其他请求可以重复2和3操作。
缺点:
1:访问数据库成为了瓶颈,效率较低。
2:如果处理请求中出现异常,不能释放锁,其他请求一直进不来,所以在程序中需要设计一个超时处理,超时了,进行锁的释放(删除数据库中此条记录)。
方法2:悲观锁
利用select … where … for update 排他锁
注意: 其他附加功能与实现一基本一致,这里需要注意的是“where name=lock ”,name字段必须要走索引,否则会锁表。有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题。
方法3:乐观锁
所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才能觉察到。我们的抢购、秒杀就是用了这种实现以防止超卖。
通过增加递增的版本号字段实现乐观锁
2:redis
核心思路:
上锁:通过setnx指令设置锁值(设置成功返回1,失败返回0);
防止死锁:通过expire指令设置过期时间(很重要,防止程序处理异常,不能释放锁);
释放锁:通过delete指令删除key,拿到锁的请求执行完之后要释放锁,让其他请求能拿到锁。
But:有个严重的问题,你知道? 集群下,redis主节点突然挂了,怎么办?
主节点驾崩了,还没有还得及交出玉玺(锁),此时诸侯(从节点)已经上位直接拿了玉玺。就导致玉玺被两人所持有,不安全产生了。
为了解决上述问题:redis官方推出redisson高性能的分布式锁实现。
3:zookepper
核心思想:
1:创建“有序临时节点+watch监听”
2:每个请求(线程)都会在规定根节点下创建一个临时节点,创建完毕后,获取所有的节点进行排序,比较自己是不是最小的节点。
3:如果是最小的节点,拿到锁,执行请求,最后释放锁(删除自己创建的临时节点)。
4:如果不是最小的节点,会监听前一个节点,当它的前一个节点释放锁时,他会获得锁。 依次类推!