1. 程式人生 > >redis分散式鎖的實現方式

redis分散式鎖的實現方式

    前言:分散式鎖的實現方式一般有三種,1:基於資料庫的樂觀鎖。2:基於redis的分散式鎖。3:基於zk的分散式鎖,本文主要介紹第二種實現,由於以前一直是單機寫筆記,所以第一次寫有寫的不好的地方歡迎大家指正。

    網上對於redis分散式鎖的實現各有不同,今天分享的這種,不確定是不是最好的,但是個人覺得最易懂,好了廢話不多說,貼公司錯誤例子跟正確寫法。

//錯誤例子

public class RedisLock {

private static final Logger logger = LoggerFactory.getLogger(RedisLockNew.class);
private static final String REDIS_KEY_PREFIX = "redis_key_";
private static final String SET_IF_NOT_EXIST = "NX";//key不存在時,我們進行set操作;若key已經存在,則不做任何操作
private static final String SET_WITH_EXPIRE_TIME = "PX";//給key加一個過期的設定

@Autowired

private JedisCluster jedisCluster;

    /**
     * @description redis鎖
     * @param key
     * @param seconds 鎖過期時間(單位:秒)
     * @return
     */
public Boolean getLock(String key,Integer seconds) {
if(StringUtils.isBlank(key)){
logger.warn("key({}) cann't be blank(null or empty)!", key);
            return false;
}
String redis_key = REDIS_KEY_PREFIX+key;
String value = "1";//value可以任意,但是如果解鎖方式是基於value的,可以設定成唯一標識
Boolean res;
try{
Integer failedStaus =  jedisCluster.ttl(redis_key).intValue();//判斷是否過期失效
if(-1 == failedStaus){
jedisCluster.expire(redis_key, seconds);//重新設定過期時間
}
String result = jedisCluster.set(redis_key, value,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,(long) seconds);
res = JedisUtils.isStatusOk(result);
}catch(Exception e){
logger.error("setnx key:{},value:{},seconds:{}", new Object[]{redis_key, value, seconds, e});
            return false;
}
         return res;
}

/**
     * @description 釋放鎖
     * @param key
     * @param seconds 鎖過期時間(單位:秒)
     * @return
     */
public void releaseLock(String key){
String redis_key = REDIS_KEY_PREFIX+key;
jedisCluster.del(redis_key);
logger.info("delete redis_key={}", redis_key);
}
}

 上面這段程式碼可以說寫的很不嚴謹,而且有點冗餘,相信細心的朋友已經發現一個問題了,如果存在兩個客戶端,客戶端a在釋放鎖之前剛好鎖失效了,客戶端b獲取了鎖,然後a再執行del操作會導致a刪了b的鎖,所以這裡刪除鎖的操作是不對的,還有一段超時的判斷的程式碼並無實際意義,至少我看不出到底是什麼意思。。。而且缺少重新獲取鎖的操作。。。,下面是我改造後的程式碼。

 

public class RedisLock {


private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
private static final String LOCK_SUCCESS = "OK";
private static final String REDIS_KEY_PREFIX = "redis_key_";
private static final String SET_IF_NOT_EXIST = "NX";//key不存在時,我們進行set操作;若key已經存在,則不做任何操作

private static final String SET_WITH_EXPIRE_TIME = "PX";//給key加一個過期的設定

@Autowired
private JedisCluster jedisCluster;


/**
     * @description redis鎖
     * @param key
     * @param seconds 鎖過期時間(單位:秒)
     * @return
     */
public Boolean getNonBlockLock(String key,Integer seconds,String requestId) {
if(StringUtils.isBlank(key)){
logger.warn("key({}) cann't be blank(null or empty)!", key);
            return false;
}
String redis_key = REDIS_KEY_PREFIX+key;
String result = null;
try{
result = jedisCluster.set(redis_key, requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,(long) seconds);
if(LOCK_SUCCESS != result){//內部嘗試獲取鎖
logger.info(">>>  沒有獲取到鎖,正在嘗試 。。。  <<<");
result = innerTryLock(redis_key, requestId,seconds);
}
}catch(Exception e){
logger.error("setnx key:{},value:{},seconds:{}", new Object[]{redis_key, requestId, seconds, e});
            return false;
}
return JedisUtils.isStatusOk(result);
}

/**
* @description 內部嘗試獲取鎖
* @param redis_key
* @param value
* @param seconds
* @return
*/
public String innerTryLock(String redis_key,String requestId,Integer seconds){
String oldVal = jedisCluster.get(redis_key);
String result = "";
if(!StringUtils.isNotBlank(oldVal)){
result = jedisCluster.set(redis_key, requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,(long) seconds);
logger.info(">>>  嘗試之後獲取到了鎖!   <<<");
}else{
logger.info(">>>  嘗試之後沒有獲取到鎖!   <<<");
}
return result;
}

/**
     * @description 釋放鎖
     * @param key
     * @param seconds 鎖過期時間(單位:秒)
     * @return
     */
public void releaseLock(String key,String requestId){
/*String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(requestId));//luna指令碼方式*/
String redis_key = REDIS_KEY_PREFIX+key;
if(requestId.equals(jedisCluster.get(redis_key))){
jedisCluster.del(redis_key);
logger.info("delete redis_key={}", redis_key);
}else{
logger.info("can not del lock cause it is being used,redis_key={}", redis_key);
}
}

}

如果有朋友想在本地做測試的話,我寫了段粗略的測試程式碼,大家可以配合redis客戶端進行測試。

public class testRedisLock {

public static void main(String[] args) {
Jedis jedisCluster=new Jedis("127.0.0.1",6379);
                                              //30 60 
Long setnx = jedisCluster.setnx("LOCK_NAME", String.valueOf(System.currentTimeMillis() + 60*1000));
        //獲取鎖成功
        if(setnx !=null && setnx.intValue()==1){
            System.out.println(">>>Task02成功獲取到redis分散式鎖<<<");
            System.out.println("執行業務>>>");
        }else {
        System.out.println("Task02>>>沒有獲取到鎖,正在嘗試<<<");
        String lockVal = jedisCluster.get("LOCK_NAME");//獲取當前LOCK_NAME的時間
            //如果老的lockVal不為空,並且當前時間已經大於過期鎖過期時間,則證明可以獲取鎖
            //100 
            if(lockVal !=null && System.currentTimeMillis()>Long.parseLong(lockVal)){
                //設定LOCK_NAME新的值,返回的是LOCK_NAME老的值
                String getOldSetVal = jedisCluster.getSet("LOCK_NAME", String.valueOf(System.currentTimeMillis() +60*1000));
                if(getOldSetVal ==null ||(getOldSetVal!=null && StringUtils.equals(lockVal,getOldSetVal))){
                System.out.println("Task02>>>嘗試之後獲取到了鎖<<<");
                    //如果返回的getOldSetVal 為空則證明以前的鎖已經被釋放,則可以重新獲取鎖
                    //如果返回的getOldSetVal 不為空且lockVal == getOldSetVal ,返回的值和查詢的時間過期值確實相等,那麼證明我確實拿到這把鎖
                System.out.println("執行業務>>>2<<<");
                }else{
                System.out.println("Task02>>>嘗試之後沒有獲取到了鎖>>>");
                }
            }else{
            System.out.println("Task02>>>嘗試之後沒有獲取到了鎖>>>");
            }
        }
        jedisCluster.close();
}

}

第一次寫部落格,還有很多不足,希望大家多多指正,一起進步。