我们知道,在Java单进程中,多线程的环境下,如果我们要操作一个共享变量,需要使用synchronized或者是JUC同步工具类才能保证线程安全。那么,多进程环境下,我们要怎样保证线程安全? 我们知道,synchronized或者是JUC同步工具类只能在同一进程中保证线程安全,他们的影响范围没办法超出本Java进程。但是随着分布式成为主流,多进程共享数据的情况越来越常见。 如上图,两个进程同时对存储在MySQL、Redis或者是zookeeper中的共享数据进行读写,即有可能出现线程安全问题,这种情况下,我们就需要一个可以在分布式环境下也可以使用的锁,来保证线程安全,这即是分布式锁。 因为要在分布式环境下生效,因此实现分布式锁使用的组件也必须是每个进程都可以连接到的,目前比较常见的是使用Redis和Zookeeper来实现。 Redis中,我们使用String数据结构来实现分布式锁。 Redis中,set的语法如下: 上述参数中,我们可以将key作为锁标识,然后设置NX参数。如果key写入成功,表示当前Redis中不存在这个key,可以加锁;如果key写入失败,表示当前Redis中已存在这个key,已经有其它线程获取到锁了。命令如下: SET lockKey requestId NX 上述命令中,可以实现加锁,但是如果在加锁后,应用挂了,或者出现了其它问题,导致没有及时解锁,就有可能出现死锁,因此,需要再给加上一个过期时间,让锁可以自动消失。命令如下: SET lockKey requestId EX seconds NX 释放锁的时候,我们不能直接使用del命令去删除Redis键值,否则会出现A获取的锁,B也可以释放的情况。因此,我们在释放锁的时候,需要判断当前锁的请求ID是否是加锁时是否一致,如果一致,才能释放锁。 if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end 既然看到这里了,感觉有所收获的朋友,不妨来个大大的点赞吧~~~前言
为什么需要分布式锁?
分布式锁可以使用什么组件实现?
Redis分布式锁实现逻辑
加锁
SET key value [EX seconds] [PX milliseconds] [NX|XX]
lockKey为锁标识,最好带上使用共享变量唯一标识,可以使用订单编号、用户编号等。 requestId为本次锁请求编号,释放锁时使用。释放锁
由于没有现成的命令可以实现上述的释放锁的逻辑,所以我们需要使用Redis的script来实现,script脚本如下:
KEYS[1]为锁标识,即加锁时的lockKey
ARGV[1]为锁请求编号,即加锁时的requestIdRedis分布式锁代码
public class RedisDistributedLock implements Lock {
// 锁键值
private String lockKey;
// 锁请求编号
private String requestId;
// redis集群客户端
private JedisCluster jedisCluster;
public RedisDistributedLock(String lockKey, JedisCluster jedisCluster){
this.lockKey = lockKey;
// 使用UUID作为锁唯一标识
requestId = UUID.randomUUID().toString();
this.jedisCluster = jedisCluster;
}
/**
* 尝试获取锁
*/
@Override
public boolean tryLock() {
// 获取锁
String result = jedisCluster.set(lockKey, requestId, "NX", "EX", 2);
return StringUtils.isNotBlank(result) && LOCK_SUCCESS.equals(result);
}
/**
* 释放锁
*/
@Override
public void unlock() {
// 若redis中存在lockKey,则删除lockKey,否则返回0
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedisCluster.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(requestId));
// 成功
if (result != null && "OK".equals(result.toString())) {
logger.debug("释放锁成功,lockKey:{}, requestId:{}", lockKey, requestId);
} else {// 失败
logger.debug("释放锁失败,lockKey:{}, requestId:{}", lockKey, requestId);
}
}
}
复制代码
后言
原文链接:https://juejin.cn/post/7159556729439518727