基于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
- 基于redis的分布式锁的实现
- 基于redis的分布式锁的实现
- 基于Redis的分布式锁实现
- 基于Redis的分布式锁实现方式
- Java实现基于Redis的分布式锁
- Java实现基于Redis的分布式锁
- Java实现基于Redis的分布式锁
- Java实现基于Redis的分布式锁
- 基于redis实现可靠的分布式锁
- 基于Redis实现简单的分布式锁
- 基于redis的分布式锁服务实现
- 基于redis 实现分布式锁的方案
- 基于Redis实现简单的分布式锁
- 基于Redis实现的分布式锁
- Java实现基于Redis的分布式锁
- 基于redis实现的分布式锁
- 基于Redis的分布式锁实现
- 分布式锁实现方式二 基于Redis的分布式锁
- 加了限制条件的动态规划
- 放苹果问题
- 二分法求快速幂
- 第一周-计算
- Mysql搜索引擎总结
- 基于Redis实现的分布式锁
- 【php基础班】第9天 if语句、switch语句、while语句、dowhile语句
- jdk+tomcat环境配置
- C语言指针与二维数组
- 【转载】[Poj 2187] 计算几何之凸包(二) {更高效的算法}
- bootstrap导航条--三级菜单
- Android Studio中的Gradle的总结
- ZooKeeper-3.3.4集群安装配置
- DFT,IDFT,FFT,IFFT算法的C++实现