redis中使用lua脚本

news/2024/9/17 7:17:41/ 标签: redis, lua, junit

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方法来使用事务功能,将一组命令打包执行,无法进行业务逻辑的操作。这期间有某一条命令执行报错(例如给字符串自增),其他的命令还是会执行,并不会回滚。
      在这里插入图片描述

lua_13">2、lua介绍:

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

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

  • 1.删除LUA脚本:
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();// 加锁setnxwhile (!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.ppmy.cn/news/1522494.html

相关文章

Qt Widget核心属性

文章目录 前言enabledgeometrywindowTitlewindowIconwindowOpacitycursorfonttoolTipfocusPolicystyleSheet 前言 Qt中的各种控件&#xff0c;都是继承自QWidget类&#xff0c;了解这个类的属性方法之后&#xff0c;后续的控件也通用 enabled enabled描述了一个控件是否处于…

文件包含PHP伪协议利用方法

1.file://协议 使⽤&#xff1a; file:// ⽂件的绝对路径和⽂件名 2.php?cmdfile://D:\phpstudy_pro\WWW\123.txt 2.php://filter协议 ⽤途&#xff1a;常⽤于读取⽂件 / 源码 2.php?cmdphp://filter/readconvert.base64-encode/resource1.php 3.php://input协议 步骤一&…

【C++拓展(一)】后端开发常用的技术栈

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; C拓展 1. 前言2. 语言层面3. 设计模式层面4. 开…

「Qt Widget中文示例指南」如何实现一个系统托盘图标?(一)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 System Tray Icon&a…

基础学习之——Apache Spark

Apache Spark是一种开源的大数据处理框架&#xff0c;它提供了快速、通用和可扩展的大数据分析和处理功能。Spark可以在大规模数据集上进行高速计算&#xff0c;并且可以与多种数据源和工具进行集成。 Spark的基本概念包括&#xff1a; 弹性分布式数据集&#xff08;Resilient…

【LeetCode 121】买卖股票的最佳时机

1. 题目 2. 分析 如果当前的价格比之前买入的价格要低&#xff0c;那么我们就“逢低买入”&#xff0c;更新买入的价格&#xff0c;因为在此后的每一天里&#xff0c;当前的价格与之前的买入价格相比是更优解。 如果读者对单调队列有接触&#xff0c;可以看到这一步的核心思想…

iOS P8证书推送测试

最近在配合服务端人员调试相关的 APNS auth key 推送的问题&#xff0c;相比于苹果的P12证书的推送&#xff0c;P8证书的推送显得方便很多&#xff0c;P8的优势在于简单&#xff0c;安全 容易生成 最重要的是不会过期。 现在我们来看下测试具体流程&#xff1a; 方法一 地址…

掌握Hive函数[1]:从基础到高级应用

目录 函数简介 单行函数 算术运算函数 数值函数 字符串函数 日期函数 流程控制函数 集合函数 案例演示 函数简介 Hive将常用的逻辑封装成函数供用户使用&#xff0c;类似于Java中的函数。这样做的好处是可以避免用户反复编写相同的逻辑代码&#xff0c;可以直接调用这些函数。…

2024 RustChinaConf 赞助商介绍

2024 RustChinaConf 得到了行业各界的广泛支持&#xff0c;在此向以下赞助商表示感谢&#xff01; 非凸科技 非凸科技是一家全栈使用Rust的金融科技公司&#xff0c;致力于为券商、私募、公募等金融机构及个人投资者提供一站式数智交易领域服务解决方案。作为本次大会的钻石赞助…

jmeter之ForEach控制器使用

ForEach控制器作用&#xff1a; 一般和用户自定义变量或者正则表达式提取器配合使用&#xff0c;读取返回结果中一系列相关的变量值&#xff0c;该控制器下的取样器都会被执行一次或多次&#xff0c;每次读取不同的变量值(类似python当中的for语句,用来遍历操作) 本节代码已上…

