美文网首页
2022-05-02_Lua脚本操作redis锁学习笔记

2022-05-02_Lua脚本操作redis锁学习笔记

作者: kikop | 来源:发表于2022-05-02 16:48 被阅读0次

20220502_Lua脚本操作redis锁学习笔记.md

1概述

释放锁要用 lua 脚本,把检查锁是不是本线程持有的逻辑和删除锁逻辑这连个操作放到一个 lua 脚本中,保证原子性,利用redis的单线程特性, 防止高并发时误删其他线程写入的锁。

考虑这样的时序:

  1. 如持有锁的线程A,过期了,但业务逻辑还在执行,
  2. 此时别的线程B获取了锁
  3. 原先持有锁的线程A业务逻辑执行完成,随手删除了锁(线程B岂不是一脸懵逼)。

所有我们要确保锁的删除合法有效(如删除判断持有者ID、获取基于redisLua脚本保持原子性)。

1.1Lua脚本的独占性

当lua脚本在执行的时候,不会有其他脚本和命令同时执行,这种语义类似于 MULTI/EXEC。

从别的客户端的视角来看,一个lua脚本要么不可见,要么已经执行完。

2代码示例

2.1pom依赖

  <!--1.jedis for redis.clients-->
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis.version}</version>
        </dependency>

2.2MyJedisLuaDeleteLockUtil

package com.kikop.util;


import com.google.common.collect.Lists;
import com.google.common.io.Files;
import redis.clients.jedis.Jedis;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;

/**
 * @author kikop
 * @version 1.0
 * @project myluascriptdemo
 * @file MyJedisLuaDeleteLockUtil
 * @desc Redislua脚本
 * @date 2022/5/2
 * @time 9:00
 * @by IDE IntelliJ IDEA
 */
public class MyJedisLuaDeleteLockUtil {


    // 初始化一次
    private static final Jedis jedis;

    static {
        try {
            jedis = new Jedis("localhost", 6379);
        } catch (Exception ex) {
            throw new RuntimeException("constructor jedis instance failed!");
        }
    }


    // Lua脚本

    // 获取key
    // 如果 key 不存在那么返回特殊值 nil--> not nil==true
    // 返回值:bool
    private static final String GET_LOCK_LUA_SCRIPT =
            "local lockClientId = redis.call('GET', KEYS[1])\n" +
                    "if lockClientId == ARGV[1] then\n" +  // Keys的值是自己,则重置延迟过期时间
                    "  redis.call('PEXPIRE', KEYS[1], ARGV[2])\n" +
                    "  return true\n" +
                    "elseif not lockClientId then\n" + // 客户ID不为空,则设置key
                    "  redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])\n" +
                    "  return true\n" +
                    "end\n" +
                    "return false"; // 锁已经被别的线程占用


    // 删除key
    // 脚本的删除,返回被删除key的数量
    // 返回值:int
    private static final String DELETE_LOCK_LUA_SCRIPT =
            "local lockClientId = redis.call('GET', KEYS[1])\n" +
                    "if lockClientId == ARGV[1] then\n" +
                    "  return redis.call('del',KEYS[1]) \n" +
                    "elseif not lockClientId then\n" +
                    "  return 0\n" +
                    "end\n" +
                    "return 0";


    /**
     * 通过Lua脚本获取锁
     *
     * @param key
     * @param clientId
     * @return
     */
    public static boolean acquireLockByLua(String key, String clientId, long expireMs) {

        // keys:
        // key[1]:key

        // args:
        // argv[1]:clientId
        // argv[2]:expireMS

        Object eval = jedis.eval(GET_LOCK_LUA_SCRIPT, // script
                Lists.newArrayList(key), // keys
                Lists.newArrayList(clientId, String.valueOf(expireMs))); // args
        if (null == eval) {
            return false;
        }
        long acquiereResult = (long) eval;
        if (acquiereResult == 1) {
            return true;
        }
        return false;
    }

    /**
     * 通过Lua脚本删除锁
     *
     * @param key
     * @param clientId
     * @return
     */
    public static boolean deleteLockByLua(String key, String clientId) {

        // 面试重点,
        // redis 分布式锁的面试题的时候,都会注意说
        // “释放锁要用 lua 脚本,把检查锁是不是本线程持有和删除锁放到一个 lua 脚本中,
        // 防止高并发时误删其他线程写入的锁”。

        // keys:
        // key[1]:key

        // args:
        // argv[1]:clientId
        long deleteResult = (long) jedis.eval(DELETE_LOCK_LUA_SCRIPT,
                Collections.singletonList(key),
                Collections.singletonList(clientId));
        if (deleteResult >= 1) {
            return true;
        }
        return false;
    }


}

2.3测试

package com.kikop;


import com.kikop.util.MyJedisLuaDeleteLockUtil;
import com.kikop.util.MyJedisLuaTimeWindowLimiterUtil;

import java.io.File;
import java.io.IOException;
import java.util.UUID;


