您的位置:首页 > 数据库 > Redis

基于redis setnx的简易分布式锁

2017-07-06 12:43 169 查看
(此文章实现有误,还未修复)

锁的原理, 就是设置个flag,记录下是否正被使用,可重入锁再判断下是否是自己在使用.

这个flag,必须保证所有资源用的是同一个.

synchronized关键字,lock类等, 可以保证此flag在单个jvm中唯一, 但是有多个jvm(集群,分布式)时候,就没办法保证了.

这时候需要用一个 跨jvm唯一的flag.  也就是常说的分布式锁了.

分布式锁的实现也有很多种. 基于redis,zookeeper等等, 也可以基于数据库.

基于redis的,也有setnx,incr等操作的.

本着简单,实用的原则, 写了一个简单的类,主要支持以下功能:

1.所有加锁操作都需要有时间限制,不能无限锁定

2.提供获取失败重试机制

3.释放锁时,保证释放的锁是自己获取到的

下面是代码,用到了jedis的jar, redis的连接是和spring整合配置的.这里就不列了. 所以不能直接运行

重点是LockUtil类.(锁存在,检查锁的剩余时间,然后重试,这里没有扣减此过程消耗的时间.所以极端情况会出现超出tryLock的timeOut设置而获取到锁)

import java.util.Objects;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.zhidian.common.util.SpringContextUtil;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisException;

