SETNX 并不难完美实现(不带过期时间),SETNX 实现锁有陷阱需谨慎
SETEX 复写,带过期时间(原子)
SETEX 复写,带过期时间(原子)
分布式锁工具
private static Logger logger = Logger.getLogger(LockUtils.class);
/**
* 最长时间锁为1天
*/
private final static int maxExpireTime = 24 * 60 * 60;
/**
* 系统时间偏移量15秒,服务器间的系统时间差不可以超过15秒,避免由于时间差造成错误的解锁
*/
private final static int offsetTime = 15;
/**
* 锁只是为了解决小概率事件,最好的方式是不用,从设计上避免分布式锁
*
* @param key
* key
* @param value
* @param waitTime
* 秒 - 最大等待时间,如果还无法获取,则直接失败
* @param expire
* 秒- 锁生命周期时间
* @return true 成功 false失败
* @throws Exception
*/
public static boolean Lock(String key, String value, int waitTime, int expire) {
long start = System.currentTimeMillis();
String lock_key = key + "_lock";
logger.info("开始获取分布式锁 key:" + key + " lock_key:" + lock_key + " value:" + value);
do {
try {
Thread.sleep(1);
long ret = CacheUtils.Setnx(CacheSpacePrefixEnum.TOOLBAR_SYS.name(), lock_key, System.currentTimeMillis() + "$T$" + value,
(expire > maxExpireTime) ? maxExpireTime : expire);
if (ret == 1) {
logger.info("成功获得分布式锁 key:" + key + " value:" + value);
return Boolean.TRUE;
} else { // 存在锁,并对死锁进行修复
String desc = CacheUtils.GSetnx(CacheSpacePrefixEnum.TOOLBAR_SYS.name(), lock_key);
// 首次锁检测
if (desc.indexOf("$T$") > 0) {
// 上次锁时间
long lastLockTime = NumberUtils.toLong(desc.split("[$T$]")[0]);
// 明确死锁,利用Setex复写,再次设定一个合理的解锁时间让系统正常解锁
if (System.currentTimeMillis() - lastLockTime > (expire + offsetTime) * 1000) {
// 原子操作,只需要一次,【任然会发生小概率事件,多个服务同时发现死锁同时执行此行代码(并发),
// 为什么设置解锁时间为expire(而不是更小的时间),防止在解锁发送错乱造成新锁解锁】
CacheUtils.Setex(CacheSpacePrefixEnum.TOOLBAR_SYS.name(), lock_key, value, expire);
logger.warn("发现死锁【" + expire + "秒后解锁】key:" + key + " desc:" + desc);
} else {
logger.info("当前锁key:" + key + " desc:" + desc);
}
} else {
logger.warn("死锁解锁中key:" + key + " desc:" + desc);
}
}
if (waitTime == 0) {
break;
}
Thread.sleep(500);
}
catch (Exception ex) {
logger.error(Trace.GetTraceStackDetails("获取锁失败", ex));
}
}
while ((System.currentTimeMillis() - start) < waitTime * 1000);
logger.warn("获取分布式锁失败 key:" + key + " value:" + value);
return Boolean.FALSE;
}
/**
* 解锁
*
* @param key
* @return
* @throws Exception
*/
public static boolean UnLock(String key) {
String lock_key = key + "_lock";
try {
CacheUtils.Del(CacheSpacePrefixEnum.TOOLBAR_SYS.name(), lock_key);
}
catch (Exception ex) {
logger.error(Trace.GetTraceStackDetails("解锁锁失败key:" + key + " lock_key:" + lock_key, ex));
}
return Boolean.FALSE;
}
redis操作分装部分
@Override
public Long Setnx(String key, String value, int expireTime) throws Exception {
ShardedJedis jedis = null;
try {
jedis = pool.getResource();
Long ret = jedis.setnx(key, value);
if (ret == 1 && expireTime > 0) {
jedis.expire(key, expireTime);
}
return ret;
}
catch (Exception e) {
throw e;
}
finally {
if (pool != null && jedis != null) {
pool.returnResourceObject(jedis);
}
}
}
@Override
public String Setex(String key, String value, int expireTime) throws Exception {
ShardedJedis jedis = null;
try {
jedis = pool.getResource();
String ret = jedis.setex(key, expireTime, value);
return ret;
}
catch (Exception e) {
throw e;
}
finally {
if (pool != null && jedis != null) {
pool.returnResourceObject(jedis);
}
}
}
@Override
public String GSetnx(String key) throws Exception {
ShardedJedis jedis = null;
try {
jedis = pool.getResource();
return jedis.get(key);
}
catch (Exception e) {
throw e;
}
finally {
if (pool != null && jedis != null) {
pool.returnResourceObject(jedis);
}
}
}
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/bigdata/8974.html