• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Redis的分布式锁问题九Redis + Lua 脚本实现分布式锁

武飞扬头像
金鳞踏雨
帮助1

Redis的分布式锁问题(九)Redis Lua 脚本实现分布式锁

上集回顾

Lua的简单介绍 

redis调用函数  

set name jack 

set name Rose,再执行get name 

redis的 EVAL 命令 

Lua脚本解决unLock业务流程 

代码实现 

unLock.lua 

RedisTemplate调用Lua脚本的API 

Lua 解决unLock问题


Redis的分布式锁问题(九)Redis Lua 脚本实现分布式锁

上集回顾

学新通

但是这仍然不是最佳的实现方案,它在极端的情况下还是会发生问题!

  1.  
    public void unlock() {
  2.  
    // 获取线程标示
  3.  
    String threadId = ID_PREFIX Thread.currentThread().getId();
  4.  
    // 获取锁中的标示
  5.  
    String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX name);
  6.  
    // 判断标示是否一致
  7.  
    if(threadId.equals(id)) {
  8.  
    // 释放锁
  9.  
    stringRedisTemplate.delete(KEY_PREFIX name);
  10.  
    }
  11.  
    }

如上述代码,如果“判断锁标识”和“释放锁”,之间发生了阻塞呢?JVM触发FULL GC

那么之前一节所讲的“锁误删”就有可能发生!!!

所以,我们的解决思路是要保证这两个操作的原子性

我们可以使用Redis的事务 乐观锁来解决这个问题,但是这样子做非常复杂!这里我们使用 Lua 脚本来实现分布式锁

Lua的简单介绍 

Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性 

Lua 教程 | 菜鸟教程 (runoob.com)学新通https://www.runoob.com/lua/lua-tutorial.html

  1.  
    if(0)
  2.  
    then
  3.  
    print("0 为 true")
  4.  
    end

redis调用函数  

学新通

set name jack 

学新通

set name Rose,再执行get name 

学新通

redis的 EVAL 命令 

如果脚本中的keyvalue不想写死,可以作为参数传递。key类型参数会放入KEYS数组,其它参数会放入ARGV数组,在脚本中可以从KEYSARGV数组获取这些参数:

在Lua中,数组的下标是从1开始的!!!

学新通

Lua脚本解决unLock业务流程 

  1. 获取锁中的线程标示
  2. 判断是否与指定的标示(当前线程标示)一致
  3. 如果一致则释放锁(删除)
  4. 如果不一致则什么都不做

学新通

学新通

代码实现 

unLock.lua 

  1.  
    -- 获取锁标识,是否与当前线程一致?
  2.  
    if(redis.call('get', KEYS[1]) == ARGV[1]) then
  3.  
    -- 一致,删除
  4.  
    return redis.call('del', KEYS[1])
  5.  
    end
  6.  
    -- 不一致,直接返回
  7.  
    return 0

RedisTemplate调用Lua脚本的API 

学新通

Lua 解决unLock问题

  1.  
    /**
  2.  
    * redis的分布式锁
  3.  
    * 实现ILock接口
  4.  
    */
  5.  
    public class SimpleRedisLock implements ILock {
  6.  
     
  7.  
    // 不同的业务有不同的锁名称
  8.  
    private String name;
  9.  
    private StringRedisTemplate stringRedisTemplate;
  10.  
    private static final String KEY_PREFIX = "lock:";
  11.  
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) "-";
  12.  
    // DefaultRedisScript,
  13.  
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
  14.  
     
  15.  
    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
  16.  
    this.name = name;
  17.  
    this.stringRedisTemplate = stringRedisTemplate;
  18.  
    }
  19.  
     
  20.  
    // 初始化 UNLOCK_SCRIPT,用静态代码块的方式,一加载SimpleRedisLock有会加载unlock.lua
  21.  
    // 避免每次调unLock() 才去加载,提升性能!!!
  22.  
    static {
  23.  
    UNLOCK_SCRIPT = new DefaultRedisScript<>();
  24.  
    // setLocation() 设置脚本位置
  25.  
    UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
  26.  
    // 返回值类型
  27.  
    UNLOCK_SCRIPT.setResultType(Long.class);
  28.  
    }
  29.  
     
  30.  
    /**
  31.  
    * 获取锁
  32.  
    */
  33.  
    @Override
  34.  
    public boolean tryLock(long timeoutSec) {
  35.  
    // 获取线程标示
  36.  
    String threadId = ID_PREFIX Thread.currentThread().getId();
  37.  
    // 获取锁
  38.  
    // set lock thread1 nx ex 10
  39.  
    // nx : setIfAbsent(如果不存在) , ex : timeoutSec(秒)
  40.  
    Boolean success = stringRedisTemplate.opsForValue()
  41.  
    .setIfAbsent(KEY_PREFIX name, threadId, timeoutSec, TimeUnit.SECONDS);
  42.  
    // 自动拆箱(Boolean -> boolean)!!!可能有风险
  43.  
    return Boolean.TRUE.equals(success);
  44.  
    }
  45.  
     
  46.  
    /**
  47.  
    * 解决判断(锁标识、释放锁)这两个动作,之间产生阻塞!!!
  48.  
    * JVM的 FULL GC
  49.  
    * 要让这两个动作具有原子性
  50.  
    */
  51.  
    @Override
  52.  
    public void unlock() {
  53.  
    // 调用lua脚本
  54.  
    stringRedisTemplate.execute(
  55.  
    UNLOCK_SCRIPT,
  56.  
    Collections.singletonList(KEY_PREFIX name),
  57.  
    ID_PREFIX Thread.currentThread().getId());
  58.  
    }
  59.  
    }
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgafjef
系列文章
更多 icon
同类精品
更多 icon
继续加载