lua整合redis

文章目录

  • lua
    • 基础只适合
    • lua连接操作redis
      • 1.下载lua依赖
      • 2.导包,连接
      • 3.常用的命令
        • 1.set,get,push命令
      • 2.自增
      • 管道命令
      • 命令集合
      • 4.使用redis操作lua
        • 1.实现秒杀功能
          • synchronized关键字
        • 分布式锁

lua

基础只适合

1.编译

-- 编译
luac a.lua
-- 运行
lua a.lua

2.命名规范

-- 多行注释
'--[[]]--'
-- 单行注释
'--'
--定义类型的不用类型修饰,每行代码结束的时候用不用分号都行,变量类型可以随意改变,区分大小写

变量类型

默认创建的都是全局变量

局部变量用local关键字

3.基本数据类型

nil,boolean,number,string,function,userdata,thread,table
--type函数返回类型
print(type(args))
--[[
number:包含int,long double,float等数字类型    
]]--

lua连接操作redis

先保证自己已经有lua的依赖

1.下载lua依赖

#在终端执行,该命令下载此依赖
luarocks install redis-lua

2.导包,连接

local redis = require("redis")
--连接 host是你部署redis的ip,port是你的redis端口
local client = redis.connect(host,port);
-- 如果有密码使用密码进行连接,password填入你的redis密码
local authResult = client:auth(password)
-- 这里的authResult会返回通过该密码是不是认证成功
--检查是否连接成功,如果response为true则连接成功
local response = client:ping()

3.常用的命令

1.set,get,push命令
--set命令,这里的和redis的相对应
client:set(key,value)
--get 获取的值就是刚才传入的value
local value = client:get(key)
--向名称为key的list中压入一个元素v
client:rpush(key,value)
-- 从名称为key的list中弹出一个元素
local v = client:lpop(key)
--相当于队列这个概念,先进先出
--设置key的过期时间
client:expire("lxy",10000)

2.自增

--进行自增,使key自增1
client:incr(key)
--增加指定数量
client:set("***",1)
client:incrby("***",10)
local v = client:get("***")
print(v) --v = 11

管道命令

--将多条命令放到一个队列里面执行
local replies = client:pipeline(function(p)p:incrby('counter', 10)p:incrby('counter', 30)p:get('counter')
end)

命令集合

punsubscribe
expireat
info
slowlog
rpush
time
psetex
monitor
zincrby
lrange
psubscribe
zrevrangebyscore
srandmember
zscore
flushdb
mset
ping
zrank
move
lastsave
bgsave
save
slaveof
client
hgetall
hmset
exists
lpushx
sadd
getrange
hvals
hmget
zcard
renamenx
sunionstore
zrange
pttl
rpushx
zcount
lindex
substr
publish
rpoplpush
scard
keys
pexpireat
brpop
subscribe
zinterstore
unwatch
sinterstore
dbsize
zunionstore
setnx
getset
decrby
exec
config
lpop
sdiffstore
sunion
incrbyfloat
rename
select
sdiff
discard
echo
spop
setbit
multi
del
hkeys
getbit
hlen
strlen
hexists
decr
hdel
ttl
append
hincrbyfloat
hincrby
bgrewriteaof
set
zrevrank
brpoplpush
setex
zrangebyscore
hsetnx
sinter
blpop
unsubscribe
incr
zremrangebyrank
zremrangebyscore
get
flushall
randomkey
rpop
eval
zrem
incrby
zadd
srem
smembers
setrange
sort
evalsha
lrem
watch
sismember
smove
pexpire
type
script
linsert
hset
expire
zrevrange
lset
ltrim
llen
lpush
hget
persist
msetnx
mget
auth

4.使用redis操作lua

1.实现秒杀功能

因为秒杀这整个过程不是原子性的所以可以用lua脚本优化

模拟操作

