1、原理
说一下setnx命令:setnx hello redis。「SET if Not exists」的缩写,如果键存在则设置失败返回0,如果键不存在则设置成功返回1。由于Redis是单线程的,如果有多个客户端同时执行setnx key value 根据sexnx的特性只有一个客户端能设置成功。senx可以作为分布式锁的一种实现方案。
2、注意点
那么使用setnx命令要注意哪些问题呢?
1、这个命令一定要设置一个过期时间(expire key seconds),如果没有过期时间,那么竞争这把锁的线程永远得不到执行
2、setnx和exprie这是2个操作并不是一个原子操作。如果执行完setnx命令之后宕机了,那么这把锁又没有设置超时时间,怎么办?
3、这个过期时间到底设置多久比较合适?
4、过期时间到了,锁也释放了,但是业务代码还没执行完,怎么办?
5、你也许会说在执行完业务代码后主动删除key然后释放锁。那么能确保释放掉的一定是自己持有的锁吗,试想一下,如果过期时间到了,锁就释放了,这时候业务代码还没执行完,等业务代码执行完在主动删除key释放锁,这个时候释放的还确定一定是自己的锁。所以用setnx命令控制好一把锁不是那么容易,下面给出代码例子
3、代码示例
/**
* 获得锁
*
* @param lockName 锁的名词
* @param acquireTimeout 获得锁的超时时间
* @param lockTimeout 锁本身的过期时间
* @return
*/
public String acquireLock(String lockName, long acquireTimeout, long lockTimeout) {
// 保证释放锁的时候是同一个持有锁的人
String identifier = UUID.randomUUID().toString();
String lockKey = "lock:" + lockName;
int lockExpire = (int) (lockTimeout / 1000);
Jedis jedis = null;
try {
jedis = JedisConnectionUtils.getJedis();
long end = System.currentTimeMillis() + acquireTimeout;
// 获取锁的限定时间
while (System.currentTimeMillis() < end) {
// 设置值成功,获得锁
if (jedis.setnx(lockKey, identifier) == 1) {
// 获取锁的时候设置一个合理的过期时间,那么即使服务器宕机了,也能保证锁被正确释放。
jedis.expire(lockKey, lockExpire); //设置超时时间
return identifier; //获得锁成功
}
// 否则判断key的有效期
// TTL 命令以秒为单位返回 key 的剩余过期时间。
// 当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,
// 返回 -1 。 否则,以毫秒为单位,返回 key 的剩余生存时间。
if (jedis.ttl(lockKey) == -1) {
jedis.expire(lockKey, lockExpire); //设置超时时间
}
try {
// 等待片刻后进行获取锁的重试
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
jedis.close();
}
return null;
}}
/**
* 释放锁
* @param lockName
* @param identifier
* @return
*/
public boolean releaseLock(String lockName, String identifier) {
System.out.println(lockName + "开始释放锁:" + identifier);
String lockKey = "lock:" + lockName;
Jedis jedis = null;
boolean isRelease = false;
try {
jedis = JedisConnectionUtils.getJedis();
while (true) {
// 在Redis中,使用watch命令实现乐观锁(watch key):
// watch命令会监视给定的key,当exec时,如果监视的key从调用watch后发生过变
// 化,则事务会失败,
jedis.watch(lockKey);
// 判断是否为同一把锁
if (identifier.equals(jedis.get(lockKey))) {
// 开启事务
Transaction transaction = jedis.multi();
transaction.del(lockKey);
// 提交事务,如果lockKey被改了则返回null
if (transaction.exec().isEmpty()) {
continue;
}
isRelease = true;
}
jedis.unwatch();
break;
}
} finally {
releaseLock(lockKey,identifier);
jedis.close();
}
return isRelease;
}
复制代码
4、后续
这个代码量其实还是挺多的,还有更好的实现分布式锁的方式,Redisson实现分布式锁,那么这些细节就不用我们考虑了,代码更加简洁清爽,后面有空我们再探讨,Redisson实现分布式锁