分布式锁用于解决分布式系统中多个进程或线程同时访问共享资源时的数据一致性问题。Redis 是实现分布式锁的一个高效工具,因为其性能高、支持单线程操作以及丰富的原子操作。以下是 Redis 实现分布式锁的完整教程。
一、分布式锁的基本要求
- 互斥性:只有一个客户端能获取锁。
- 不会死锁:即使客户端崩溃或网络分区,锁能最终被释放。
- 容错性:分布式锁能在少量 Redis 节点故障时正常运行。
- 解锁操作只会由加锁的客户端执行:避免误解锁。
二、使用 Redis 实现分布式锁
1. 加锁(SET 命令实现原子操作)
Redis 提供的 SET
命令可以用来实现分布式锁:
SET lock_key value NX EX 10
lock_key
:表示锁的键。value
:唯一标识符(如 UUID,确保解锁时对应)。NX
:只在键不存在时设置,保证互斥性。EX 10
:锁的过期时间为 10 秒,防止死锁。
示例代码: 使用 Python 的 redis-py
库:
import redis
import uuid
# 创建 Redis 连接
redis_client = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)
# 唯一标识锁的值
lock_value = str(uuid.uuid4())
# 尝试加锁
lock_key = "resource_lock"
lock = redis_client.set(lock_key, lock_value, nx=True, ex=10) # 过期时间 10 秒
if lock:
print("锁获取成功!")
else:
print("锁已被占用!")
2. 解锁(确保安全解锁)
解锁时需保证只有持有锁的客户端才能释放锁,避免误解锁。
解锁的逻辑:
- 检查
lock_key
的值是否是自己设置的value
。 - 如果是,执行
DEL
命令删除锁。
使用 Lua 脚本实现原子解锁:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
Python 实现:
# Lua 脚本
unlock_script = """
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
"""
# 执行解锁
result = redis_client.eval(unlock_script, 1, lock_key, lock_value)
if result == 1:
print("解锁成功!")
else:
print("解锁失败!")
3. 锁续命(防止长时间任务超时)
如果任务执行时间可能超过锁的有效期,可以通过续命机制延长锁的过期时间:
# 延长锁的过期时间(每隔 5 秒执行一次)
redis_client.expire(lock_key, 10)
使用守护线程自动续命:
import threading
import time
def keep_lock_alive(redis_client, lock_key, lock_value, expire_time):
while True:
time.sleep(expire_time // 2)
if redis_client.get(lock_key) == lock_value:
redis_client.expire(lock_key, expire_time)
else:
break
# 启动守护线程
thread = threading.Thread(target=keep_lock_alive, args=(redis_client, lock_key, lock_value, 10))
thread.daemon = True
thread.start()
三、使用 Redisson 实现分布式锁(推荐)
Redisson 是一个功能强大的 Redis 客户端,内置了分布式锁功能。
Java 示例:
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
public class RedisLockExample {
public static void main(String[] args) throws InterruptedException {
// 配置 Redis
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取锁
RLock lock = redisson.getLock("resource_lock");
// 加锁
if (lock.tryLock(10, 5, TimeUnit.SECONDS)) { // 等待时间 10s,锁过期时间 5s
try {
System.out.println("锁获取成功!");
// 执行任务
} finally {
lock.unlock(); // 释放锁
}
} else {
System.out.println("获取锁失败!");
}
redisson.shutdown();
}
}
四、分布式锁高级实现(RedLock)
RedLock 是由 Redis 官方推荐的一种分布式锁算法,适用于多 Redis 节点的场景。它的核心思想是通过在多个独立的 Redis 实例上尝试加锁,增加锁的容错能力。
步骤:
- 同时在多个 Redis 节点上尝试加锁(NX + EX)。
- 至少在一半以上的节点上成功加锁才算锁获取成功。
- 解锁时逐个释放所有节点的锁。
五、常见问题及优化
- 锁过期时间设置过短或过长
- 设置过短:任务尚未完成锁就已过期。
- 设置过长:锁占用时间过久,影响其他客户端。
- 误解锁问题
- 某客户端持有锁时崩溃,锁被其他客户端重用并释放。
- 网络延迟或分区问题
- 锁未同步到所有节点,导致分布式一致性问题。
六、总结
Redis 提供了高效的分布式锁实现,可以满足大部分分布式系统对资源同步访问的需求:
- 对于单点 Redis,使用
SET NX EX
和 Lua 脚本可以实现简单的分布式锁。 - 对于复杂场景,推荐使用 Redisson 或 RedLock 来增强锁的稳定性和容错性。
在实际使用中,应根据业务需求权衡锁的粒度、性能和安全性。
发布者:myrgd,转载请注明出处:https://www.object-c.cn/4446