Redis实现分布式锁

·  阅读 522

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实现分布式锁

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改