基于Redis实现的分布式锁

基于Redis的分布式锁实现

背景

  • 根据redis的setnx命令实现只有一个客户端可以拿到锁;
  • RedissonLock的分布式锁实现使用了lua脚本,这里提供一种不适用脚本实现的方法;

基本实现

  • 使用redis的setnx命令,再加上一个过期时间防止死锁
  • 缺点:不支持重入,不支持wait,如果调用unlock的时间>leaseTime,则会删除之后获得的锁;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.concurrent.TimeUnit;

/**
 * Created by jinshuan.li on 2016/12/1.
 */
public class DefaultDistrLock implements DistrLock {

    private static JedisPool jedisPool = null;
    private static Long DEFAULT_LEASE_TIME = 30000L;
    private String lockKey;

    public DefaultDistrLock(String lockKey) {
        this.lockKey = lockKey;
    }

    /**
     * 初始化
     *
     * @param jedisPool
     */
    public static void init(JedisPool jedisPool) {
        DefaultDistrLock.jedisPool = jedisPool;
    }

    @Override
    public boolean tryLock() {

        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Long setnx = jedis.setnx(lockKey, String.valueOf(Thread.currentThread().getId()));
            if (setnx.equals(1L)) {
                jedis.pexpire(lockKey, DEFAULT_LEASE_TIME);
                return true;
            }
        } finally {
            closeJedis(jedis);
        }
        return false;
    }

    @Override
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Long setnx = jedis.setnx(lockKey, String.valueOf(Thread.currentThread().getId()));
            if (setnx.equals(1L)) {
                long toMillis = unit.toMillis(leaseTime);
                jedis.pexpire(lockKey, toMillis);
                return true;
            }
        } finally {
            closeJedis(jedis);
        }
        return false;
    }

    @Override
    public void unlock() {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.del(lockKey);
        } finally {
            closeJedis(jedis);
        }
    }

    private void closeJedis(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
}

可重入的分布式锁实现

  • 思路:在setnx失败之后,查看其value是否为特定值,如果是,则可以继续获取锁。同时加入holdCount记录锁的层数,unlock时也要进行相应处理
  • 缺点:不可等待,每次竞争锁都需要访问redis
/**
 * Created by jinshuan.li on 2016/12/1.
 */
public class DefaultDistrLock implements DistrLock {

    private static JedisPool jedisPool = null;
    private static Long DEFAULT_LEASE_TIME = 30000L;
    private String lockKey;
    private String uuid = UUID.randomUUID().toString();

    private AtomicInteger holdCount = new AtomicInteger(0);

    public DefaultDistrLock(String lockKey) {
        this.lockKey = lockKey;
    }

    /**
     * 初始化
     *
     * @param jedisPool
     */
    public static void init(JedisPool jedisPool) {
        DefaultDistrLock.jedisPool = jedisPool;
    }

    @Override
    public boolean tryLock() {

        Jedis jedis = null;
        try {
            String setValue=uuid + Thread.currentThread().getId();
            jedis = jedisPool.getResource();
            Long setnx = jedis.setnx(lockKey, setValue);
            if (setnx.equals(1L)) {
                jedis.pexpire(lockKey, DEFAULT_LEASE_TIME);
                holdCount.incrementAndGet();
                return true;
            }
            String value = jedis.get(lockKey);
            if (StringUtils.equals(uuid + Thread.currentThread().getId(), value)) {
                // 可重入
                holdCount.incrementAndGet();
                return true;
            }
        } finally {
            closeJedis(jedis);
        }
        //没有获得锁 设置为0
        holdCount = new AtomicInteger(0);
        return false;
    }

    @Override
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) {
        Jedis jedis = null;
        try {
            String setValue=uuid + Thread.currentThread().getId();
            jedis = jedisPool.getResource();
            Long setnx = jedis.setnx(lockKey, setValue);
            if (setnx.equals(1L)) {
                long toMillis = unit.toMillis(leaseTime);
                jedis.pexpire(lockKey, toMillis);
                holdCount.incrementAndGet();
                return true;
            }
            String value = jedis.get(lockKey);
            if (StringUtils.equals(uuid + Thread.currentThread().getId(), value)) {
                // 可重入
                holdCount.incrementAndGet();
                return true;
            }
        } finally {
            closeJedis(jedis);
        }
        holdCount = new AtomicInteger(0);
        return false;
    }

    @Override
    public void unlock() {
        final int countValue = holdCount.decrementAndGet();
        if (countValue < 0) {
            throw new IllegalStateException("this thread does not get lock");
        }
        if (countValue == 0) {
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                jedis.del(lockKey);
            } finally {
                closeJedis(jedis);
            }
        }
    }

    private void closeJedis(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
}

支持重入,等待的分布式锁

  • 思路:在访问redis之前,先用一个innerLock拿到本地jvm的锁,原因是:如果在本地线程中都竞争不过,在分布式环境下则更竞争不过其他线程;
  • 因此同时访问redis的并发只跟机器数目有关;
  • 等待的实现,通过redis的pub、sub实现,同时使用countDownLatch来等待;在sub的通知中countDown以激活等待线程;
  • 最终不断重试来获取锁;
import org.apache.commons.lang.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;

import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by jinshuan.li on 2016/12/1.
 */
public class DefaultDistrLock implements DistrLock {

