利用redis實現分布式鎖
SETNX 并不難完美實現(不帶過期時間),SETNX 實現鎖有陷阱需謹慎
SETEX 復寫,帶過期時間(原子) </div>
SETEX 復寫,帶過期時間(原子) </div>
分布式鎖工具
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);
}
}
} 本文由用戶 RegPoate 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!