Java采用Redis相关命令实现分布式锁

Java本地锁(synchronized或J.U.C.Lock)只能解决当前jvm下的并发问题,如果是集群环境下或者一个机器跑多个jvm实例且相互间有交互或重叠时,此时需要一个“中央锁”来进行控制。

以下实现来自http://doc.redisfans.com/string/set.html相关描述:

命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。

客户端执行以上的命令:

如果服务器返回 OK ,那么这个客户端获得锁。
如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
设置的过期时间到达之后,锁将自动释放。

可以通过以下修改,让这个锁实现更健壮:

不使用固定的字符串作为键的值,而是设置一个不可猜测(non-guessable)的长随机字符串,作为口令串(token)。
不使用 DEL 命令来释放锁,而是发送一个 Lua 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。
这两个改动可以防止持有过期锁的客户端误删现有锁的情况出现。

以下是一个简单的解锁脚本示例:

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end
这个脚本可以通过 EVAL ...script... 1 resource-name token-value 命令来调用。

RedisLock.java

import java.util.UUID;

import redis.clients.jedis.Jedis;

/**
 * 
 * A class that acts as a distributed lock using the method
 * {@code Jedis.set(String key, String value, String nxxx, String expx, long time)}.
 * <h3>Usage Examples</h3>
 * <p>Here is an example:
 * <pre>{@code //the time value 10 means the method invocation in try block takes no more than 10 seconds
 * RedisLock lock = RedisLock.getLock("myLock",10);
 * if(lock!=null){
 *  try{
 *    //doSomething
 *  } finally{
 *    lock.unlock();
 *  }
 * }}
 */
public class RedisLock {

	/**
	 * A value that identifies the current lock
	 */
	private final String value;
	
	/**
	 * A key represents the lock
	 */
	private final String key;
	
	/**
	 * Constructs a {@code RedisLock} used for release the lock via {@link #unlock()}
	 * @param key the key
	 * @param value the value, which identifies the current lock
	 */
	private RedisLock(String key, String value){
		this.key = key;
		this.value = value;
	}
	
	/**
	 * Try to hold the lock represented by the specified key and
	 * set a timeout(in seconds) on the lock (that is, the specified key) 
	 * if the lock is successfully obtained to avoid the lock is never released
	 * when fails to invoke {@link #unlock()}(which means the method may be
	 * throws an uncaught exception, but not the return value {@code false}).
	 * 
	 * @param key the key used as lock object
	 * @param seconds expire time in seconds
	 * @return {@code RedisLock} if the lock is successfully obtained, else {@code null}
	 * @throws NullPointerException if the specified key is null
	 * @throws IllegalArgumentException if the key is empty, 
	 * or the time value is less then or equal to zero
	 */
	public static RedisLock getLock(String key, int seconds){
		check(key, seconds);
		String value = UUID.randomUUID().toString();
		Jedis jedis = null;
		try{
			jedis = RedisPool.get();
			//Redis 2.6.12 版本及以上 可以直接通过set命令实现
			String status = jedis.set(key, value, "NX", "EX", seconds);
			if("OK".equalsIgnoreCase(status)){
				return new RedisLock(key, value);
			}
			//Redis 2.6.12 版本以前 可通过脚本实现
//			String[] param = {key, value, String.valueOf(seconds)};
//			String script = "local ok = redis.call('setnx', KEYS[1], ARGV[1]) if ok == 1 then redis.call('expire', KEYS[1], ARGV[2]) end return ok";
//			Object status = jedis.eval(script, 1, param);
//			if(((Number)status).intValue()==1){
//				return new RedisLock(key, value);
//			}
		} finally{
			RedisPool.close(jedis);
		}
		
		return null;
	}
	
	// 之所以不重载为getLock(String key, long millis)是因为int与long不好区分,想明确调用的话必须加后缀L
	// 由于网络来回传输比较耗时,毫秒的精度是否有必要?
	
//	/**
//	 * Try to hold the lock represented by the specified key and
//	 * set a timeout(in milliseconds) on the lock (that is, the specified key) 
//	 * if the lock is successfully obtained to avoid the lock is never released
//	 * when fails to invoke {@link #unlock()}(which means the method may be 
//	 * throws an uncaught exception, but not the return value {@code false}).
//	 * 
//	 * @param key the key used as lock object
//	 * @param millis expire time in milliseconds
//	 * @return {@code RedisLock} If the lock is successfully obtained, else {@code null}
//	 * @throws NullPointerException if the specified key is null
//	 * @throws IllegalArgumentException if the key is empty, 
//	 * or the time value is less then or equal to zero
//	 */
//	public static RedisLock getLockMillis(String key, long millis){
//		check(key, millis);
//		String value = UUID.randomUUID().toString();
//		Jedis jedis = null;
//		try{
//			jedis = RedisPool.get();
//			//Redis 2.6.12 版本及以上 可以直接通过set命令实现
//			String status = jedis.set(key, value, "NX", "PX", millis);
//			if("OK".equalsIgnoreCase(status)){
//				return new RedisLock(key, value);
//			}
//			//Redis 2.6.12 版本以前 可通过脚本实现
			String[] param = {key, value, String.valueOf(millis)};
			String script = "local ok = redis.call('setnx', KEYS[1], ARGV[1]) if ok == 1 then redis.call('PEXPIRE', KEYS[1], ARGV[2]) end return ok";
			Object status = jedis.eval(script, 1, param);
			if(((Number)status).intValue()==1){
				return new RedisLock(key, value);
			}
//		} finally{
//			RedisPool.close(jedis);
//		}
//		
//		return null;
//	}
	
	private static void check(String key, long time){
		checkKey(key);
		if(time<=0){
			throw new IllegalArgumentException("Invalid time value:"+time);
		}
	}
	
	private static void checkKey(String key){
		if(key==null){
			throw new NullPointerException("Null key!");
		}
		if(key.isEmpty()){
			throw new IllegalArgumentException("Empty key!");
		}
	}
	
	/**
	 * Releases the lock.
	 * @return true if successfully releases by the current thread,
	 * false otherwise.
	 */
	public boolean unlock(){
		Jedis jedis = null;
		try{
			jedis = RedisPool.get();
			String[] param = {key, value};
			Object status = jedis.eval("if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end", 1, param);
			return ((Number)status).intValue()>0;
		} finally{
			RedisPool.close(jedis);
		}
	}
}

RedisPool.java

import redis.clients.jedis.Jedis;

/**
 * 
 * 这里是你的Redis管理器的实现
 *
 */
public final class RedisPool {
 
	/**
	 * 从资源池里获取一个Jedis连接
	 * @return Jedis
	 */
	public static Jedis get(){
		//TODO 作为示例 直接返回null
		return null;
	}
	
	/**
	 * 将指定的Jedis连接归还给资源池
	 * @param jedis
	 */
	public static void close(Jedis jedis){
		//TODO 你的实现
	}
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值