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/8974.html