综合门户网站开发,做网站学什么代码,淘客免费网站建设,什么网站可以找到防水工程做文章目录 #x1f354;Redis的分布式锁#x1f6f8;误删问题#x1f388;解决方法#x1f50e;代码实现 #x1f6f8;原子性问题#x1f339;Lua脚本 ⭐利用Java代码调用Lua脚本改造分布式锁#x1f50e;代码实现 #x1f354;Redis的分布式锁
Redis的分布式锁是通过利… 文章目录 Redis的分布式锁误删问题解决方法代码实现 原子性问题Lua脚本 ⭐利用Java代码调用Lua脚本改造分布式锁代码实现 Redis的分布式锁
Redis的分布式锁是通过利用Redis的原子操作和特性来实现的。在分布式环境中多个应用程序或服务可能同时访问共享资源为了保证数据的一致性和避免冲突可以使用分布式锁来进行同步控制。
以下是一种常见的使用Redis实现分布式锁的方式
获取锁当一个应用程序需要获取锁时它可以通过执行以下操作在Redis中设置一个特定的键值对
SET lock_key unique_value NX PX lock_timeout这里的lock_key是锁的唯一标识unique_value是唯一的值可以是随机生成的UUIDNX表示只有当键不存在时才会设置成功PX表示设置键的过期时间。通过设置过期时间即使获取锁的应用程序崩溃或异常退出锁也会在一段时间后自动释放避免出现死锁。
释放锁当应用程序完成对共享资源的操作后它可以通过执行以下操作释放锁
if GET lock_key unique_value thenDELETE lock_key
end应用程序首先获取锁的当前值然后比较是否与自己持有的唯一值相等如果相等则删除该键表示释放锁。这样可以确保只有持有锁的应用程序才能释放锁避免误释放其他应用程序的锁。 需要注意的是分布式锁并不是绝对安全和可靠的。在高并发的环境中可能存在竞争条件和死锁等问题。因此在实际使用中需要考虑更复杂的场景和解决方案。
误删问题
遇到下面的情况的话会出现Redis分布式锁的误删问题 这种情况下。线程1首先获取锁但是发生了阻塞于是线程2拿到了执行权在线程2执行的过程中线程1苏醒了继续执行到后面线程1执行到了删除锁的操作此时就会把本应该属于线程2的锁删除这样子就造成了误删问题
解决方法
就是在每个线程释放锁的时候去判断一下当前这把锁是否属于自己如果属于自己则不进行锁的删除假设还是上边的情况线程1卡顿锁自动释放线程2进入到锁的内部执行逻辑此时线程1反应过来然后删除锁但是线程1一看当前这把锁不是属于自己于是不进行删除锁逻辑当线程2走到删除锁逻辑时如果没有卡过自动释放锁的时间点则判断当前这把锁是属于自己的于是删除这把锁。 代码实现
public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name name;this.stringRedisTemplate stringRedisTemplate;}private static final String KEY_PREFIX lock:;//使用uuid在获取锁的时候存入线程标识private static final String ID_PREFIX UUID.randomUUID().toString(true) -;Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标示String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);//这里不能是return success否则 因为public后面的boolean是基本类型而Boolean是引用类型如果直接返回success是一个自动拆箱的过程可能回发生空指针异常}Overridepublic void unlock() {// 获取线程标示String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁中的标示String id stringRedisTemplate.opsForValue().get(KEY_PREFIX name);// 判断标示是否一致if(threadId.equals(id)) {// 释放锁stringRedisTemplate.delete(KEY_PREFIX name);}}
}
原子性问题
上面我们解决了误删问题 在误删问题的情况下遇到下面的情况的话会出现Redis分布式锁的原子性问题 这种情况下线程1先执行一段线程1先判断锁标识判断成功标识是属于线程1的后面就在线程1正准备删除锁释放的过程中突然线程1的锁过期了线程1发生阻塞 这个时候线程2开始执行在线程2执行过程中线程1阻塞结束了会执行删除锁的操作相当于判断锁标识并没有起到作用因为之前一句判断过了于是就把线程2的锁给删除掉了又一次发生了误删操作 这个时候线程3趁虚而入执行业务 这就是删锁时的原子性问题之所以有这个问题是因为判断锁标识和删除锁是2个动作这2个动作中间产生了阻塞 那么我们就要让这2个操作一起执行中间不能出现间隔
Lua脚本
Redis提供了Lua脚本功能在一个脚本中编写多条Redis命令确保多条命令执行时的原子性。Lua是一种编程语言它的基本语法大家可以参考网站https://www.runoob.com/lua/lua-tutorial.html这里重点介绍Redis提供的调用函数我们可以使用lua去操作redis又能保证他的原子性这样就可以实现拿锁比锁删锁是一个原子性动作了作为Java程序员这一块并不作一个简单要求并不需要大家过于精通只需要知道他有什么作用即可。
这里重点介绍Redis提供的调用函数语法如下
redis.call(命令名称, key, 其它参数, ...)例如我们要执行set name jack则脚本是这样
# 执行 set name jack
redis.call(set, name, jack)例如我们要先执行set name Rose再执行get name则脚本如下
# 先执行 set name jack
redis.call(set, name, Rose)
# 再执行 get name
local name redis.call(get, name)
# 返回
return name写好脚本以后需要用Redis命令来调用脚本调用脚本的常见命令如下 例如我们要执行 redis.call(‘set’, ‘name’, ‘jack’) 这个脚本语法如下 如果脚本中的key、value不想写死可以作为参数传递。key类型参数会放入KEYS数组其它参数会放入ARGV数组在脚本中可以从KEYS和ARGV数组获取这些参数 ⭐利用Java代码调用Lua脚本改造分布式锁
接下来我们来回一下我们释放锁的逻辑
释放锁的业务流程是这样的
1、获取锁中的线程标示
2、判断是否与指定的标示当前线程标示一致
3、如果一致则释放锁删除
4、如果不一致则什么都不做
如果用Lua脚本来表示则是这样的
最终我们操作redis的拿锁比锁删锁的lua脚本就会变成这样
-- 这里的 KEYS[1] 就是锁的key这里的ARGV[1] 就是当前线程标示
-- 获取锁中的标示判断是否与当前线程标示一致
if (redis.call(GET, KEYS[1]) ARGV[1]) then-- 一致则删除锁return redis.call(DEL, KEYS[1])
end
-- 不一致则直接返回
return 0lua脚本本身并不需要大家花费太多时间去研究只需要知道如何调用大致是什么意思即可所以在笔记中并不会详细的去解释这些lua表达式的含义。
我们的RedisTemplate中可以利用execute方法去执行lua脚本参数对应关系就如下图
代码实现
我们先写入lua这个脚本
-- 比较线程标示与锁中的标示是否一致
if(redis.call(get, KEYS[1]) ARGV[1]) then-- 释放锁 del keyreturn redis.call(del, KEYS[1])
end
return 0然后我们来调用这个脚本 下面是完整代码
public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name name;this.stringRedisTemplate stringRedisTemplate;}private static final String KEY_PREFIX lock:;private static final String ID_PREFIX UUID.randomUUID().toString(true) -;private static final DefaultRedisScriptLong UNLOCK_SCRIPT;static {UNLOCK_SCRIPT new DefaultRedisScript();UNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.lua));UNLOCK_SCRIPT.setResultType(Long.class);}Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标示String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);//这里不能是return success否则 因为public后面的boolean是基本类型而Boolean是引用类型如果直接返回success是一个自动拆箱的过程可能回发生空指针异常}Overridepublic void unlock() {// 调用lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX name),ID_PREFIX Thread.currentThread().getId());}
} 在技术的道路上我们不断探索、不断前行不断面对挑战、不断突破自我。科技的发展改变着世界而我们作为技术人员也在这个过程中书写着自己的篇章。让我们携手并进共同努力开创美好的未来愿我们在科技的征途上不断奋进创造出更加美好、更加智能的明天