分布式锁

2019/04/22 分布式

分布式锁

分布式锁解决的是多进程的并发问题。

确保分布式锁可用,至少要确保锁的实现满足以下四个条件:

  • 互斥性:在任意时刻,只有一个客户端能持有锁;
  • 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁(锁可重入/设置超时时间实现);
  • 具有容错性:只要大部分节点正常运行,客户端就可用加锁和解锁;
  • 加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

一般的几种实现方式:数据库自己的排他锁,数据库乐观锁,基于Redis等缓存的分布式锁,基于ZK的分布式锁。

1.ZK

获取分布式锁的思路(临时顺序节点+watcher):

  • ZK集群下创建一个持久节点locker,获取分布式锁的时候在locker节点下创建临时顺序节点,释放锁的时候删除该临时节点;
  • 客户端调用createNode()在locker下创建临时顺序节点,然后调用getChildren(“locker”)来获取locker下面的所有子节点;
  • 客户端获取到所有的子节点path之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁;
  • 如果发现自己创建的节点并不是最小的,说明没有获取到锁,此时客户端需要找到最小的节点,对其调用exist()方法,同时对其注册事件监听器。
  • 等到这个最小的节点释放锁,节点被删除,客户端的Watcher会收到相应通知,次吃再判断自己创建的节点是不是最小的,如果是则获取到锁,如果不是再重复上述步骤。

2.Redis

  • 加锁:

    jedis.set(String key, String value, String nxxx, String expx, int time)

    • key:用key当锁,key是唯一的;
    • value:传入requestId,这样就可用知道这把锁是哪个请求加的了,在解锁的时候有依据。可用使用UUID.randomUUID().toString()来生成。
    • nxxx:传入nx,当key不存在时进行set操作,如果已经存在,不做任何操作;
    • expx:传图px,给这个key加过期时间;
    • time:代表key的过期时间。
  • 解锁:

     String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
     Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    

    使用Lua脚本,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁。可以保证判断和删除两步操作的原子性。

Jedis客户端加锁是不可重入的,所以设置超时时间防止死锁发生。

Redisson客户端可以实现可重入锁。

参考:

zookeeper分布式锁原理

Redis分布式锁的正确实现方式

Search

    Table of Contents