基于Redis实现的分布式锁

来源:互联网 发布:淘宝何小姐正品 编辑:程序博客网 时间:2024/05/29 03:12

基于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;    }}
0 0
原创粉丝点击