/**
 * @author kikop
 * @version 1.0
 * @project myluascriptdemo
 * @file MyNormalLuaScriptApplication
 * @desc
 * @date 2022/5/2
 * @time 9:00
 * @by IDE IntelliJ IDEA
 */
public class MyNormalLuaScriptApplication {

    private static void testClassPath() {
        String classPathFile = MyNormalLuaScriptApplication.class.getResource("/").getFile();
        System.out.println(classPathFile);

        String classPathFile2 = MyNormalLuaScriptApplication.class.getResource("/mytimewindowlimit.lua").getFile();

        File file = new File(classPathFile2);
        System.out.println(file.getAbsolutePath());
    }

    public static void main(String[] args) throws IOException {


        String key = "my:lock:goods";
        String clientId = UUID.randomUUID().toString();
        long expireMs = 2 * 60000L; // 2分钟

        boolean acquireLockByLuaResult = testGetKey(key, clientId, expireMs);
        if (acquireLockByLuaResult) {
            System.out.println("获取锁成功");
        } else {
            System.out.println("获取锁失败");
        }
    }

    /**
     * 获取锁
     *
     * @param key
     * @param clientId
     * @param expireMs
     * @return
     */
    private static boolean testGetKey(String key, String clientId, long expireMs) {
        boolean acquireLockByLuaResult = MyJedisLuaDeleteLockUtil.acquireLockByLua(key, clientId, expireMs);
        return acquireLockByLuaResult;
    }

    /**
     * 删除锁
     *
     * @param key
     * @param clientId
     * @return
     */
    private static boolean testRemoveKey(String key, String clientId) {
        boolean deleteLockByLuaResult = MyJedisLuaDeleteLockUtil.deleteLockByLua(key, clientId);
        return deleteLockByLuaResult;
    }

    /**
     * 由持有锁的线程删除锁
     */
    private static void testLockOperOk() {
        String key = "my:lock:goods";
        String clientId = UUID.randomUUID().toString();
        long expireMs = 2 * 60000L; // 2分钟

        boolean acquireLockByLuaResult = testGetKey(key, clientId, expireMs);
        if (acquireLockByLuaResult) {
            boolean deleteLockByLuaResult = testRemoveKey(key, clientId);
            System.out.println("获取锁成功");
            if (deleteLockByLuaResult) {
                System.out.println("删除锁成功");
            } else {
                System.out.println("删除锁失败");
            }
        } else {
            System.out.println("获取锁失败");
        }
    }

    /**
     * 由非持有锁的线程删除锁
     */
    private static void testLockOperFail() {
        String key = "my:lock:goods";
        String clientId = UUID.randomUUID().toString();
        long expireMs = 2 * 60000L; // 2分钟

        boolean acquireLockByLuaResult = testGetKey(key, clientId, expireMs);
        if (acquireLockByLuaResult) {
            String newkey = String.format("%s001", key);
            boolean deleteLockByLuaResult = testRemoveKey(newkey, clientId);
            if (deleteLockByLuaResult) {
                System.out.println("删除锁成功");
            } else {
                System.out.println("删除锁失败");
            }
        } else {
            System.out.println("获取锁失败");
        }
    }
}

[图片上传失败...(image-7043be-1651481526512)]

[图片上传失败...(image-862d06-1651481526513)]

相关文章

  • 2022-05-02_Lua脚本操作redis锁学习笔记

    20220502_Lua脚本操作redis锁学习笔记.md 1概述 释放锁要用 lua 脚本,把检查锁是不是本线程...

  • 分布式锁之redis-lua脚本

    目录 redis分布式锁,Lua,Lua脚本,lua redis,redis lua 分布式锁,redis set...

  • 2021-08-06_lua脚本学习笔记

    20210806_lua脚本学习笔记 1概述 1.1为什么lua脚本具有原子性 Redis保证以原子方式执行脚本,...

  • mac上安装lua

    一、背景 最近在操作redis的时候,有些时候是需要原子操作的,而redis中支持lua脚本,因此为了以后学习lu...

  • redis 的lua脚本

    参考redis写分布式竞争一个锁的时候,碰到了lua脚本的问题,引出学习一下参考文档:redis.iohttps:...

  • 罗列下必须掌握的互联网应用技术

    ZooKeeper 线程同步锁 redis monggoDB springboot dubbo shell脚本 L...

  • 9. Redisson源码剖析-读写锁

    一、读锁 读写锁的意义: 1, redis分布式锁,主要就是在理解他里面的lua脚本的逻辑,逻辑全部都在lua脚本...

  • 分布式锁

    Redis实现 使用Redis集群实现分布式锁。使用 Redisson 框架,基于LUA脚本去实现 Zookepp...

  • 并发编程之redis分布式锁

    基于redis实现的锁机制,主要是依赖redis自身的原子操作(因为redis是单线程)。 原子操作命令:SET ...

  • Redis学习笔记-Lua脚本

    前言 Redis在2.6版推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。 在Lua脚本中可...

网友评论

      本文标题:2022-05-02_Lua脚本操作redis锁学习笔记

      本文链接:https://www.haomeiwen.com/subject/nflvyrtx.html