这篇文章主要介绍“什么是Redis分布式锁”,在日常操作中,相信很多人在什么是Redis分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是Redis分布式锁”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
在分布式系统中,有些业务场景会用到分布式锁,实现分布式锁的方式有很多,本篇主要讲根据Redis如何来实现。
首先我们要知道分布式锁的一些基本特点:
互斥性:只有一个客户端可以持有锁
不会产生死锁:即使持有锁的客户端崩溃,也能保证后续其他客户端可以获得锁
只有持有这把锁的客户端才能解锁
下边我们通过几个例子来说明分布式锁为什么需要以上3个特点。
/** * 使用jedis客户端实现分布式锁 * @Author: maomao * @Date: 2021-04-27 08:42 */ public class DistLock { 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 RELEASE_SUCCESS = 1L; /** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param value 值 * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String value, String requestId, int expireTime) { // set支持多个参数 NX(not exist) XX(exist) EX(seconds) PX(million seconds) String result = jedis.set(lockKey, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } /** * 直接删除解锁,未判断客户端ID,会导致其他客户端把锁释放 * @param jedis * @param lockKey */ public static void releaseLock1(Jedis jedis, String lockKey) { jedis.del(lockKey); } }
上边代码是一个不验证客户端的例子,加锁是没有问题的,但在解锁时会有很大的问题。
通过上图可以看到,因为没有校验客户端逻辑,Thread B可以直接解锁,而Thread A程序还未执行完,但已被解锁,造成锁失效。如果此时有其他客户端加锁是可以加锁成功的。
那我们可以在代码中增加一个客户端校验不就可以了?
/** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识-修改此处为客户端唯一标致 * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { // set支持多个参数 NX(not exist) XX(exist) EX(seconds) PX(million seconds) String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } /** * 增加锁判断,但因判断与删除不是原子操作,在并发场景时,会导致错误删除 * @param jedis * @param lockKey * @param requestId */ public static void releaseLock2(Jedis jedis, String lockKey, String requestId) { // 判断加锁与解锁是不是同一个客户端 if (requestId.equals(jedis.get(lockKey))) { // 若在此时,这把锁突然不是这个客户端的,则会误解锁 // 两个操作不能保证原子性 jedis.del(lockKey); } }
在解锁代码中可以看到,我们也增加了客户端标志校验应该可以解决客户端校验问题了吧?其实并没有,我们要知道对redis来说,每个命令都是原子的,你的get与del方法是两个命令,无法保证原子操作。也就是我们多线程中常见的i++;操作,其实他是由3个操作执行。
那我们如何确保get与del的原子操作呢?我们可以使用lua脚本来实现。上述代码我们可以调整为一个lua脚本。
/** * 释放分布式锁,使用lua脚本删除,可确保判断与删除的原子操作 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; }
通过增加客户端校验与解锁的原子性就可以实现安全的解锁。
有了上边的方式是不是就可以确保分布式锁的全部问题了?并不是,还有一种场景没有考虑到。
如果我们的加锁程序执行时间超出锁过期时间时,就会导致分布式锁失效。此时其他客户端是可以获得到锁的。如下图:
那么这种问题如何解决呢?
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid),提供了分布式和可扩展的Java数据结构,比如分布式对象,分布式集合(Map、List、Queue、Set),分布式锁等等功能,不需要自己去运行一个服务实现。
Redisson官网
Redisson Git地址
Redission是由一个中国人与俄罗斯人共同发起的,所以中文文档比较详细。
使用Redission可以很简单的实现分布式锁,代码如下:
public static void main(String[] args) throws InterruptedException { //设定锁标志 //会在redis中创建一个Hash,Key是客户端UUID,value是锁重入次数 RLock rLock = redissonClient.getLock("lockKey"); // 最多等待100秒、上锁10s以后自动解锁 if(rLock.tryLock(100,10, TimeUnit.SECONDS)){ System.out.println("获取锁成功,此时可以查看redis中的数据!"); } //线程等待后可在redis中查到 Thread.sleep(20000); rLock.unlock(); }
Redission不只可以实现独占锁,还可以实现如:可重入锁、公平锁、联锁、红锁、读写锁等等。
redission实现分布式锁的逻辑基本与上边我们讲的原理差不多,它还解决了我们最后一个问题,程序执行时间超出锁过期时间的问题。
他使用了一个《看门狗》的概念来实现自动续期。默认最大续期时间30s,也就是说如果业务超出30秒还未执行会自动解锁。
到此,关于“什么是Redis分布式锁”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。