基于 Redis 和 ZooKeeper 的分布式锁

Redis 锁

SET key value EX 120 NX
  
该命令是原子操作,表示只有在 key 不存在的情况下,才会赋值成功,并且 120 秒后会自动删除,这样就实现了带超时时间的互斥锁功能,获得锁的程序删除 key 就是释放了锁,如果程序出错退出,达到超时时间后也会保证锁能被释放
  
这种方法在比较极端的情况下可能失效
  程序 A 获取锁
  master 将数据同步到 slave 之前 master 宕机了
  slave 被选为 master 且没有 A 的锁
  程序 B 获取锁成功,这时候程序 A 还在正常运行,导致两个程序同时获得锁
  

Redisson

https://redisson.org/
Redis Java Client with features of In-Memory Data Grid
Redisson 是基于 redis 的一个 Java 程序,提供了非常丰富的功能,其中就包括了锁
  
Redisson 提供了多种锁,具体可参考官网
https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers
  
这里以基本的 Lock 为例子

RLock lock = redisson.getLock("myLock");

// traditional lock method
lock.lock();

// or acquire lock and automatically unlock it after 10 seconds
lock.lock(10, TimeUnit.SECONDS);

// or wait for lock aquisition up to 100 seconds 
// and automatically unlock it after 10 seconds
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {
     ...
   } finally {
       lock.unlock();
   }
}


ZooKeeper 锁

ZooKeeper 的节点叫 ZNode,共有 4 种类型

  1. 持久节点 (PERSISTENT)
  2. 持久节点顺序节点(PERSISTENT_SEQUENTIAL)
  3. 临时节点(EPHEMERAL)
  4. 临时顺序节点(EPHEMERAL_SEQUENTIAL)

通过创建临时节点获取锁
  只有节点不存在时才能创建成功
  临时节点只有在 client 和 server 保持连接的时候才存在,session 断开后会被 server 删除
  阻塞模式下,可以设置获取锁的超时时间,没能成功获取会抛异常
  似乎没有可以设置成功获取锁后多长时间内会自动释放
  删除节点就是释放锁
  存在程序还在跑但 session 断开的异常情况要处理
  
通过创建临时顺序节点获取锁
  同一个节点下的临时顺序节点,会按照创建的先后顺序编号,可以实现排队机制
  程序 A B C 先后在同一个节点下创建临时顺序节点
  程序在成功创建后会检查所有临时顺序节点,判断自己是不是编号最小的
  A 程序发现自己是最小的于是 A 拿到锁
  B 程序发现 A 的编号在自己之前,于是等待并通过 watcher 监听 A 创建的节点
  C 程序发现 B 的编号在自己之前,于是等待并通过 watcher 监听 B 创建的节点
  A 程序删除节点释放锁
  B 程序通过 watcher 得知,并再次检查自己是否编号最小,如果是就获取锁
  B 程序删除节点释放锁
  C 程序通过 watcher 得知,并再次检查自己是否编号最小,如果是就获取锁
  
ZooKeeper 的性能应该会比 Redis 差些

# coding=utf-8
from kazoo.client import KazooClient
from kazoo.client import KazooState

zk = KazooClient(host="localhost:2181")
zk.start()


def my_listener(state):
    if state == KazooState.LOST:
        print("KazooState.LOST")
    elif state == KazooState.SUSPENDED:
        print("KazooState.SUSPENDED")
    else:
        print("KazooState.CONNECTED")


zk.add_listener(my_listener)


# 通过直接创建节点获取锁
try:
    zk.create('/test/lock/resource', b'app name', makepath=True, ephemeral=True, sequence=False)
except:
    print("failed to get the lock")
    exit()

zk.delete('/test/lock/resource')


# 通过三方包的功能获取锁
lock = zk.Lock('/test/lock/resource', b'app name')
if lock.acquire(blocking=True, timeout=None, ephemeral=True):
    # acquire 函数会使用 sequence=True 在 /test/lock/resource 节点下用包含 uuid 的名字创建子节点,这样节点必然创建成功
    # 如果子节点序号不是最小且 blocking=True 则注册 watcher 然后等待,否则序号最小则返回 True,不是最小或异常则返回 False
    # 如果超过了 timeout 时间还没获取则返回 False
    pass
else:
    print("failed to get the lock")
    exit()

# release 函数会 delete 节点
lock.release()




posted @ 2020-04-02 01:56  moon~light  阅读(280)  评论(0编辑  收藏  举报