基于 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 种类型
- 持久节点 (PERSISTENT)
- 持久节点顺序节点(PERSISTENT_SEQUENTIAL)
- 临时节点(EPHEMERAL)
- 临时顺序节点(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()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界