java可以用redisson集成,python可以自己写一个。
以下是一个具备集群,主从哨兵,以及具备故障转移和容错机制的分布式锁 Python 实现代码:
import redis
import time
import random
from threading import Lock, Thread
from redis.sentinel import Sentinel
# Redis 集群配置
redis_cluster = [
{"host": "localhost", "port": 6379},
{"host": "localhost", "port": 6380},
{"host": "localhost", "port": 6381}
]
# 主从哨兵配置
sentinel = sentinel.Sentinel(redis_cluster, socket_timeout=0.1)
master = sentinel.master_for('mymaster', password=None)
slave = sentinel.slave_for('mymaster', password=None)
# 分布式锁相关配置
lock_timeout = 30
watchdog_timeout = 25
half_response_timeout = 10
# 连接 Redis 集群
redis_client = redis.Redis(connection_pool=master)
class DistributedLock:
def __init__(self, lock_name):
self.lock_name = lock_name
self.identifier = None
self.renewal_thread = None
self.heartbeat_thread = None
self.timeout_event = None
self.failed_nodes = set() # 记录故障节点
def acquire(self):
with concurrent_lock:
# 获取当前时间戳
current_time = time.time()
# 生成锁的唯一标识(可以使用 UUID 或其他方式生成)
self.identifier = str(random.randint(1000, 9999))
# 尝试获取锁
lock_acquired = redis_client.set(self.lock_name, self.identifier, nx=True, ex=lock_timeout)
# 如果成功获取锁,启动锁续约和心跳线程,并设置超时事件
if lock_acquired:
print(f"Distributed lock acquired: {self.lock_name}")
self.renewal_thread = Thread(target=self.renew_lock, args=(current_time,))
self.renewal_thread.start()
self.heartbeat_thread = Thread(target=self.send_heartbeat, args=(current_time,))
self.heartbeat_thread.start()
self.timeout_event = Thread(target=self.timeout_check, args=(current_time,))
self.timeout_event.start()
return self.identifier
# 获取锁失败,检查是否需要故障转移
elif self.check_failover(current_time):
return self.acquire_failover()
# 获取锁失败,等待一段时间后重试
else:
time.sleep(0.1)
def release(self, identifier):
with concurrent_lock:
# 使用 Lua 脚本删除锁,确保原子性操作
lua_script = f"""
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
result = redis_client.eval(lua_script, 1, self.lock_name, identifier)
# 如果删除成功,停止锁续约和心跳线程,并通知其他等待锁的线程
if result == 1:
print(f"Distributed lock released: {self.lock_name}")
if self.renewal_thread and self.renewal_thread.is_alive():
self.renewal_thread.join()
if self.heartbeat_thread and self.heartbeat_thread.is_alive():
self.heartbeat_thread.join()
self.timeout_event.set()
return True
# 删除失败,可能是锁已过期或被其他节点获取
else:
return False
def renew_lock(self, current_time):
while True:
# 计算锁的剩余时间
remaining_time = redis_client.ttl(self.lock_name)
# 如果锁即将过期,进行续约
if remaining_time <= lock_renew_interval:
redis_client.expire(self.lock_name, lock_timeout)
# 等待一段时间后继续检查锁的状态
time.sleep(lock_renew_interval)
def send_heartbeat(self, current_time):
while True:
# 发送心跳信息
redis_client.set(f"heartbeat:{self.lock_name}", current_time)
# 等待一段时间后继续发送心跳
time.sleep(heartbeat_interval)
def timeout_check(self, current_time):
while True:
# 检查锁是否超时
if time.time() - current_time > lock_timeout:
print("Lock timeout. Releasing lock.")
self.release(self.identifier)
return
# 等待一段时间后继续检查
time.sleep(1)
def check_failover(self, current_time):
# 获取故障转移锁
failover_lock = redis_client.get(f"failover:{self.lock_name}")
# 如果故障转移锁存在且未过期,进行故障转移
if failover_lock and time.time() - float(failover_lock) <= failover_timeout:
print(f"Failover detected for {self.lock_name}. Releasing lock and acquiring failover lock.")
self.release(self.identifier)
return True
return False
def acquire_failover(self):
# 等待故障转移锁过期
time.sleep(failover_timeout)
# 遍历从节点,尝试获取锁
for node in slave.nodes:
try:
redis_client = redis.Redis(connection_pool=node)
identifier = self.acquire()
if identifier:
return identifier
except redis.exceptions.ConnectionError:
# 连接从节点失败,记录故障节点
self.failed_nodes.add(node)
# 如果所有从节点都连接失败,重新尝试获取主节点的锁
if self.failed_nodes:
print("All slaves are down. Reconnecting to master.")
redis_client = redis.Redis(connection_pool=master)
identifier = self.acquire()
if identifier:
return identifier
# 获取锁失败
return None
# 示例用法
lock_name = "my_distributed_lock"
# 创建分布式锁对象
lock = DistributedLock(lock_name)
# 获取分布式锁
identifier = lock.acquire()
if identifier:
# 执行需要加锁的操作
print("Lock acquired. Performing critical operation...")
time.sleep(5) # 模拟执行操作
# 释放分布式锁
lock.release(identifier)
else:
print("Failed to acquire lock.")
在上述代码中,添加了以下故障转移和容错机制:
1. **故障转移锁**:引入了故障转移锁,用于检测主节点的故障。当主节点不可用时,通过获取故障转移锁来进行故障转移。
2. **从节点故障处理**:在获取锁的过程中,如果从节点连接失败,将其记录到故障节点集合中。当所有从节点都连接失败时,重新连接主节点尝试获取锁。
3. **超时检查**:添加了超时检查线程,定期检查锁是否超时。如果锁超时,将自动释放锁。
4. **心跳机制**:通过发送心跳信息来保持锁的活性,防止锁被意外释放。
通过以上优化,可以提高分布式锁的可靠性和容错性,在节点故障或网络问题发生时能够进行自动故障转移,并确保锁的正确释放。
分布式锁代码的逻辑和参数配置的详细解释:
1. **参数配置**:
- `redis_cluster`:Redis 集群的节点信息。
- `sentinel`:Redis 哨兵对象,用于监控主从节点的状态。
- `master`:主节点的 Redis 连接。
- `slave`:从节点的 Redis 连接。
- `lock_timeout`:锁的超时时间(秒)。
- `watchdog_timeout`:看门狗的超时时间(秒),应小于锁的超时时间。
- `half_response_timeout`:半数响应的超时时间(秒)。
- `concurrent_lock`:并发控制锁,用于保证线程安全。
2. **DistributedLock 类**:
- `__init__` 方法:初始化分布式锁对象,设置锁的名称和相关属性。
- `acquire` 方法:获取分布式锁。
- 获取当前时间戳。
- 生成锁的唯一标识。
- 尝试获取锁。
- 如果获取成功,启动锁续约和心跳线程,并设置超时事件。
- 如果获取失败,检查是否需要故障转移,然后重试获取锁。
- `release` 方法:释放分布式锁。
- 使用 Lua 脚本删除锁,确保原子性操作。
- 如果删除成功,停止锁续约和心跳线程,并设置超时事件。
- `renew_lock` 方法:续租锁。
- 计算锁的剩余时间。
- 如果锁即将过期,续租锁。
- `send_heartbeat` 方法:发送心跳。
- 发送心跳信息到 Redis。
- `check_failover` 方法:检查是否需要故障转移。
- 获取故障转移锁。
- 如果故障转移锁存在且未过期,释放当前锁并获取故障转移锁。
- `acquire_failover` 方法:获取故障转移锁。
- 等待故障转移锁过期。
- 遍历从节点,尝试获取锁。
- 如果所有从节点都连接失败,重新连接主节点尝试获取锁。
- `timeout_check` 方法:检查锁是否超时。
- 如果锁超时,释放锁。
3. **代码逻辑**:
- 在 `acquire` 方法中,首先尝试获取锁。如果获取成功,启动锁续约和心跳线程,并设置超时事件。如果获取失败,检查是否需要故障转移,然后重试获取锁。
- 在 `release` 方法中,使用 Lua 脚本删除锁,并停止锁续约和心跳线程。
- 在 `renew_lock` 方法中,定期检查锁的剩余时间,如果锁即将过期,续租锁。
- 在 `send_heartbeat` 方法中,定期发送心跳信息到 Redis,保持锁的活性。
- 在 `check_failover` 方法中,检查是否需要故障转移。如果需要,释放当前锁并获取故障转移锁。
- 在 `acquire_failover` 方法中,等待故障转移锁过期,然后遍历从节点尝试获取锁。如果所有从节点都连接失败,重新连接主节点尝试获取锁。
- 在 `timeout_check` 方法中,定期检查锁是否超时。如果锁超时,释放锁。