1. 程式人生 > >Redis實現分散式鎖(spring定時任務叢集應用Redis分散式鎖)

Redis實現分散式鎖(spring定時任務叢集應用Redis分散式鎖)

         之前2片文章介紹了

描述:

             不管用不用動態執行,單機服務都是沒有問題的,但是如果服務是叢集模式下,那麼一個任務在每臺機器都會執行一次,這肯定不是我們需要的,我們要實現的是整個叢集每次只有一個任務執行成功,但是spring對此並沒有很好的支援,所以我們需要有一個統一的資料獲取處,參考網上決定利用rides的一致性來實現,開始就參考網上利用setnx命令實現的,但是還是感覺不太完善,就去拋開定時任務,直接實現Redis分散式鎖應該是最合適的,在定時任務業務處加鎖,業務執行完解鎖即可。

             此事例經過多個網站參考並自己實際操作是不存在任何問題,完全可以正常使用。

實現:


import redis.clients.jedis.JedisCluster;

import java.util.Collections;

/**
 *
 * redis實現分散式鎖,並釋放鎖
 */
public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 嘗試獲取分散式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請求標識
     * @param expireTime 超期時間,毫秒
     * @return 是否獲取成功
     */
    public static boolean tryGetDistributedLock(JedisCluster jedis, String lockKey, String requestId, int expireTime) {
        /*
        *設定鎖並設定超時時間,lockKey表示Redis key,requestId表示Redis value,SET_IF_NOT_EXIST表示有值不進行設定(NX),
        * SET_WITH_EXPIRE_TIME表示是否設定超時時間(PX)設定,expireTime表示設定超時的毫秒值
        * */
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    /**
     * 釋放分散式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請求標識
     * @return 是否釋放成功
     */
    public static boolean releaseDistributedLock(JedisCluster jedis, String lockKey, String requestId) {

        /*
        * 利用Lua指令碼程式碼,首先獲取鎖對應的value值,檢查是否與requestId相等,如果相等則刪除鎖(解鎖)
        * eval命令執行Lua程式碼的時候,Lua程式碼將被當成一個命令去執行,並且直到eval命令執行完成,Redis才會執行其他命令,這樣就不會出現上一個程式碼執行完掛了後邊的出現問題,還是一致性的解決
        * */
        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(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}

根據上篇部落格的業務進行引用

package com.rails.travel.conf.task.myschedule;

import com.rails.travel.common.FrameSpringBeanUtil;
import com.rails.travel.common.RedisTool;
import redis.clients.jedis.JedisCluster;

import java.util.Date;


public class MyRunnable3 implements Runnable {

    //因為是執行緒所以無法注入bean物件,只能通過這種方法去獲取bean物件
    //FrameSpringBeanUtil工具類在另一篇部落格:https://blog.csdn.net/QiaoRui_/article/details/83094960
    private JedisCluster jedisCluster = FrameSpringBeanUtil.getBean(JedisCluster.class);

    @Override
    public void run() {
        //加鎖,呼叫上邊的工具類,引數具體看工具類說明
        boolean lock = RedisTool.tryGetDistributedLock(jedisCluster, MyRunnable3.class.toString(), MyRunnable3.class.toString(), 3000);
        //加鎖成功則執行業務,不成功則不執行業務
        if (lock){
            //此處業務程式碼
            System.out.print("業務執行了3" + new Date());
            //業務執行完成解鎖,如果在執行業務中或者是在解鎖出現了異常,宕機等,鎖會根據加鎖時的key過期時間自己消除
            RedisTool.releaseDistributedLock(jedisCluster,MyRunnable3.class.toString(), MyRunnable3.class.toString());
        }
    }
}

說明:

            加鎖解鎖工具類直接是檢視部落格和官網等獲取用的,部落格必須看,可以完全明白實現原理及現在網上的錯誤做法的問題所在,我開始用的就是部落格中提到的第一種錯誤方法