Redis分布式锁详解
分布式锁是在分布式系统中用于控制多个进程/服务对共享资源进行互斥访问的一种机制。在单机系统中,我们可以使用语言提供的锁机制(如Java的synchronized或ReentrantLock),但在分布式环境下,这些本地锁无法跨进程/机器工作,因此需要分布式锁。
什么是分布式锁
分布式锁是在分布式系统中用于控制多个进程/服务对共享资源进行互斥访问的一种机制。在单机系统中,我们可以使用语言提供的锁机制(如Java的synchronized或ReentrantLock),但在分布式环境下,这些本地锁无法跨进程/机器工作,因此需要分布式锁。
Redis实现分布式锁的基本原理
Redis实现分布式锁主要依赖其单线程执行命令的特性,以及SETNX(SET if Not eXists)命令:
-
SETNX命令:当key不存在时设置值,返回1;key已存在则不做任何操作,返回0
-
DEL命令:释放锁时删除key
基本流程:
加锁:SETNX lock_key unique_value
解锁:DEL lock_key
Redis分布式锁的正确实现方式
基本实现方案
# 加锁
SET resource_name my_random_value NX PX 30000
# 解锁(使用Lua脚本保证原子性)
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
关键点:
-
使用
NX参数实现SETNX的效果 -
使用
PX设置过期时间,防止客户端崩溃导致锁无法释放 -
解锁时使用Lua脚本保证原子性,并验证value值,防止误删其他客户端的锁
Redlock算法
Redis作者提出的更安全的分布式锁算法,适用于Redis集群环境:
-
获取当前时间(毫秒)
-
依次尝试从N个独立的Redis实例获取锁(使用相同的key和随机value)
-
计算获取锁花费的时间(当前时间减去步骤1的时间),当且仅当从大多数(N/2+1)实例获取锁成功,并且总耗时小于锁的过期时间,才认为加锁成功
-
如果加锁成功,锁的有效时间 = 初始有效时间 - 获取锁花费的时间
-
如果加锁失败,向所有Redis实例发起释放锁请求
Redis分布式锁的最佳实践
-
设置合理的过期时间:不宜过长(影响系统可用性)或过短(可能导致任务未完成锁已释放)
-
使用唯一标识作为value:通常使用UUID或客户端ID+线程ID,确保只有锁的持有者才能释放锁
-
避免长时间持有锁:业务逻辑应尽量快速执行,减少锁的持有时间
-
考虑锁的可重入性:如果需要可重入锁,需要在客户端实现计数逻辑
-
实现锁续约机制:对于执行时间不确定的任务,可以定期延长锁的过期时间(看门狗机制)
Redis分布式锁的优缺点
优点:
-
实现相对简单
-
性能高
-
社区支持完善,有多种客户端实现
缺点:
-
单Redis实例有单点故障风险
-
主从切换可能导致锁失效(Redlock可以缓解)
-
需要处理网络分区等复杂场景
与其他分布式锁方案的对比
-
Zookeeper分布式锁:
-
基于临时顺序节点实现
-
更严格的强一致性保证
-
性能通常低于Redis
-
-
数据库分布式锁:
-
基于唯一约束或乐观锁实现
-
性能较差,不推荐高并发场景使用
-
常见问题及解决方案
-
锁过期但任务未完成:
-
实现锁续约机制
-
合理设置超时时间
-
-
客户端长时间阻塞导致锁失效:
-
使用带超时的获取锁操作
-
实现锁获取失败的回退策略
-
-
Redis主从切换导致锁丢失:
-
使用Redlock算法
-
考虑使用Redis集群模式
-
Redis分布式锁在大多数场景下是一个简单高效的解决方案,但在对一致性要求极高的场景下,可能需要考虑Zookeeper等替代方案。
具体实例:
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class RedisUtils {
private static RedissonClient REDISSON_CLIENT;
public RedisUtils(@Autowired RedissonClient redissonClient) {
REDISSON_CLIENT = redissonClient;
}
/**
* 尝试获取锁
*
* @param key 锁key
* @param waitTime 最多等待时间,0:表示不等待,只尝试加锁一次
* @param leaseTime 上锁后自动释放锁时间,-1:表示不自动释放锁
* @return 获取锁成功返回true 失败返回false
*/
public static boolean tryLock(String key, long waitTime, long leaseTime) {
RLock lock = REDISSON_CLIENT.getLock(key);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
public static void unlock(String key) {
RLock lock = REDISSON_CLIENT.getLock(key);
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
} else {
log.warn("redis锁已超时释放");
}
}
}
使用
String lockKey = CommonConstant.INIT_QW_USER_LOCK_KEY;
// 只加一次锁,获取不到证明已经有一个任务获取到了
if (RedisUtils.tryLock(lockKey, 0, 60 * 25)) {
try {
log.info("分布式锁加锁成功, lockKey = {}", lockKey);
qwUserService.sync(null);
} finally {
RedisUtils.unlock(lockKey);
log.info("分布式锁释放成功, lockKey = {}", lockKey);
}
} else {
log.info("未获取到分布式锁");
}

更多推荐


所有评论(0)