Redis分佈式鎖 基於GETSET SETNX REDISSON 的實現

Redis分佈式鎖的應用html

  • 有些場景須要加鎖處理,好比:秒殺,全局遞增ID,樓層生成等等。大部分的解決方案是基於DB實現的,Redis爲單進程單線程模式,採用隊列模式將併發訪問變成串行訪問,且多客戶端對Redis的鏈接並不存在競爭關係。其次Redis提供一些命令SETNX,GETSET,能夠方便實現分佈式鎖機制。

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實現

https://my.oschina.net/haogrgr/blog/469439

相關文章
相關標籤/搜索