当前位置: 首页 > article >正文

redis中使用lua脚本

1、现实问题

  • 1.redis采用单线程架构,可以保证单个命令的原子性,但是无法保证一组命令在高并发场景下的原子性。例如:
    • 在串行场景下:A和B的值肯定都是3
    • 在并发场景下:A和B的值可能在0-6之间。

在这里插入图片描述

  • 2.极限情况下1:则A的结果是0,B的结果是3
    在这里插入图片描述
  • 3.极限情况下2:A和B的结果都是6
    • 如果redis客户端通过lua脚本把3个命令一次性发送给redis服务器,那么这三个指令就不会被其他客户端指令打断。
    • Redis 也保证脚本会以原子性(atomic)的方式执行: 当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。 这和使用 MULTI/ EXEC 包围的事务很类似。
    • 但是MULTI/ EXEC方法来使用事务功能,将一组命令打包执行,无法进行业务逻辑的操作。这期间有某一条命令执行报错(例如给字符串自增),其他的命令还是会执行,并不会回滚。
      在这里插入图片描述

2、lua介绍:

b1.lua概述:
  • 1.Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
  • 2.设计目的:其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
  • 3.Lua 特性:
    • 轻量级:它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
    • 可扩展:Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
    • 其它特性:
      • 支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
      • 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
      • 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
      • 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
b2.lua基本语法
  • 1.对lua脚本感兴趣的同学,请移步到官方教程或者《菜鸟教程》。这里仅以redis中可能会用到的部分语法作介绍。
a = 5               -- 全局变量
local b = 5         -- 局部变量, redis只支持局部变量
a, b = 10, 2*x      -- 等价于       a=10; b=2*x
  • 2.流程控制:
if( 布尔表达式 1)
then
   --[ 在布尔表达式 1 为 true 时执行该语句块 --]
elseif( 布尔表达式 2)
then
   --[ 在布尔表达式 2 为 true 时执行该语句块 --]
else 
   --[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end
b3. redis执行lua脚本 - EVAL指令
  • 1.命令介绍:在redis中需要通过eval命令执行lua脚本。
  • 2.格式:
EVAL script numkeys key [key ...] arg [arg ...]
script:lua脚本字符串,这段Lua脚本不需要(也不应该)定义函数。
numkeys:lua脚本中KEYS数组的大小
key [key ...]:KEYS数组中的元素
arg [arg ...]:ARGV数组中的元素
b3-1.案例1:基本案例:
  • 1.命令:
EVAL "return 10" 0
  • 2.输出:(integer) 10
b3-2.案例2:动态传参
  • 1.命令:
EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 5 10 20 30 40 50 60 70 80 90
# 输出:10 20 60 70
EVAL "if KEYS[1] > ARGV[1] then return 1 else return 0 end" 1 10 20
# 输出:0
EVAL "if KEYS[1] > ARGV[1] then return 1 else return 0 end" 1 20 10
# 输出:1
  • 2.传入了两个参数10和20,KEYS的长度是1,所以KEYS中有一个元素10,剩余的一个20就是ARGV数组的元素。
  • 3.redis.call()中的redis是redis中提供的lua脚本类库,仅在redis环境中可以使用该类库。
b3-3.案例3:执行redis类库方法
  • 1.命令:
set aaa 10  -- 设置一个aaa值为10
EVAL "return redis.call('get', 'aaa')" 0
# 通过return把call方法返回给redis客户端,打印:"10"
  • 2.注意:
    • **脚本里使用的所有键都应该由 KEYS 数组来传递。**但并不是强制性的,代价是这样写出的脚本不能被 Redis 集群所兼容。
b3-4.案例4:给redis类库方法动态传参
  • 1.命令:
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 bbb 20
  • 2.学到这里基本可以应付redis分布式锁所需要的脚本知识了。
b3-5.案例5:pcall函数的使用(了解)
  • 1.命令:
-- 当call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,输出错误信息
EVAL "return redis.call('sets', KEYS[1], ARGV[1]), redis.call('set', KEYS[2], ARGV[2])" 2 bbb ccc 20 30
-- pcall函数不影响后续指令的执行
EVAL "return redis.pcall('sets', KEYS[1], ARGV[1]), redis.pcall('set', KEYS[2], ARGV[2])" 2 bbb ccc 20 30
  • 2.注意:
    • set方法写成了sets**,肯定会报错。
      在这里插入图片描述

3、使用lua保证删除原子性

  • 1.删除LUA脚本:
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
  • 2.代码实现:
public void deduct() {
    String uuid = UUID.randomUUID().toString();
    // 加锁setnx
    while (!this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS)) {
        // 重试:循环
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    try {
        // this.redisTemplate.expire("lock", 3, TimeUnit.SECONDS);
        // 1. 查询库存信息
        String stock = redisTemplate.opsForValue().get("stock").toString();

        // 2. 判断库存是否充足
        if (stock != null && stock.length() != 0) {
            Integer st = Integer.valueOf(stock);
            if (st > 0) {
                // 3.扣减库存
                redisTemplate.opsForValue().set("stock", String.valueOf(--st));
            }
        }
    } finally {
        // 先判断是否自己的锁,再解锁
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
            "then " +
            "   return redis.call('del', KEYS[1]) " +
            "else " +
            "   return 0 " +
            "end";
        this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);
    }
}
  • 3.压力测试,库存量也没有问题,截图略过。。。


http://www.kler.cn/a/294854.html

相关文章:

  • 力扣 653. 两数之和 IV 二叉树/binary-tree two-sum IV
  • 网络安全技术在能源领域的应用
  • docker compose 多个 Dockerfile
  • 【Linux系统编程】第四十五弹---线程互斥:从问题到解决,深入探索互斥量的原理与实现
  • 什么是数据平台?10 个值得了解的大数据平台示例
  • ESP32-S3模组上跑通esp32-camera(12)
  • 在 Linux 和类 Unix 系统中,终端(Terminal)和 Shell
  • zblog自动生成文章插件(百度AI写作配图,图文并茂)
  • SpringBoot教程(十五) | SpringBoot集成RabbitMq(消息丢失、消息重复、消息顺序、消息顺序)
  • 深度学习与大模型第3课:线性回归模型的构建与训练
  • AI对于程序行业的冲击
  • Java中的配置文件
  • 记录一个拖拽组件vue3+ts
  • 汇编:嵌入式软件架构学习资源
  • Python 算法交易实验88 QTV200日常推进-关于继续前进的思考
  • 爆改YOLOv8|利用MobileNetV4 的UIB改进C2f模块-yolov8改进
  • 【0324】Postgres内核 Shared Buffer Access Rules (共享缓冲区访问规则)说明
  • 数据结构代码集训day15(适合考研、自学、期末和专升本)
  • GraphPad Prism 10 for Mac/Win:高效统计分析与精美绘图的科学利器
  • 【Qt】文件对话框QFileDialog
  • 设计模式大全和详解,含Python代码例子
  • 基于“SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI 指数的生态质量评价及拓展应用
  • 编写vue的输入框的自定义指令研究
  • 力扣9.7
  • 最新版 Java 网络编程经典案例:IM 系统、网络拷贝|万字笔记
  • 软件工程-图书管理系统的概要设计