    private static JedisPool jedisPool = null;
    private static Long DEFAULT_LEASE_TIME = 30000L; // 默认租约时间
    private static ExecutorService executorService = Executors.newFixedThreadPool(5);
    private String lockKey;
    private String uuid = UUID.randomUUID().toString();
    private Lock innerLock = new ReentrantLock();
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    JedisPubSub pubSub = new JedisPubSub() {
        @Override
        public void onMessage(String channel, String message) {
            if (StringUtils.equals("DELETE", message)) {
                //有key过期了
                this.unsubscribe();
            }
        }

        @Override
        public void onUnsubscribe(String channel, int subscribedChannels) {
            //countDown以使等待线程里面结束
            countDownLatch.countDown();
        }
    };
    private AtomicInteger holdCount = new AtomicInteger(0);
    private long firstAccireTime = 0;
    private long leaseTime = DEFAULT_LEASE_TIME;

    public DefaultDistrLock(String lockKey) {
        this.lockKey = lockKey;
    }


    /**
     * 初始化
     *
     * @param jedisPool
     */
    public static void init(JedisPool jedisPool) {
        DefaultDistrLock.jedisPool = jedisPool;
    }

    @Override
    public boolean tryLock() {

        boolean getLocalLock = false;
        Jedis jedis = null;
        try {
            // 首先要赢得内部竞争
            getLocalLock = innerLock.tryLock();
            if (!getLocalLock) {
                return false;
            }
            jedis = jedisPool.getResource();
            boolean remoteLock = getRemoteLock(jedis, DEFAULT_LEASE_TIME, TimeUnit.MILLISECONDS);
            if (remoteLock) {
                return true;
            }
        } finally {
            closeJedis(jedis);
        }
        // 没有获得锁 设置为0
        holdCount = new AtomicInteger(0);
        return false;
    }

    @Override
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) {
        Jedis jedis = null;
        boolean getLocalLock = false;
        long waitTimeMillis = unit.toMillis(waitTime);
        try {
            // 首先要赢得内部竞争
            long startTime = System.currentTimeMillis();
            getLocalLock = innerLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
            if (!getLocalLock) {
                return false;
            }
            jedis = jedisPool.getResource();
            // 尝试获取锁
            boolean remoteLock = getRemoteLock(jedis, leaseTime, unit);
            if (remoteLock) {
                return true;
            }
            // 根据等待时间不断尝试
            while (true) {
                long lastWaitTime = waitTimeMillis - (System.currentTimeMillis() - startTime); // 还可以等待的时间
                if (lastWaitTime <= 0) {
                    break;
                }
                Long pttl = jedis.pttl(lockKey);
                if (null == pttl) {
                    break;
                }
                long shouldWaitTime = pttl < lastWaitTime ? pttl : lastWaitTime;
                if (shouldWaitTime != 0) {
                    if (countDownLatch.getCount() != 1) {
                        countDownLatch = new CountDownLatch(1);//重新赋值
                    }
                    subscribeDelete(); //订阅
                    countDownLatch.await(shouldWaitTime, TimeUnit.MILLISECONDS); // 等这么久
                }
                lastWaitTime = waitTimeMillis - (System.currentTimeMillis() - startTime); // 再次计算时间
                if (lastWaitTime > 0) {
                    // 还有时间 尝试获取锁
                    remoteLock = getRemoteLock(jedis, leaseTime, unit);
                    if (remoteLock) {
                        return true;
                    }
                } else {
                    break;
                }
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            closeJedis(jedis);
        }
        holdCount = new AtomicInteger(0);
        return false;
    }

    private JedisPubSub subscribeDelete() {
        if (pubSub.isSubscribed()) {
            return pubSub;
        }
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                Jedis jedis1 = null;
                try {
                    jedis1 = jedisPool.getResource();
                    jedis1.subscribe(pubSub, lockKey + "-channel");
                } finally {
                    closeJedis(jedis1);
                }
            }
        });

        return pubSub;
    }

    @Override
    public void unlock() {
        innerLock.unlock();
        if (holdCount.get() > 0) {
            // 此时才删除
            final int countValue = holdCount.decrementAndGet();
            if (countValue != 0) {
                return;
            }
            // 此处直接删除会有问题 考虑一种情况: unlock的时间已经过了leaseTime,key因为超时过期,key被其他线程获取 直接删除会把不属于该对象的key删除
            if (System.currentTimeMillis() - firstAccireTime >= leaseTime) {
                //如果当前时间大于租约时间 不处理
                return;
            }
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                jedis.del(lockKey);
                jedis.publish(lockKey + "-channel", "DELETE"); //发消息
            } finally {
                closeJedis(jedis);
            }

        }
    }

    private void closeJedis(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

    /**
     * 获取远程锁
     *
     * @param jedis
     * @param leaseTime
     * @param unit
     * @return
     */
    private boolean getRemoteLock(Jedis jedis, long leaseTime, TimeUnit unit) {
        String setValue = uuid + Thread.currentThread().getId();
        Long setnx = jedis.setnx(lockKey, setValue);
        if (setnx.equals(1L)) {
            long toMillis = unit.toMillis(leaseTime);
            jedis.pexpire(lockKey, toMillis);
            firstAccireTime = System.currentTimeMillis();
            this.leaseTime = leaseTime;
            holdCount.incrementAndGet();
            return true;
        }
        String value = jedis.get(lockKey);
        if (StringUtils.equals(setValue, value)) {
            // 可重入
            holdCount.incrementAndGet();
            return true;
        }
        return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值