首页 > 其他 > 详细

redis实现分布式锁

时间:2021-05-03 00:18:10      阅读:29      评论:0      收藏:0      [点我收藏+]

Redis分布式锁

我们使用redis 实现分布式锁,因为redis是单线程的,因此我们不必考虑并发安全问题

1.加锁

setnx lock volue  //返回1代表lock这个键不存在,即获取到锁,返回0代表锁有人在用

2.解锁

del lock

3.设置过期时间

如果在删除锁之前客户端宕机,锁就永远无法删除,所以我们可以设置过期时间
也可以引入watch-dog本质是一个守护线程,只要客户端不宕机,又没有释放锁就给锁续期
@Component
public class RedisService {

private final Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Autowired
    JedisPool jedisPool;
     
    // 获取锁之前的超时时间(获取锁的等待重试时间)
    private long acquireTimeout = 5000;
    // 获取锁之后的超时时间(防止死锁)
    private int timeOut = 10000;
    
    /**
     * 获取分布式锁
     * @return 锁标识
     */
    public boolean getRedisLock(String lockName,String val) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            // 1.计算获取锁的时间
            Long endTime = System.currentTimeMillis() + acquireTimeout;
            // 2.尝试获取锁
            while (System.currentTimeMillis() < endTime) {
                // 3. 获取锁成功就设置过期时间
                if (jedis.setnx(lockName, val) == 1) {
                    jedis.expire(lockName, timeOut/1000);
                    return true;
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            returnResource(jedis);
        }
        return false;
    }
    /**
     * 释放分布式锁
     * @param lockName 锁名称
     */
    public void unRedisLock(String lockName) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            // 释放锁
            jedis.del(lockName);
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            returnResource(jedis);
        }
    }

但此时仍然可能出现问题! 如果A获取锁后 超时了,锁被释放,B获取到锁,但A如果此时删除了锁,及时B没有释放锁 ,C也可能获得锁,因此我们需要判断是否删除的是自己的锁,我们可以在value中存入当前线程的唯一标识

/**
 * 第二种分布式锁
 */
public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    
    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, "NX", "PX", 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) {
        if (jedis.get(lockKey).equals(requestId)) {
            System.out.println("释放锁..." + Thread.currentThread().getName() + ",identifierValue:" + requestId);
            jedis.del(lockKey);
            return true;
        }
        return false;
    }
}

这里还有问题,就是如果我们在判断是不是自己锁的时候,由于redis与服务器之间通信有延迟,所以可能在传输结果过程中锁过期了,自动释放了,但是判断结果传输过来了通过了,我们删除的还不是自己的锁,因此判断+删除锁应该也是一个原子性的操作,我们需要使用Lua脚本

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end

Redisson

Redisson是redis推出的解决redis在分布式情况下的一些问题的一个框架,对分布式锁也有比较好的支持
1.导入依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.11.1</version>
</dependency>

2.编写配置类,返回RedissonClient

@Configuration
public class MyRedissonConfig{
@Bean(destroyMethod="shutdown")
public RedissionClient redisson() throws IOException{
    //创建配置
   Config config=new Config();
   //我这里是使用单机模式,注意必须加上redis://,如果redis启用了安全连接则必须为rediss://
   config.useSingleServer().setAddress("redis://localhost:6379");
   //返回RedissionClient
   return Redisson.create(config);
    
}
}

也可以通过yml文件的方式

Config config = Config.fromYAML(new File("config-file.yaml"));
RedissonClient redisson = Redisson.create(config);

3.测试
可重入锁:某个线程在已经获取锁的情况下可以再次获取锁

@Autowird
RedissonClient redissonClient;
public void test(){
RLock lock=redission.getLock("lock1");  //括号里为锁的名字,只要锁名一样就是同一把锁
lock.lock();//加锁,会自璇尝试获取锁,锁的默认时间是30s

try{
 Thread.sleep(30000);

}catch (Exception e){
}finally{
  lock.unlock();//解锁
}
}

Redisson中有一个看门狗机制,可以实现
1.对锁的自动续期,如果业务时间较长,锁会自动续期(看门狗时间/3 续一次)
2.加锁的业务只要自动完成,就不会给当前锁续期,即使出现异常没有手动解锁,锁的默认时间是30s

如果是手动设置锁的过期时间,则不能自动续期

lock.lock(10,TimeUnit.SECONDS);//此时过期时间一定要大于业务时间

即使锁过期了,业务没执行完,也不会删除不是自己的锁(会基于Lua脚本自动判断锁是不是自己的)

4.其他锁

//公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序依次获取锁,类似于排队吃饭。
RLock fairLock = redissonClient.getFairLock("anyLock");

//连锁:基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
lock.lock();// 所有的锁都上锁成功才算成功。
...
lock.unlock();
//红锁:从多个独立的Redis节点中获取锁,只有获得绝大多数锁才算获得锁
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
lock.lock();// 红锁在大部分节点上加锁成功就算成功。
...
lock.unlock();

//读写锁(**推荐**):读共有,写独占,读需要等待写
RReadWriteLock rwlock = redissonClient.getReadWriteLock("anyRWLock");
rwlock.readLock().lock();// 最常见的使用方法
rwlock.writeLock().lock();

//信号量:限制同时访问的线程数量
RSemaphore semaphore=redissonClient.getSemaphore("semaphore");
semaphore.trySetPermits(5); //设置信号量

semaphore.acquire();//占用一个坑位,阻塞式获取
boolean exits=semaphore.tryAcquire();//获取不到就返回false,可以用作限流
semaphore.release();//释放一个坑位


//闭锁:分布式CountDownLatch,等待其他线程完成后再继续
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(5);
latch.await();

RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");// 在其他线程或其他JVM中
latch.countDown();




redis实现分布式锁

原文:https://www.cnblogs.com/wangstudyblog/p/14726556.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!