币安/欧易合约对冲APP系统开发

币安合约对冲系统开发是一个复杂且专业的过程&#xff0c;它涉及到多个方面的技术和策略。以下是对币安合约对冲系统开发的一个概述&#xff1a; 一、系统概述 币安合约对冲系统是一种利用币安交易所提供的合约交易功能&#xff0c;通过同时建立多头和空头仓位来减少或消除市…

hutool 集合相关交集、差集

开发过程中会遇到集合之间的对比之类的需求&#xff0c;之前经常会自己写个工具类来实现&#xff0c;目前hutool可以帮助我们解决很多问题&#xff0c;接下来我们就来实践下。 相关jar包 <dependency><groupId>cn.hutool</groupId><artifactId>hutool…

Python办公自动化案例

文章目录 系列文章办公自动化案例案例1&#xff1a;批量重命名文件案例2&#xff1a;Excel数据自动筛选案例3&#xff1a;PDF文件合并案例4&#xff1a;批量发送电子邮件案例5&#xff1a;日程安排提醒案例6&#xff1a;CSV文件数据统计案例7&#xff1a;Word文档生成案例8&…

Day18_0.1基础学习MATLAB学习小技巧总结(18)——MATLAB绘图篇(1)

利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍&#xff0c;为了在这个过程中加深印象&#xff0c;也为了能够有所足迹&#xff0c;我会把自己的学习总结发在专栏中&#xff0c;以便学习交流。 参考书目&#xff1a;《MATLAB基础教程 (第三版) (薛山)》 之前的章节都是…

前端vue项目服务器部署(docker)

前端vue项目服务器部署(docker) 步骤 1: 导入 Nginx Docker 镜像 1、上传 Nginx Docker 镜像 将你的nginx-alpine.tar包上传到服务器上。假设路径为 /var/v3-admin-vite/nginx-alpine.tar。 scp -r "C:\Users\86184\Desktop\v3-admin-vite" root110.40.179.182:/…

第十五届蓝桥杯 Python 省赛题目及解析

第十五届蓝桥杯 Python 省赛题目及解析 选择题 1. 运行下面程序&#xff0c;输出的结果是&#xff08;&#xff09;。 s ‘py’ print(‘t’.join(s)) A、tpyB、ptyC、tptyD、tptyt 正确答案&#xff1a;B 答案解析&#xff1a; join() 方法是字符串的一个方法&#xff0c;…

数据分析 设备一个月以来的参数变化

1数据预处理 import pandas as pd import glob import os# 读取所有CSV文件并合并为一个DataFrame path path_to_your_csv_files/ # CSV文件的文件夹路径 all_files glob.glob(os.path.join(path, "*.csv"))df_list [] for file in all_files:df pd.read_csv(f…

FPGA进阶教程16 同一块FPGA的两个网口实现arp自通信

本项目使用同一个FPGA的两个网口进行千兆以太网的arp自动绑定 目的是为了以后实现两个FPGA之间进行以太网数据的交互 起因:最近公司画了一块板子,上面有两个网口,市面上常见的项目都是将FPGA与PC端实现arp的自动绑定和UDP数据的回环,很少实现两块FPGA之间的以太网数据通信…

网络学习-eNSP配置ACL

AR1路由器配置 <Huawei>system-view Enter system view, return user view with CtrlZ. [Huawei]undo info-center enable Info: Information center is disabled. [Huawei]interface gigabitethernet 0/0/0 [Huawei-GigabitEthernet0/0/0]ip address 192.168.2.254 24 …

chunk-vendors.js 文件过大导致页面加载缓慢解决方案

1、路由懒加载 在 Webpack 中&#xff0c;我们可以使用动态 import语法来定义代码分块点 (split point)&#xff1a; import(./Foo.vue) // 返回 Promise如果您使用的是 Babel&#xff0c;你将需要添加 syntax-dynamic-import 插件&#xff0c;才能使 Babel 可以正确地解析语…