分布式锁需要具备的条件:
分布式锁主流的实现方案:
1. 基于数据库实现分布式锁:
首先在数据库中创建一张表:
| SQL CREATE TABLE `myLock` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `method_name` varchar(100) NOT NULL DEFAULT '' COMMENT '锁定的方法名', `value` varchar(1024) NOT NULL DEFAULT '锁信息', PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法'; |
如果要锁住某方法,只要执行:
| SQL insert into myLock(method_name, value) values ('m1', '1'); |
method_name做了唯一性约束,一个方法只能存在一条记录,执行完这个方法后,再释放锁:
| SQL delete from myLock where method_name ='m1'; |
这就使用数据库实现了一个简单的分布式锁,但是对比分布式锁的条件,只满足了可斥性。
虽然数据库实现分布式锁非常简单,但是数据库建立连接查询是一个非常耗性能的操作,基本不会使用
2. 基于缓存(Redis等)
基本原理如图:
Redis实现分布式锁
| Java public void testLock() { // 1. 从redis中获取锁,setnx String uuid = UUID.randomUUID().toString(); Boolean lock = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 2, TimeUnit.SECONDS); if (lock) { //自己的业务代码 //TODO // 2. 释放锁 del(使用lua脚本使释放锁是原子性操作) // if(uuid.equals((String)redisTemplate.opsForValue().get("lock"))) { // this.redisTemplate.delete("lock"); // } String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return tostring(redis.call('del',KEYS[1])) else return 0 end"; this.redisTemplate.execute(new DefaultRedisscript<>(script), Collections.singletonList("lock"), uuid); } else { // 3. 每隔1秒钟回调一次,再次尝试获取锁 try { Thread.sleep(500); testLock(); } catch (InterruptedException e) { e.printStackTrace(); } } } |
这种方式存在一些问题:
过期时间太长会影响系统性能,太短有可能会业务未执行完时释放锁
在Redis集群状态下会存在问题:
- 客户端A从master获取到锁
- 在master将锁同步到slave之前,master宕掉了。
- slave节点被晋级为master节点
- 客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。
进阶1:使用Redisson实现分布式锁
1,加锁机制:
如果该客户端面对的是一个redis cluster集群,他首先会根据hash节点选择一台机器,线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。
2.watch dog自动延期机制:
Redisson设计了看门狗机制, 线程1 业务还没有执行完时到达过期时间,线程1 还想持有锁的话,就会启动一个watch dog后台线程,不断的延长锁key的生存时间。
进阶2:Redlock
Redis集群部署时,在master将锁同步到slave之前,master宕掉会造成风险,Redlock可以大大减少这种风险出现的概率,Redlock是redis官方提出的一种分布式锁的算法。Redlock流程为:
- 顺序向集群节点请求加锁
- 根据一定的超时时间来推断是不是跳过该节点
- 2N + 1个节点加锁成功并且花费时间小于锁的有效期
- 认定加锁成功
- 如果获取失败,客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。
- 释放锁时也类似
3. 基于Zookeeper
解释: 左边的整个区域表示一个Zookeeper集群,locker是Zookeeper的一个持久节点,node_1、node_2、node_3是locker这个持久节点下面的临时顺序节点。client_1、client_2、client_n表示多个客户端,Service表示需要互斥访问的共享资源。
下面描述使用zookeeper实现分布式锁的算法流程,假设锁空间的根节点为/lock:
- 客户端连接zookeeper,并在/locker下创建临时的且有序的子节点,第一个获取锁的客户端对应的子节点为/locker/node_1,第二个为/locker/node_2,以此类推。
- 客户端获取/locker下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则创建监听器监听/locker的子节点变更消息,这个被关注的节点删除时,则客户端的监听器收到相应通知,此时再次判断自己创建的节点是否是locker子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤直至获得锁;
- 执行业务代码;
- 完成业务流程后,删除对应的子节点释放锁。
问题:zookeeper会不会出现Redis集群那样的数据同步问题呢?
zookeeper宕机可能发生在Follwer也可能发生在Cilent,分别分析这两种情况:
综上,采用Zookeeper作为分布式锁,你要么就获取不到锁,一旦获取到了,必定节点的数据是一致的,不会出现redis那种异步同步导致数据丢失的问题。
每一种分布式锁解决方案都有各自的优缺点:
1. 性能:redis最高
2. 可靠性:zookeeper最高
Redis分布式锁: Redis分布式锁-这一篇全了解(Redission实现分布式锁完美方案)_Coder-CSDN博客_redission分布式锁、
redlock:https://mp.weixin.qq.com/s/JLEzNqQsx-Lec03eAsXFOQ
Distributed locks with Redis – Redis
zookeeper实现分布式锁:https://blog.csdn.net/sunfeizhi/article/details/51926396



