1. 程式人生 > >Java 正確實現 redis 分散式鎖

Java 正確實現 redis 分散式鎖

Java 正確實現 redis 分散式鎖

1 源起

因為專案中有需要一個需求 ,就是在分散式的環境下 ,要根據一個 key,去獲取一把分散式鎖。所以。。。。。

2 我想要的效果

我想要的其實很簡單, 就是根據一個 key 獲取一個 分散式鎖就行了 比如這樣

// 獲取分散式鎖物件
RedisDistributeLock locker = new DefaultRedisDistributeLock();

// 鎖定
locker.lock
(jedis, "test1"); // TODO 業務邏輯 // 解鎖 locker.release(jedis, "test1");

3 擼起袖子開幹

3.1 匯入 jedis 依賴

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

3.2 RedisDistributeLock 介面

RedisDistributeLock 定義了一些分散式鎖的介面 實現, 本人能力有限,只實現了 非公平鎖


package org.study.distributed_look.redis.way2;

import redis.clients.jedis.Jedis;

/**
 * @Author: huangwenjun
 * @Description:
 * @Date: Created in 13:45  2018/5/22
 **/
public interface RedisDistributeLock {

    /**
     * 公平鎖, 只能說 本機的公平
     * @param jedis
     * @param key
     * @param uuid
     */
    void fairLock(Jedis jedis, String key, String uuid);

    /**
     * 非公平鎖
     * @param jedis
     * @param key
     * @param uuid
     */
    void unfairLock(Jedis jedis, String key, String uuid);

    /**
     * 鎖 預設非公平
     * @param jedis
     * @param key
     * @param uuid
     */
    void lock(Jedis jedis, String key, String uuid);

    /**
     * 解鎖
     * @param jedis
     * @param key
     * @param uuid
     */
    void release(Jedis jedis, String key, String uuid);
}

3.3 DefaultRedisDistributeLock

DefaultRedisDistributeLock 是我封裝的 預設的分散式鎖的實現


package org.study.distributed_look.redis.way2;

import org.study.distributed_look.redis.RedisTool;
import redis.clients.jedis.Jedis;

/**
 * @Author: huangwenjun
 * @Description:
 * @Date: Created in 13:48  2018/5/22
 **/
public class DefaultRedisDistributeLock implements RedisDistributeLock {

    /**
     * 預設 非公平鎖
     */
    private static final Boolean DEFALUT_FAIR = false;

    /**
     * 過期時間 預設 10 秒, 太短會導致鎖不住, 如果業務無法在指定過期時間內 完成, 則必須加長過期時間
     */
    private static final Integer DEFAULT_EXPIRE_TIME = 10000;

    private Boolean isFair;

    private Integer expireTime;

    public DefaultRedisDistributeLock() {
        isFair = DEFALUT_FAIR;
        expireTime = DEFAULT_EXPIRE_TIME;
    }

    public DefaultRedisDistributeLock(boolean isFair, Integer expireTime) {
        isFair = isFair;
        expireTime = expireTime;
    }

    @Override
    public void lock(Jedis jedis, String key, String uuid) {
        if (isFair) {
            fairLock(jedis, key, uuid);
        } else {
            unfairLock(jedis, key, uuid);
        }
    }

    @Override
    public void fairLock(Jedis jedis, String key, String uuid) {
        // 通過一個佇列維護, 參照 AQS 實現
    }

    @Override
    public void release(Jedis jedis, String key, String uuid) {
        try {
            while (true) {
                boolean released = RedisTool.releaseDistributedLock(jedis, key, uuid);

                if (released) {
                    break;
                }
            }
        } finally {
            jedis.close();
        }
    }

    @Override
    public void unfairLock(Jedis jedis, String key, String uuid) {
        while (true) {
            boolean locked = RedisTool.tryGetDistributedLock(jedis, key, uuid, expireTime);

            if (locked) {
                break;
            }
        }
    }
}

3.4 RedisTool

RedisTool 是我參照網上一個大神的程式碼, 正確的實現了 redis 分散式鎖的 獲取 和 釋放


package org.study.distributed_look.redis;

import redis.clients.jedis.Jedis;

import java.util.Collections;

/**
 *
 * redis 分散式鎖實現
 *
 * @Author: huangwenjun
 * @Description:
 * @Date: Created in 15:00  2018/5/21
 **/
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(Jedis jedis, String lockKey, String requestId, int 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(Jedis jedis, String lockKey, 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(lockKey), Collections.singletonList(requestId));

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

        return false;
    }
}

4 必須有測試啊!!!

4.1 測試程式碼


package org.study.distributed_look.redis.way2;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * @Author: huangwenjun
 * @Description:
 * @Date: Created in 14:10  2018/5/22
 **/
public class TestRedisDistributeLock {

    public static void main(String[] args) {
        for (int i = 1; i < 10; i ++) {
            new TestLock().start();
        }
    }

    static class TestLock extends Thread {

        static RedisDistributeLock locker = new DefaultRedisDistributeLock();

        JedisPool jedisPool = new JedisPool();

        @Override
        public void run() {
            Jedis jedis = jedisPool.getResource();
            locker.lock(jedis, "test1", "qwerqwer");

            // TODO 模擬業務
            System.out.println(Thread.currentThread().getName() + " get lock.....");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " release lock.....");

            locker.release(jedis, "test1", "qwerqwer");
        }
    }
}

4.2 輸出

Thread-4 get lock.....
Thread-4 release lock.....
Thread-2 get lock.....
Thread-2 release lock.....
Thread-7 get lock.....
Thread-7 release lock.....
Thread-9 get lock.....
Thread-9 release lock.....
Thread-0 get lock.....
Thread-0 release lock.....
Thread-8 get lock.....
Thread-8 release lock.....
Thread-5 get lock.....
Thread-5 release lock.....
Thread-6 get lock.....
Thread-6 release lock.....
Thread-3 get lock.....
Thread-3 release lock.....

4.3 優化策略

一些優化策略

  • 1 這裡為了簡單起見, 沒有優化 jedis 的連線池, 生產環境 必須手動設定 jedis 連線池
  • 2 locker.lock(jedis, “test1”, “qwerqwer”); 第三個 引數可以用 uuid, 至於為什麼要有第三個引數 參見

https://www.cnblogs.com/linjiqin/p/8003838.html

5 整合到業務中

// 獲取分散式鎖物件
RedisDistributeLock locker = new DefaultRedisDistributeLock();

// 鎖定
locker.lock(jedis, "test1", "uuid");

// TODO 業務邏輯

// 解鎖
locker.release(jedis, "test1", "uuid");

和之前設想的基本一致, 如果有人發現問題 歡迎指正