0%

分布式锁

分布式锁

分布式锁的实现一般有redis或zookeeper

redis分布式锁

利用redis的setnx命令,只有在key不存在的情况下,才能set成功

1
2
3
4
5
6
7
8
//加锁  返回1,说明key原本不存在,线程得到了锁;返回0说明key已经存在了,获取锁失败
jedis.setnx(key,value)

// 释放锁,将key删掉,这样setnx就可以获得锁了
jedis.del(key)

// 锁超时 当超过该时间,则会自动释放锁
jedis.expire(key, 30)

但是sernx+expire是两个操作,不是原子性的,可能会出现setnx刚执行成功,还没来得及执行expire命令,服务挂掉了,这样就会导致该key没有设置过期时间,别的服务无法获取到锁

1
2
3
4
// 可以使用set方法  
//nxxx参数是NX|XX,如果为NX,则表示只有不存在该key的时候才会set;XX表示只有存在该key的时候才会set
// expx参数是EX|PX EX表示过期时间单位是秒,PX表示过期时间单位是毫秒
String set(String key, String value, String nxxx, String expx, long time);

但是在可能会出现另一种情况,线程A获取到锁之后,开始执行业务,此时key过期删除了,线程B同样获取到锁,之后线程A执行完之后就会删除锁,但是删除的是线程B的锁,所以在加锁的时候可以使用当前线程ID作为value,删除之前进行验证key对应的value是不是自己线程的ID

1
2
3
4
// 为保证操作原子性,使用lua脚本
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));

zookeeper分布式锁

利用zookeeper的顺序临时节点,在zookeeper中创建一个持久节点locks

  • 三个系统A/B/C都去访问locks节点,访问的时候会创建带顺序号的临时/短暂节点,(例如,A创建了id_0000节点,B创建了id_0001节点,C创建了id_0002节点)
  • 拿到/locks节点下的所有子节点(id_0000,id_0001,id_0002),getChildren方法,判断自己创建的是不是最小的节点
  • 如果是,则拿到锁,执行完操作之后,把创建的节点删掉,即为释放锁
  • 如果不是,找到比自己小1的节点,exists方法,并进行监听,发现比自己小1的节点删掉之后,发现自己已经是最小的节点了,拿到锁
  • 如果在操作过程中,应用服务挂掉了,那么由于连接断开,此时的顺序临时节点也会自动删除