//先将商品id为1库存设置为1000
set good:1 1000@RestController
public class controller {@Autowiredprivate StringRedisTemplate redisTemplate;@GetMapping("/test")private String test(String idx){//得到对应的商品keyString key="goods:"+idx;//获取商品剩余数量Integer amount = Integer.parseInt(redisTemplate.opsForValue().get(key));//如果又剩余就购买成功对应数量-1if(amount >1){redisTemplate.opsForValue().set(key, String.valueOf(--amount));return "success";}else{//否则这返回失败return "fault";}}

上面的代码在单线程下不会出现问题,但是在多线程的时候如果同时访问这个接口,因为这个操作不是原子的所以可能导致
用户1和用户2同时购买他们获取的amount都为1000 用户1买完后填入999用户2买完后填入999这就导致商品数量和购买数量不一致
这时我们就可以采用lua脚本来模拟上述过程,

synchronized关键字
@RestController
public class controller {@Autowiredprivate StringRedisTemplate redisTemplate;@GetMapping("/test")private String test(@RequestParam("idx") String idx) {synchronized (this) {String key = "good:" + idx;String s = redisTemplate.opsForValue().get(key);System.out.println("s:" + s);Integer amount = Integer.parseInt(s);if (amount > 1) {redisTemplate.opsForValue().set(key, String.valueOf(--amount));return "success";} else {return "fault";}}}
}

使用synchronized关键字锁住上面代码块,这样写的效果就是同一时间只能有一个对象能执行该代码块,但是还是存在问题,如果采用集群模式,那么每台服务器都有自己的jvm这时上锁就会失败,因为锁的信息都存各自的jvm里面

分布式锁

当有多个线程要访问某一资源,为了协调多个线程的同步访问,此时就需要使用分布式锁了

具体的思想就是让线程在访问共享变量之前先获取到一个令牌token,只有具有了令牌的线程才可以访问共享资源,这个令牌就是通过各种方式实现的分布式锁,这种分布式锁是一种互斥锁,就是只要有线程抢到了锁,那么其他线程只能等待,知道锁被释放或等待超时

redis实现

利用redis中的setnx命令实现,setnx是如果存在的话就不会创建成功,不存在才能创建成功下面是对应的逻辑代码

@RestController
public class controller {private final String lockKey ="lock";@Autowiredprivate StringRedisTemplate redisTemplate;@GetMapping("/test")private String test(@RequestParam("idx") String idx) {try {//获取锁Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "***");if(!lock){return "未抢占到锁";}String key = "good:" + idx;String s = redisTemplate.opsForValue().get(key);System.out.println("s:" + s);Integer amount = Integer.parseInt(s);if (amount > 1) {redisTemplate.opsForValue().set(key, String.valueOf(--amount));return "success";} else {return "fault";}}finally {//释放锁redisTemplate.delete(lockKey);}}}

上面使用setnx来当锁,但是仍然是存在问题的,比如在finally语句执行前服务器直接宕机了,那么这个锁就得不到释放,就没人能获取锁了,

解决方案

1.使用超时剔除

@RestController
public class controller {private final String lockKey ="lock";@Autowiredprivate StringRedisTemplate redisTemplate;@GetMapping("/test")private String test(@RequestParam("idx") String idx) {try {//获取锁,设置过期时间Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "***", Duration.ofSeconds(5));if(!lock){return "未抢占到锁";}String key = "good:" + idx;String s = redisTemplate.opsForValue().get(key);System.out.println("s:" + s);Integer amount = Integer.parseInt(s);if (amount > 1) {redisTemplate.opsForValue().set(key, String.valueOf(--amount));return "success";} else {return "fault";}}finally {//释放锁redisTemplate.delete(lockKey);}}}

上面的代码还是存在问题,如果还没到获取商品的数量的时候key就被剔除了,那么下个线程获取到锁后,这个线程到了释放阶段,这时就会把其他线程的锁释放掉.

解决方案

//将lock的key设置成唯一标识,在释放的时候判断是不是自己的锁如果是自己的才进行释放
@RestController
public class controller {private final String lockKey ="lock";@Autowiredprivate StringRedisTemplate redisTemplate;@GetMapping("/test")private String test(@RequestParam("idx") String idx) {String uuid = UUID.randomUUID().toString();try {//获取锁Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, Duration.ofSeconds(5));if(!lock){return "未抢占到锁";}String key = "good:" + idx;String s = redisTemplate.opsForValue().get(key);System.out.println("s:" + s);Integer amount = Integer.parseInt(s);if (amount > 1) {redisTemplate.opsForValue().set(key, String.valueOf(--amount));return "success";} else {return "fault";}}finally {//判断是不是自己的锁if(uuid.equals(redisTemplate.opsForValue().get(lockKey))) {//释放锁redisTemplate.delete(lockKey);}}}
}

上面的解决方案还是会存在问题,如果进入了if判断里面,还没有释放锁的哪一步,这时有个线程加锁了,那么接下来释放锁还是会释放其他线程的锁.

finally {//判断是不是自己的锁if(uuid.equals(redisTemplate.opsForValue().get(lockKey))) {//到了这一步,这时有现成创建了锁//释放锁,这时就会释放其他线程的锁redisTemplate.delete(lockKey);}}

关键的问题就是上面的代码不具有原子性,我们可以通过事务或者lua脚本

我们这里讲使用lua脚本

//redis执行lua的命令:eval script numkeys [key[key...]][args[args...]]
//script:要执行的lua脚本
//numkeys:key的数量
//[key[key...]]:要操作的key
//[args[args..]]:传入的参数  
eg:eval "return KEYS[2]" 3 name age depart aaa bbb ccc
//执行结果为age

接下来我们修改一下逻辑,先编写一个简单的lua脚本

--获取第一个key,也就是lock的key
local lock = KEYS[1]
-- 获取第一个参数用户的uuid
local uuid = ARGV[1]
--判断是否相等
-- redis.call("要执行的redis命令","对应的参数")
-- 获取locK对应的value
local v = redis.call("get",lock)
--  如果是该用户的锁就进行解锁
if v == uuid then--进行删除num接受收影响的行数local num =  redis.call("del",lock)return num
end
return 0

上面的脚本就是实现我们上面的释放锁的逻辑下面来编写代码逻辑

我这里放到了这个路径

image-20240421101851090

@RestController
public class controller {private final String lockKey ="lock";@Autowiredprivate StringRedisTemplate redisTemplate;@GetMapping("/test")private String test(@RequestParam("idx") String idx) {String uuid = UUID.randomUUID().toString();try {//获取锁Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, Duration.ofSeconds(5));if(!lock){return "未抢占到锁";}String key = "good:" + idx;String s = redisTemplate.opsForValue().get(key);System.out.println("s:" + s);Integer amount = Integer.parseInt(s);if (amount > 1) {redisTemplate.opsForValue().set(key, String.valueOf(--amount));return "success";} else {return "fault";}}finally {//这里就是加载对应的lua脚本DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();//这里填写你的脚本地址redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/"+"Test.lua")));redisScript.setResultType(Long.type);String result = "";String argsone = "none";//logger.error("开始执行lua");try {//第一个参数是对应的脚本,第二个是对应的key,第三个参数是对应的argsresult = redisTemplate.execute(redisScript, Arrays.asList(lockKey),uuid);} catch (Exception e) {// logger.error("发生异常",e);}}}}

上面的代码代码就能实现我们想要的结果,还有其他问题,锁提前到期的问题,锁到期小于业务执行时间,

可以写一个lua代码实现库存减少

local key = KEYS[1]
local res = redis.call("exists",key)
if res == false thenreturn 0;
end
local amount = tonumber(redis.call("get",key))
if amount >0 thenres = redis.call("decr",key)return res
end
return 0

我们可以封装一个工具类来执行lua脚本

package com.lxy.lualearn.demos.web;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;import java.util.Collections;
import java.util.List;@Component
public class LuaUtils {@Autowiredprivate StringRedisTemplate redisTemplate;public boolean exect(String filePath, List<String> keys,String args){DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(filePath)));redisScript.setResultType(Long.TYPE);Long result ;String argsone = "none";//logger.error("开始执行lua");try {result = redisTemplate.execute(redisScript, keys,args);return  result>0;} catch (Exception e) {// logger.error("发生异常",e);e.printStackTrace();}return false;}
}

执行一下

@RestController
public class controller {private final String lockKey ="lock";@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate LuaUtils luaUtils;@GetMapping("/test")private String test(@RequestParam("idx") String idx) {boolean exect = luaUtils.exect("/lua/Test1.lua", Collections.singletonList("goods:" + idx), "");return exect ? "success" : "error";}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/1290.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring-datasource事务管理-手动请求事务回滚

什么场景下会触发&#xff1f; 在 Spring 中&#xff0c;调用 setRollbackOnly() 方法会将当前事务标记为 rollback-only&#xff0c;表示事务只能回滚&#xff0c;不能提交。这种情况通常发生在以下情景中&#xff1a; 业务逻辑判断&#xff1a;在方法中根据某些业务逻辑的判…

Java——数组

数组是一块连续的内存&#xff0c;用来存储相同类型的数据 一、数组的定义 数组的创建 T[] 数组名 new T[N]; T&#xff1a;表示数组中存放元素的类型 T[]&#xff1a;表示数组的类型 N&#xff1a;表示数组的长度 数组的初始化 数组的初始化主要分为动态初始化和静态初始…

哈尔滨等保测评综述

​ 定级是网络安全等级保护的首要环节和关键环节&#xff0c;可以梳理各行业、各部门、各单位的等级保护对象类型、重要程度和数量等基本信息&#xff0c;确定分级保护的重点。定级不准&#xff0c;系统备案、建设、整改、等级测评等后续工作都会失去意义&#xff0c;等级…

ElasticSearch查询时修改打分

原生的ES打分基于BM25算法&#xff0c;相比于TF-IDF已经有了较大的改进&#xff0c;但是在实际场景中往往最终的排序效果还是需要进行调整。由于直接修改索引的权重往往代价较大&#xff0c;比较经济的方式还是在查询时即时修改得分以实现排序控制。 注&#xff1a;案例测试数据…

开源项目实现简单实用的股票回测

1 引言 之前&#xff0c;尝试做股票工具一直想做的大而全&#xff0c;试图抓取长期的各个维度数据&#xff0c;然后统计或者训练模型。想把每个细节做到完美&#xff0c;结果却陷入了细节之中&#xff0c;最后烂尾了。 最近&#xff0c;听到大家分享了一些关于深度学习、时序…

Vue+OpenLayers7入门专栏目录,OpenLayers7中文文档,OpenLayers7中文手册api,OpenLayers7中文教程

返回入门到实战汇总目录&#xff1a;汇总目录 前言 本篇作为《VueOpenLayers7入门教程》所有文章的二合一汇总目录&#xff0c;方便查找。 本专栏源码是由OpenLayers7.x版本结合Vue框架编写。本专栏基本上每章都有详细的源代码和运行示例以供参考&#xff0c;且保证每章代码都…

微软如何打造数字零售力航母系列科普01 --- Azure顾问(AZURE Advisor)简介

Azure顾问&#xff08;AZURE Advisor&#xff09;简介 目录 一、什么是AZURE顾问&#xff08;AZURE Advisor&#xff09;&#xff1f; 二、常见问题 三、接下来的步骤 一、什么是AZURE顾问&#xff1f; AZURE顾问是一种数字云助手&#xff0c;可帮助您遵循最佳实践来优化Az…

openai whisper 语音转文字尝鲜

最近大模型很火&#xff0c;也试试搭一下&#xff0c;这个是openai 开源的whisper&#xff0c;用来语音转文字。 安装 按照此文档安装&#xff0c;个人习惯先使用第一个pip命令安装&#xff0c;然后再用第二个安装剩下的依赖&#xff08;主要是tiktoken&#xff09; https:/…

Docker构建Golang项目常见问题

Docker构建Golang项目常见问题 1 Dockerfile1.1 dockerfile报错&#xff1a;failed to read expected number of bytes: unexpected EOF1.2 go mod tidy: go.mod file indicates go 1.21, but maximum supported version is 1.171.3 是否指定启动文件问题 2 构建及部署 1 Docke…

【Python系列】python 如何打印带时间的日志

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

音视频封装格式解析(1)——H264格式简析,I/P/B帧是什么?H264压缩原理

文章目录 1. H264编码参数2. H264编码原理2.1 压缩原理2.2 编码结构解析 3. NALU结构4. H264 annexb模式5. 补充说明5.1 I帧5.2 P帧5.3 B帧 1. H264编码参数 视频质量和⽹络带宽占⽤是相⽭盾的。通常情况下&#xff0c;视频流占⽤的带宽越⾼则视频质量也越⾼&#xff0c;需要的…

用友NC Cloud saveImageServlet/doPost接口存在任意文件上传漏洞

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 用友NC Cloud 是基于云计算技术的企业管理软件。它提…

网络安全产品---数据库防火墙/审计

数据库防火墙 防火墙的类型繁多&#xff0c;即使下一代防火墙或者说AI防火墙集成功能再多&#xff0c;我觉得waf与数据库防火墙也有其无法被替代的理由&#xff0c;以此记录我对数据库防火墙的理解 what 数据库防火墙是基于数据库协议分析与访问行为控制的数据库安全防护产品…

IOS 32位调试环境搭建

一、背景 调试IOS程序经常使用gdb&#xff0c;目前gdb只支持32位程序调试&#xff0c;暂不支持IOS 64位程序调试。IOS 32位程序使用GDB调试之前&#xff0c;必须确保手机已越狱&#xff0c;否则无法安装和使用GDB调试软件。下面详细介绍GDB调试IOS 32位程序的环境搭建。 二、I…

MapReduce排序机制(Hadoop)

在MapReduce中&#xff0c;排序的目的是为了方便Reduce阶段的处理&#xff0c;通常是为了将相同键的键值对聚合在一起&#xff0c;以便进行聚合操作或其他处理。 1. Map阶段的局部排序&#xff08;Local Sorting&#xff09;&#xff1a; 对于MapTask&#xff0c;它会将处理的…

python爬虫-----深入了解 requests 库下篇(第二十五天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

一文学会 ts 构建工具 —— tsup

文章目录 能打包什么&#xff1f;安装用法自定义配置文件条件配置在 package.json 中配置多入口打包生成类型声明文件sourcemap生成格式自定义输出文件代码分割产物目标环境支持 es5编译的环境变量对开发命令行工具友好监听模式 watch提供成功构建的钩子 onSuccess压缩产物 min…

RocketMQ学习笔记

kafka适合于日志收集的场景&#xff08;不需要太多topic&#xff1b;topic下面的partition多了会造成写文件的速度变慢&#xff0c;因为要造很多索引&#xff09; RocketMQ更适合于电商场景&#xff08;适用于topic特别多的情况&#xff09; 快速安装RocketMQ RocketMQ的官网…

C++进阶:搜索树

目录 1. 二叉搜索树1.1 二叉搜索树的结构1.2 二叉搜索树的接口及其优点与不足1.3 二叉搜索树自实现1.3.1 二叉树结点结构1.3.2 查找1.3.3 插入1.3.4 删除1.3.5 中序遍历 2. 二叉树进阶相关练习2.1 根据二叉树创建字符串2.2 二叉树的层序遍历I2.3 二叉树层序遍历II2.4 二叉树最近…

一、MinIO基本知识

MinIO基本知识 一、简介1.许可 二、部署1.Docker部署1.1 部署容器 1.2 MinIO页面访问1.3 创建Bucket![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6c8aa92975f146b691f1f36ce1033e7c.png) 三、Python-API1.安装包2.Bucket、Object概念3.Bucket-API4.MinIOClient-…