/**
* 基于redis setnx的 分布式锁 实, 前提是所有的锁都要有锁定时间.
* 获取锁的时候,需要指定value,在unlock的时候,会根据value判断是否remove
*
* @author: qq315737546
*/
public class LockUtil {
private static Log logger = LogFactory.getLog(LockUtil.class);
private static final String LOCK_PREFIX = "LOCK";
private static final Integer DEFAULT_LOCK_TIME = 20;// 默认锁定时间秒
private static final Long DEFAULT_SLEEP_TIME = 100L;// 默认sleep时间,100毫秒

/**
* 单台服务器可直接用系统时间,多台服务器可用redis.time()
*
* @return
* @Author: qq315737546
*/
public static String getCurTime() {
return String.valueOf(System.currentTimeMillis());
}

/**
* 获取锁,如果失败,自动重试
*
* @param key
* @param value
* @return
* @Author: qq315737546
*/
public static void lock(String key, String value) {
lock(key, value, DEFAULT_LOCK_TIME);
}

/**
* 获取锁,如果失败,自动重试
*
* @param key
* @param value
* @param lockTime
*            锁定时间
* @return
* @Author: qq315737546
*/
public static void lock(String key, String value, int lockTime) {
lock(key, value, lockTime, true);
}

/**
*
* @param key
* @param value
* @param lockTime
*            锁定时间
* @param reTry
*            是否重试
* @return
* @Author: wangxingfei
*/
private static boolean lock(String key, String value, int lockTime, boolean reTry) {
return lock(key, value, lockTime, reTry, 0, false, 0);
}

/**
* 获取锁,如果失败,直接返回false
*
* @param key
* @param value
* @return
* @Author: qq315737546
*/
public static boolean tryLock(String key, String value) {
return tryLock(key, value, DEFAULT_LOCK_TIME);
}

/**
* 获取锁,如果失败,直接返回false
*
* @param key
* @param value
* @param lockTime
*            锁定时间
* @return
* @Author: qq315737546
*/
public static boolean tryLock(String key, String value, int lockTime) {
return lock(key, value, lockTime, false);
}

/**
* 尝试获取锁,如果获取失败,重试,直到成功或超出指定时间
*
* @param key
* @param value
* @param lockTime
* @param timeOutMillis
*            获取锁超时时间 (毫秒)
*
* @return
* @Author: qq315737546
*/
public static boolean tryLock(String key, String value, int lockTime, long timeOutMillis) {
return lock(key, value, lockTime, true, 0, true, timeOutMillis);
}

/**
* 释放锁,key对应的value于参数value一致,才删除key
*
* @param key
* @param value
* @Author: qq315737546
*/
public static void unlock(String key, String value) {
String fullKey = getFullKey(key);
String existValue = JedisUtil.getObject(fullKey);
if (Objects.equals(value, existValue)) {
logger.info("unlock success ; key:" + key + ",value:" + value);
JedisUtil.remove(fullKey);
} else {
logger.info("unlock failed ; key:" + key + ",value:" + value + ",existValue:" + existValue);
}
}

/**
* 获取锁
*
* @param key
* @param value
* @param lockTime
*            锁定时间
* @param reTry
*            失败是否重试
* @param curTryTime
*            当前尝试次数
* @param needTimeOut
*            是否需要判断超时时间
* @param timeOutMillis
*            尝试超时时间(毫秒)
* @return
* @Author: qq315737546
*/
private static boolean lock(String key, String value, int lockTime, boolean reTry, int curTryTime,
boolean needTimeOut, long timeOutMillis) {
logger.info(Thread.currentThread().getName() + ",lock come in ; key:" + key + ",value:" + value + ",lockTime:"
+ lockTime + ",reTry:" + reTry + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut
+ ",timeOutMillis:" + timeOutMillis);
curTryTime++;
String fullKey = getFullKey(key);
long result = JedisUtil.setnx(fullKey, value);
// 获取成功,直接返回
if (result > 0) {
logger.info("lock success ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
JedisUtil.expire(fullKey, lockTime);
return true;
}

// 如果不成功,判断现在的key的有效期,如果是无限期,则删除key,并重试
long expire = JedisUtil.ttl(fullKey);
if (expire < 0) {
if (expire == -1) {
JedisUtil.remove(fullKey);
logger.info("remove key ; key:" + key + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut
+ ",timeOutMillis:" + timeOutMillis);
} else {
logger.info("ttl key ; key:" + key + " is return " + expire);
}
return lock(key, value, lockTime, reTry, curTryTime, needTimeOut, timeOutMillis);
}

// 获取失败,不需要重试,直接返回
if (!reTry) {
logger.info("lock failed ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
return false;
}

// 获取失败, 且已超时,返回
if (needTimeOut && timeOutMillis <= 0) {
logger.info("lock failed ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
return false;
}

// 获取sleep时间
long sleepMillis = DEFAULT_SLEEP_TIME;
if (needTimeOut) {
timeOutMillis = timeOutMillis - DEFAULT_SLEEP_TIME;
if (timeOutMillis < DEFAULT_SLEEP_TIME) {
sleepMillis = timeOutMillis;
}
}

// sleep后重新获取锁
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
logger.error("lock sleep errro ; key:" + key + ",value:" + value, e);
}

if (curTryTime > 100) {
logger.warn("lock warning ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
}

return lock(key, value, lockTime, reTry, curTryTime, needTimeOut, timeOutMillis);
}

private static String getFullKey(String key) {
return LOCK_PREFIX + ":" + key;
}

}

class JedisUtil {

private static Log logger = LogFactory.getLog(JedisUtil.class);

protected static JedisPool jedisPool = (JedisPool) SpringContextUtil.getBean("jedisPool");;

/**
* 获取redis操作实例(不必加锁)
*
* @return jedis
*/
protected static Jedis getJedis() throws JedisException {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
} catch (JedisException e) {
logger.warn("failed:jedisPool getResource.", e);
if (jedis != null) {
jedisPool.returnBrokenResource(jedis);
}
throw e;
}
return jedis;
}

protected static void release(Jedis jedis, boolean isBroken) {
if (jedis != null) {
if (isBroken) {
jedisPool.returnBrokenResource(jedis);
} else {
jedisPool.returnResource(jedis);
}
}
}

/**
* set if not exists
*/
public static long setnx(String key, String value) {
Jedis jedis = null;
boolean isBroken = false;
long result = 0L;
try {
jedis = getJedis();
result = jedis.setnx(key, value);
} catch (Exception e) {
isBroken = true;
logger.error("JedisDao::setnx: key: " + key + ",value:" + value + " message: " + e.getMessage(), e);
} finally {
release(jedis, isBroken);
}
return result;
}

/**
* 设置过期时间
*/
public static void expire(String fullKey, int lockTime) {
Jedis jedis = null;
boolean isBroken = false;
try {
jedis = getJedis();
jedis.expire(fullKey, lockTime);
} catch (Exception e) {
isBroken = true;
logger.error("JedisDao:expire: key: " + fullKey + ",lockTime:" + lockTime + " message: " + e.getMessage(),e);
} finally {
release(jedis, isBroken);
}
}

/**
* 查看key的有效期
*
* @param fullKey
* @return 如果key不存在或者已过期,返回 -2 ;如果key没有设置过期时间(永久有效),返回 -1 ,否则返回有效期(单位秒)
*/
public static long ttl(String fullKey) {
Jedis jedis = null;
boolean isBroken = false;
Long result = 0L;
try {
jedis = getJedis();
result = jedis.ttl(fullKey);
} catch (Exception e) {
isBroken = true;
logger.error("JedisDao: ttl : key: " + fullKey + " message: " + e.getMessage(), e);
} finally {
release(jedis, isBroken);
}
return result;
}

public static void remove(String key) {
Jedis jedis = null;
boolean isBroken = false;
try {
jedis = getJedis();
jedis.del(key);
} catch (Exception e) {
isBroken = true;
logger.error("JedisDao::remove: key: " + key + " message: " + e.getMessage(), e);
} finally {
release(jedis, isBroken);
}
}

public static String getObject(String key) {
Jedis jedis = null;
boolean isBroken = false;
try {
jedis = getJedis();
String value = jedis.get(key);
return value;
} catch (Exception e) {
isBroken = true;
logger.error("JedisDao::getObject: key: " + key + " message: " + e.getMessage(), e);
} finally {
release(jedis, isBroken);
}
return null;

}

}


使用示例如下

String key = "testLock";
String value = LockUtil.getCurTime();

try{
LockUtil.lock(key, value);
}finally{
LockUtil.unlock(key, value);
}

boolean lock = LockUtil.tryLock(key, value);
if(lock){
try{
//TODO
}finally{
LockUtil.unlock(key, value);
}
}


欢迎讨论拍砖交流
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: