lua整合redis

news2024/11/14 12:13:09

文章目录

  • 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 {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @GetMapping("/test")
    private String test(String idx){
        //得到对应的商品key
        String key="goods:"+idx;
        //获取商品剩余数量
        Integer amount = Integer.parseInt(redisTemplate.opsForValue().get(key));
        //如果又剩余就购买成功对应数量-1
        if(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 {
    @Autowired
    private 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";
    @Autowired
    private 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";
    @Autowired
    private 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";
    @Autowired
    private 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";
    @Autowired
    private 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,第三个参数是对应的args
                result = 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 then
    return 0;
end
local amount = tonumber(redis.call("get",key))
if amount >0 then
     res = 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 {
    @Autowired
    private 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";
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private 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.coloradmin.cn/o/1613180.html

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

相关文章

【Hadoop】- MapReduce YARN 初体验[9]

目录 提交MapReduce程序至YARN运行 1、提交wordcount示例程序 1.1、先准备words.txt文件上传到hdfs&#xff0c;文件内容如下&#xff1a; 1.2、在hdfs中创建两个文件夹&#xff0c;分别为/input、/output 1.3、将创建好的words.txt文件上传到hdfs中/input 1.4、提交MapR…

Dynamic Wallpaper for Mac激活版:视频动态壁纸软件

Dynamic Wallpaper for Mac 是一款为Mac电脑量身打造的视频动态壁纸应用&#xff0c;为您的桌面带来无限生机和创意。这款应用提供了丰富多样的视频壁纸选择&#xff0c;涵盖了自然风景、抽象艺术、科幻奇观等多种主题&#xff0c;让您的桌面成为一幅活生生的艺术画作。 Dynami…

ES中文检索须知:分词器与中文分词器

ElasticSearch (es)的核心功能即为数据检索&#xff0c;常被用来构建内部搜索引擎或者实现大规模数据在推荐召回流程中的粗排过程。 ES分词 分词即为将doc通过Analyzer切分成一个一个Term&#xff08;关键字&#xff09;&#xff0c;es分词在索引构建和数据检索时均有体现&…

(避雷指引:管理页面超时问题)windows下载安装RabbitMQ

一、背景&#xff1a; 学习RabbitMQ过程中&#xff0c;由于个人电脑性能问题&#xff0c;直接装在windows去使用RabbitMQ&#xff0c;根据各大网友教程&#xff0c;去下载安装完之后&#xff0c;使用web端进行简单的入门操作时&#xff0c;总是一直提示超时&#xff0c;要么容…

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器(TcpServer板块)

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现⾼并发服务器&#xff08;TcpServer板块&#xff09; 一、思路图二、模式关系图三、定时器的设计1、Linux本身给我们的定时器2、我们自己实现的定时器&#xff08;1&#xff09;代码部分&#xff08;2&#xff09;思…

图论——基础概念

文章目录 学习引言什么是图图的一些定义和概念图的存储方式二维数组邻接矩阵存储优缺点 数组模拟邻接表存储优缺点 边集数组优缺点排序前向星优缺点链式前向星优缺点 学习引言 图论&#xff0c;是 C 里面很重要的一种算法&#xff0c;今天&#xff0c;就让我们一起来了解一下图…

使用docker搭建GitLab个人开发项目私服

一、安装docker 1.更新系统 dnf update # 最后出现这个标识就说明更新系统成功 Complete!2.添加docker源 dnf config-manager --add-repohttps://download.docker.com/linux/centos/docker-ce.repo # 最后出现这个标识就说明添加成功 Adding repo from: https://download.…

【数据结构】顺序表:与时俱进的结构解析与创新应用

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 数据结构与算法 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 目录 一、数据结构的概念 二、顺序表&#xff08;Sequence List&#xff09; 2.1 线性表的概念以及结构 2.2 顺序表分类 …

SpringMVC深解--一起学习吧之架构

SpringMVC的工作原理主要基于请求驱动&#xff0c;它采用了前端控制器模式来进行设计。以下是SpringMVC工作原理的详细解释&#xff1a; 请求接收与分发&#xff1a; 当用户发送一个请求到Web服务器时&#xff0c;这个请求首先会被SpringMVC的前端控制器&#xff08;Dispatche…

ExpertPrompting:指导大语言模型成为杰出专家

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 论文标题&#xff1a;ExpertPrompting: Instructing Large Language Models to be Distinguished Experts 论文地址&#xff1a;https://arxiv.org/abs/2305.14688 作者 & 机构&#xff1a;Benfen…

【号码工具】批量手机号码归属地查询,一次性查询40万个,如何大批量的进行手机号码归属地查询

前言&#xff1a; 批量的筛选出一个地区的手机号码、批量查询一批号码的归属地&#xff0c;按城市分类&#xff0c;按省份分类&#xff0c;按运营商分类&#xff0c;都可以&#xff0c;比如我想找广东省的&#xff0c;那么查询好后&#xff0c;就按照省进行分类&#xff0c;找…

Spring Security之Session管理

前言 在聊认证过滤器的时候&#xff0c;我们埋了个坑&#xff1a;Session管理。实际上&#xff0c;事情从这里开始&#xff0c;就变得复杂了。提前跟大家交个底&#xff1a;后续我们将涉及多个需要协同才能完成的功能。 什么是Session 想要管理session&#xff0c;就必须搞清…

分析和比较深度学习框架 PyTorch 和 Tensorflow

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 深度学习作为人工智能的一个重要分支&#xff0c;在过去十年中取得了显著的进展。PyTorch 和 TensorFlow 是目前最受欢迎、最强大的两个深度学习框架&#xff0c;它们各自拥有独特的特点和优势。 1. Py…

Llama 3大模型发布!快速体验推理及微调

Meta&#xff0c;一家全球知名的科技和社交媒体巨头&#xff0c;在其官方网站上正式宣布了一款开源的大型预训练语言模型——Llama-3。 据了解&#xff0c;Llama-3模型提供了两种不同参数规模的版本&#xff0c;分别是80亿参数和700亿参数。这两种版本分别针对基础的预训练任务…

【系统分析师】软件工程

文章目录 1、信息系统生命周期2、软件开发模型2.1 原型及其演化2.2 增量模型和螺旋模型2.3 V模型、喷泉模型、快速应用开发2.4 构件组装模型2.5 统一过程-UP2.6 敏捷方法 3、逆向工程4、净室软件工程 【写在前面】 记录了一系列【系统分析师】文章&#xff0c;点击下面的链接&a…

【Kyuubi】Apache Kyuubi 1.8 特性解读

Apache Kyuubi 1.8 特性解读 1.Apache Kyuubi 简介2.场景扩展 —— 在线分析&#xff0c;离线跑批3.流式增强 —— 流批一体&#xff0c;面向未来4.企业特性 —— 行业沉淀&#xff0c;持续打磨5.开源社区 —— 开放包容&#xff0c;合作共赢 本文来自于 Apache Kyuubi PMC Mem…

数据结构与算法解题-20240421

数据结构与算法解题-20240421 一、278. 第一个错误的版本二、541. 反转字符串 II三、右旋字符串四、替换数字五、977.有序数组的平方 一、278. 第一个错误的版本 简单 你是产品经理&#xff0c;目前正在带领一个团队开发新的产品。不幸的是&#xff0c;你的产品的最新版本没有…

深度学习-优化策略

1.使用众所周知的梯度下降法。 &#xff08;1&#xff09;.批量梯度下降法&#xff1a;每次参数更新使用所有的样本&#xff08;2&#xff09;.随机梯度下降法&#xff1a;每次参数更新只使用一次样本&#xff08;3&#xff09;.小批量梯度下降法&#xff1a;每次参数更新使用…

【python】启动一个公司级项目的完整报错和解决方案

启动一个项目对于新手都是不容易的事情 操作 打开项目 使用pyCharm打开python项目以后&#xff0c;先找main方法&#xff0c;一般在根目录有一个.py的文件 点进去以后会让你配置Python解释器 每个项目都有自己的一个虚拟环境&#xff0c;配置自己的解释器&#xff0c;可能…

Gitea 简单介绍、用法以及使用注意事项!

Gitea 是一个轻量级的代码托管解决方案&#xff0c;它提供了一个简单而强大的平台&#xff0c;用于托管和协作开发项目。基于 Go 语言编写&#xff0c;与 GitLab 和 GitHub Enterprise 类似&#xff0c;但专为自托管而设计。以下是对 Gitea 的详细介绍&#xff0c;包括常用命令…