天天看點

分布式鎖Redis實作

      上篇講了分布式鎖的資料庫實作,這篇我們繼續來講分布式鎖的redis實作。

那麼如何通過redis來實作一個分布式鎖呢? 一般最容易想到的指令,就是setNx

那在使用redis setNx指令時,還需要關注哪些點呢?

  1. redis分布式鎖常用指令,SETNX(key, val)

    當且僅當key不存在時,設定成功,傳回“1”,否者什麼都不做,傳回“0”

    我們可以利用該指令的特性進行加鎖操作。

    假如同時有兩個線程要競争資源,其中一個線程先擷取到資源,調用setnx指令,将設定成功,傳回“1”,表示加鎖成功。另一個線程也調用setnx指令,傳回“0”,表示該key已經設定過了,加鎖失敗,需要重新等待。

  2. expire key timeout

    為了防止,加鎖過程中出現異常,鎖一直不釋放,需要給加鎖的key,設定一個逾時時間,逾時時間的設定,是所有的分布式鎖實作都需要考慮的。

  3. delete key

    删除某個key,釋放對應的鎖。

     下面是簡單的源碼實作:

/**
 * 分布式鎖的簡單實作代碼
 */
public class DistributedLock {

    private final JedisPool jedisPool;

    public DistributedLock(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 加鎖
     * @param lockName       鎖的key
     * @param acquireTimeout 擷取逾時時間
     * @param timeout        鎖的逾時時間
     * @return 鎖辨別
     */
    public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {
        Jedis conn = null;
        String retIdentifier = null;
        try {
            // 擷取連接配接
            conn = jedisPool.getResource();
            // 随機生成一個value
            String identifier = UUID.randomUUID().toString();
            // 鎖名,即key值
            String lockKey = "lock:" + lockName;
            // 逾時時間,上鎖後超過此時間則自動釋放鎖
            int lockExpire = (int) (timeout / 1000);

            // 擷取鎖的逾時時間,超過這個時間則放棄擷取鎖
            long end = System.currentTimeMillis() + acquireTimeout;
            while (System.currentTimeMillis() < end) {
                if (conn.setnx(lockKey, identifier) == 1) {
                    conn.expire(lockKey, lockExpire);
                    // 傳回value值,用于釋放鎖時間确認
                    retIdentifier = identifier;
                    return retIdentifier;
                }
                // 傳回-1代表key沒有設定逾時時間,為key設定一個逾時時間
                if (conn.ttl(lockKey) == -1) {
                    conn.expire(lockKey, lockExpire);
                }

                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        } catch (JedisException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return retIdentifier;
    }

    /**
     * 釋放鎖
     * @param lockName   鎖的key
     * @param identifier 釋放鎖的辨別
     * @return
     */
    public boolean releaseLock(String lockName, String identifier) {
        Jedis conn = null;
        String lockKey = "lock:" + lockName;
        boolean retFlag = false;
        try {
            conn = jedisPool.getResource();
            while (true) {
                // 監視lock,準備開始事務
                conn.watch(lockKey);
                // 通過前面傳回的value值判斷是不是該鎖,若是該鎖,則删除,釋放鎖
                if (identifier.equals(conn.get(lockKey))) {
                    Transaction transaction = conn.multi();
                    transaction.del(lockKey);
                    List<Object> results = transaction.exec();
                    if (results == null) {
                        continue;
                    }
                    retFlag = true;
                }
                conn.unwatch();
                break;
            }
        } catch (JedisException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return retFlag;
    }
}