Redis分佈式鎖的應用html
GETSET方式java
http://doc.redisfans.com/string/getset.htmlredis
public final class RedisLockUtil { private static final int defaultExpire = 60; /** * 加鎖 * @param key redis key * @param expire 過時時間,單位秒 * @return true:加鎖成功,false,加鎖失敗 */ public static boolean lock(String key, int expire) { RedisService redisService = SpringUtils.getBean(RedisService.class); long status = redisService.setnx(key, "1"); //若是狀態等於一 則 成功 返回值成功 if(status == 1) { redisService.expire(key, expire); return true; } //不然設置失敗 return false; } public static boolean lock(String key) { return lock2(key, defaultExpire); } /** * 加鎖 * @param key redis key * @param expire 過時時間,單位秒 * @return true:加鎖成功,false,加鎖失敗 */ public static boolean lock2(String key, int expire) { RedisService redisService = SpringUtils.getBean(RedisService.class); long value = System.currentTimeMillis() + expire;//當時時間加過時時間 long status = redisService.setnx(key, String.valueOf(value));//嘗試存一下 if(status == 1) { //若是能夠直接存進去就 成功了 獲取了鎖 return true; } //若是沒有設置成功 的後獲取鎖的value long oldExpireTime = Long.parseLong(redisService.get(key, "0")); if(oldExpireTime < System.currentTimeMillis()) { //超時 long newExpireTime = System.currentTimeMillis() + expire; //用getset覆蓋 (getset理解爲先get,再set。get的是舊的值,set的是新的值。) long currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime))); if(currentExpireTime == oldExpireTime) { return true; } } return false; } public static void unLock1(String key) { RedisService redisService = SpringUtils.getBean(RedisService.class); redisService.del(key); } public static void unLock2(String key) { RedisService redisService = SpringUtils.getBean(RedisService.class); long oldExpireTime = Long.parseLong(redisService.get(key, "0")); //oldExpireTime = 原當前時間 + 過時時間 其實不大於當前時間 都已通過期了 if(oldExpireTime > System.currentTimeMillis()) { redisService.del(key); } } }
在實際使用總大可能是註解,獲取切面實現。併發
SETNX方式分佈式
http://redisdoc.com/string/setnx.htmlide
如下是http://blog.csdn.net/u010359884/article/details/50310387 實現方式,通過使用和一些業務改造。this
public class CacheLockInterceptor implements InvocationHandler{ public static int ERROR_COUNT = 0; private Object proxied; public CacheLockInterceptor(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { CacheLock cacheLock = method.getAnnotation(CacheLock.class); //沒有cacheLock註解,pass if(null == cacheLock){ System.out.println("no cacheLock annotation"); return method.invoke(proxied, args); } //得到方法中參數的註解 Annotation[][] annotations = method.getParameterAnnotations(); //根據獲取到的參數註解和參數列表得到加鎖的參數 Object lockedObject = getLockedObject(annotations,args); String objectValue = lockedObject.toString(); //新建一個鎖 RedisLock lock = new RedisLock(cacheLock.lockedPrefix(), objectValue); //加鎖 boolean result = lock.lock(cacheLock.timeOut(), cacheLock.expireTime()); if(!result){//取鎖失敗 ERROR_COUNT += 1; throw new CacheLockException("get lock fail"); } try{ //加鎖成功,執行方法 return method.invoke(proxied, args); }finally{ lock.unlock();//釋放鎖 } } /** * * @param annotations * @param args * @return * @throws CacheLockException */ private Object getLockedObject(Annotation[][] annotations,Object[] args) throws CacheLockException{ if(null == args || args.length == 0){ throw new CacheLockException("方法參數爲空,沒有被鎖定的對象"); } if(null == annotations || annotations.length == 0){ throw new CacheLockException("沒有被註解的參數"); } //不支持多個參數加鎖,只支持第一個註解爲lockedObject或者lockedComplexObject的參數 int index = -1;//標記參數的位置指針 for(int i = 0;i < annotations.length;i++){ for(int j = 0;j < annotations[i].length;j++){ if(annotations[i][j] instanceof LockedComplexObject){//註解爲LockedComplexObject index = i; try { return args[i].getClass().getField(((LockedComplexObject)annotations[i][j]).field()); } catch (NoSuchFieldException | SecurityException e) { throw new CacheLockException("註解對象中沒有該屬性" + ((LockedComplexObject)annotations[i][j]).field()); } } if(annotations[i][j] instanceof LockedObject){ index = i; break; } } //找到第一個後直接break,不支持多參數加鎖 if(index != -1){ break; } } if(index == -1){ throw new CacheLockException("請指定被鎖定參數"); } return args[index]; } }
/** * 加鎖 * 使用方式爲: * lock(); * try{ * executeMethod(); * }finally{ * unlock(); * } * @param timeout timeout的時間範圍內輪詢鎖 * @param expire 設置鎖超時時間 * @return 成功 or 失敗 */ public boolean lock(long timeout,int expire){ long nanoTime = System.nanoTime(); timeout *= MILLI_NANO_TIME; try { //在timeout的時間範圍內不斷輪詢鎖 while (System.nanoTime() - nanoTime < timeout) { //鎖不存在的話,設置鎖並設置鎖過時時間,即加鎖 if (this.redisClient.setnx(this.key, LOCKED) == 1) { this.redisClient.expire(key, expire);//設置鎖過時時間是爲了在沒有釋放 //鎖的狀況下鎖過時後消失,不會形成永久阻塞 this.lock = true; return this.lock; } System.out.println("出現鎖等待"); //短暫休眠,避免可能的活鎖 Thread.sleep(3, RANDOM.nextInt(30)); } } catch (Exception e) { throw new RuntimeException("locking error",e); } return false; } public void unlock() { try { if(this.lock){ redisClient.delKey(key);//直接刪除 } } catch (Throwable e) { } }
這也是你們經常使用的方式,可是這種方式的實現存在鎖過時釋放時,被正確釋放。推薦GETSET。.net
https://www.cnblogs.com/yjf512/archive/2017/03/22/6597814.html線程
http://blog.csdn.net/u010648555/article/details/70139541指針
若是長時間獲取不到,就會獲取鎖失敗,至關於沒加鎖!
這裏還有可能發生其餘問題:
(1)併發狀況,expire主動釋放鎖的時候,可能釋放的是別人的鎖;
好比,這個鎖我上了10s,可是我處理的時間比10s更長,到了10s,這個鎖自動過時了,被別人取走了,而且對它從新上鎖了。那麼這個時候,我再調用Redis::del就是刪除別人創建的鎖了。
(2)Redis服務掛掉,鎖失敗,至關於沒加鎖!最好使用主從+哨兵提升 高可用。集羣。
Redis實現分佈式鎖全局鎖—Redis客戶端Redisson中分佈式鎖RLock實現