0


【Redis从入门到进阶】第 7 讲:基于 Redis 实现分布式锁

本文已收录于专栏
🍅《Redis从入门到进阶》🍅

专栏前言

   本专栏开启,目的在于帮助大家更好的掌握学习

Redis

,同时也是为了记录我自己学习

Redis

的过程,将会从基础的数据类型开始记录,直到一些更多的应用,如缓存击穿还有分布式锁等。希望大家有问题也可以一起沟通,欢迎一起学习,对于专栏内容有错还望您可以及时指点,非常感谢大家 🌹。

目录

1.什么是分布式锁?

  锁这个东西,大家都知道,在我们

jvm

内部多个线程竞争同一个资源时,我们利用

jvm

提供的

synchronized

或者一些其他的锁可以帮助我们让线程对资源的串行使用。但这种方法并不适合现在企业广泛使用的分布式架构。因为在这种集群模式下,

jvm

内部的锁无法被其他

jvm

内部感知到,那这样肯定无法满足我们的要求,因为锁肯定是要被大家都能感知到的,所以分布式锁应用而生。以前的锁竞争对象是线程之间,而分布式系统中竞争共享资源的单位从线程升级为了进程。

2. 分布式锁的条件

  那么作为一个分布式锁,它应该具备哪些条件呢?

  1. 可见性:多个进程之间均可以看见该锁,且可以尝试获取该锁
  2. 互斥性:锁最基本的特性,同一时间只能保证锁被一个进程持有
  3. 高可用性:也可以理解为容错性,当提供锁的服务结点产生故障时,程序不会因为守到强烈影响
  4. 高性能:锁的释放和添加本身十分消耗性能,我们应选择性能较好的锁

3.常见的分布式锁

  我们一般常见的分布式锁,有以下三种:

  1. MySQLMySQL自带锁机制,但由于其性能一般,所以作为分布式锁比较少见
  2. RedisRedis是分布式锁一种非常常见的实现方式,我们可以利用setnx这个方法,如果插入成功表示锁获取成功,否则获取失败。利用这个机制完成互斥,从而实现分布式锁,而且Redis存储在内存中本身就符合高性能特点。
  3. ZookeeperZookeeper也是企业开发中较好的一种实现分布式锁的方案,以后有机会讲解。

4.Redis 实现分布式锁

基于

Redis

实现分布式锁,我们使用两个方法:

  • 1.获取锁在这里插入图片描述 该指令会插入一个结构为lock:thread01的锁,且超时时间为100秒,返回值为OK说明获取锁成功,失败则返回false,该方法不会进行阻塞。
  • 2.释放锁在这里插入图片描述 通过手动删除该锁来进行释放,或者可以等待TTL让该锁自动过期

核心思路:
  利用

Redis

SETNX

方法,当多个进程同时竞争该锁时,都会调用该方法获取锁,只有一个进程成功能成功获取成功,此时

Redis

将会生成该锁,其他进程获取失败。那么获取锁成功的进程将会去执行业务,最后删除该锁,这样就将锁释放了。如果想让获取锁失败的进程重新获取,可以手动休眠一段时间后重新获取。

下面是一个简单的使用

StringRedisTemplate

实现分布式锁的代码:

publicclassDistributedLock{privateStringRedisTemplate redisTemplate;privateString lockKey;privateString lockValue;privatelong lockTimeout;//构造方法publicDistributedLock(StringRedisTemplate redisTemplate,String lockKey,String lockValue,long lockTimeout){this.redisTemplate = redisTemplate;//keythis.lockKey = lockKey;//valuethis.lockValue = lockValue;//过期时间this.lockTimeout = lockTimeout;}publicbooleantryLock(){// 尝试获取锁Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, lockTimeout,TimeUnit.MILLISECONDS);return result !=null&& result;}publicvoidunlock(){// 释放锁
        redisTemplate.delete(lockKey);}}

5. 分布式锁误删问题

  上面的逻辑看上去完美无缺,但还是存在很严重的问题,考虑下面一个场景:
逻辑说明
  线程

A

持有锁执行业务的时候发生了堵塞,导致他的锁

TTL

到期自动释放了,此时线程

B

成功获取到锁了,因为线程

A

已经释放该锁了。这时候线程

A

阻塞完毕后继续执行完业务,然后删除该锁,线程

B

执行完业务时突然发现——woc 我锁呢? 它的锁被线程

A

误删了,这就是分布式锁误删问题。
解决方案
  上诉问题的产生主要原因,还是因为每个线程并不知道该锁是不是自己的,那我们可以在删除锁的时候去加以判断,如果该锁不属于自己,则不删除该锁。如果该锁是自己的且还未到期,再进行删除锁。我们这里标识一把锁的时候同时存入线程的

ID

,一般在同一个

jvm

中线程的标识一般不相同,但我们这是在集群模式下,所以也有可能出现

ThreadID

重复的情况,所以我们可以考虑在前面拼接上一个

UUID


privatestaticfinalString ID_PREFIX = UUID.randomUUID().toString(true)+"-";@OverridepublicbooleantryLock(long timeoutSec){// 获取线程标识String threadId = ID_PREFIX +Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec,TimeUnit.SECONDS);returnBoolean.TRUE.equals(success);}@Overridepublicvoidunlock(){// 获取当前线程的标识String threadId = ID_PREFIX +Thread.currentThread().getId();// 获取锁中的标识String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);// 判断标识是否一致if(threadId.equals(id)){// 释放锁
        stringRedisTemplate.delete(KEY_PREFIX + name);}}

6. 分布式锁原子性问题

  在进行区分锁的处理后,那么是不是一定不会产生问题呢?
  考虑一种更加极端的情况,当线程

A

判断完标识发现一致后,准备释放锁的时候又突然出现了阻塞情况(比如

JVM

垃圾挥手),锁又到期了,线程

B

进来拿了一把锁,因为线程

A

已经判断完标识,所以它一删锁又把

B

的锁给删掉了,这就又产生了误删的问题。
  解决的方案需要我们使用

Lua

脚本,来保证拿锁、判断标识、删锁三个操作是一个原子性操作,而

Lua

脚本可以同时执行多条

Redis

指令并且保证原子性,

Lua

脚本是一门脚本语言,有兴趣可以自行了解一下。
在这里插入图片描述

标签: redis 分布式 java

本文转载自: https://blog.csdn.net/m0_57487901/article/details/129908693
版权归原作者 执 梗 所有, 如有侵权,请联系我们删除。

“【Redis从入门到进阶】第 7 讲:基于 Redis 实现分布式锁”的评论:

还没有评论