Jedis 實現簡單的分佈式鎖(基於jdk的Lock接口)

Jedis 實現簡單的分佈式鎖(基於jdk的Lock接口)

redis在高併發場景中的使用比較流行,雖然其內部IO處理使用單線程,但是依然能夠快速處理,支撐比較高的併發。基於這個特點,redis在互聯網應用中作爲分佈式鎖的中間件被廣泛應用,例如搶購,秒殺等業務場景。redis的分佈式鎖的實現原理在其官方文檔上面已經寫得十分詳細(https://redis.io/topics/distlock),此文章只是簡單地實現jedis的分佈式鎖,保證其互斥性(Mutual exclusion),無死鎖(Deadlock free),容錯性(Fault tolerance)。

代碼

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import org.apache.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class RedisDistributedLock implements Lock {

    Logger logger = Logger.getLogger(RedisDistributedLock.class);

    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 LOCK_TIMEOUT = 5 * 1000L;

    private static final long DEFAULT_EXPIRE_TIME = 1000L;
    private static final long RELEASE_SUCCESS = 1L;
    private static final String UNLOCK_SCRIPT = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;

    private JedisPool jedisPool;

    /**
     * redis store key
     */
    private String lockKey;

    /**
     * redis store value
     */
    private String lockUserId;

    /**
     * @param jedisPool
     * @param lockKey 
     * @param lockUserId (ensure uniqueness)
     */
    public RedisDistributedLock(JedisPool jedisPool, String lockKey, String lockUserId) {
        this.jedisPool = jedisPool;
        this.lockKey = lockKey;
        this.lockUserId = lockUserId;
    }

    @Override
    public void lock() {
        if (tryLock()) {
            if (logger.isDebugEnabled()) {
                logger.debug(lockUserId + 
                        " get the redis lock(" + lockKey + ") successfully.");
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        tryLock(DEFAULT_EXPIRE_TIME, TimeUnit.MILLISECONDS);
    }

    @Override
    public boolean tryLock() {
        try {
            return tryLock(LOCK_TIMEOUT, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * @param time redis key timeout
     * @param unit time unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        String result = null;
        Jedis jedis = null;
        while (!LOCK_SUCCESS.equals(result)) {
            if (Thread.currentThread().isInterrupted()) {
                evalUnLock(jedis);
                throw new InterruptedException();
            }

            try {
                jedis = jedisPool.getResource();
                result = jedis.set(lockKey, lockUserId, 
                        SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, unit.toMillis(time));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                jedis.close();
            }

            if (!LOCK_SUCCESS.equals(result)) {
                Thread.sleep(getRandomAquireTime());
            } else {
                return true;
            }
        }

        return false;
    }

    @Override
    public void unlock() {
        Long resultCode = null;

        Jedis jedis = jedisPool.getResource();
        resultCode = evalUnLock(jedis);

        if (RELEASE_SUCCESS == resultCode) {
            if (logger.isDebugEnabled()) {
                logger.debug(lockUserId + 
                        " release the redis lock(" + lockKey + ") successfully.");
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug(lockUserId + 
                        " release the redis lock(" + lockKey + ") unsuccessfully.");
            }
        }
    }

    @Override
    public Condition newCondition() {
        // TODO Auto-generated method stub
        return null;
    }

    private Long evalUnLock(Jedis jedis) {
        Long resultCode = null;
        try {
            resultCode = (Long) jedis.eval(UNLOCK_SCRIPT, 1, lockKey, lockUserId);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedis.close();
        }
        return resultCode;
    }

    /**
     * generate the sleep time
     * @return
     */
    private static long getRandomAquireTime() {
        return new Random().nextInt(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
    }

提示

如果獲取鎖不成功,那麼線程會隨機sleep一段時間,這樣做的目的是爲了避免同一時間內過大的併發量。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章