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 {
    @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.mfbz.cn/a/572524.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数字化转型成功的企业到底是什么样的?

数字化转型成功的企业通常具备以下特征&#xff1a; 1、以客户为中心的业务模式&#xff1a;成功的数字化转型企业将客户放在业务模式的核心位置&#xff0c;通过提供个性化的服务和产品来满足客户需求。这种以客户为中心的模式能够带来更好的客户体验和忠诚度。 2、强大的数…

Power BI 如何解决月份排序错误/乱序问题(自定义顺序/正确排序)

问题描述 在创建图标时&#xff0c;月份标签没有按照正确的顺序排列。 解决方案&#xff1a; Sort by Column 单击选中排序错误的列&#xff08;MMM&#xff09;根据列排序 (sort by Column)选择需要根据哪一列排序。 这里选择的是Month列&#xff0c;这一列有月份的序号1-…

Golang——GMP原理与调度

一. Golang调度器的由来 1.1 单进程时代不需要调度器 我们知道&#xff0c;一切软件都跑在操作系统上&#xff0c;真正用来干活(计算)的是CPU。早期的操作系统每一个程序就是一个进程&#xff0c;直到一个程序运行完&#xff0c;才能进行下一个进程&#xff0c;就是"单进程…

CSS盒子模型与常见问题

CSS盒子模型 显示模式转换显示模式 盒子模型边框线内边距padding 多值写法 尺寸计算与内减法模式外边距 清除默认样式元素溢出外边距问题合并现象塌陷现象 行内元素 – 内外边距问题 显示模式 显示模式&#xff1a;标签&#xff08;元素&#xff09;的显示方式作用&#xff1a;…

C++中的数制转换工具

一、引言 在编程和日常计算中&#xff0c;我们经常需要在不同的数制之间进行转换。二进制、十进制和十六进制是最常用的数制。二进制是计算机内部处理数据的基础&#xff0c;十进制是我们日常生活中最常用的数制&#xff0c;而十六进制则在编程和硬件相关领域中广泛使用。 二…

不要摆摊,不要开早餐店,原因如下

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 我最近开通了视频号会员专区嘛&#xff0c;专区有个问答功能可以提问&#xff0c;有个会员问了我问题&#xff0c;其中一条问答分享给大家&#xff1a; 松哥&#xff0c;突然想去兼职&#xff0c;早上卖点杂粮煎饼果…

小塔 | 时尚领域RFID应用,别人早你一步!

优衣库&#xff0c;作为知名服装品牌零售商&#xff0c;近年来在数字化转型的道路上取得了显著的成果。其中&#xff0c;RFID技术的应用成为了优衣库提升运营效率、优化客户体验以及实现精准营销的重要工具。 RFID助力时尚门店品牌升级 优衣库深知RFID技术的潜力&#xff0c;将…

web--跨域,cors,jsonp,域名接管劫持

同源策略 可以放在csrf cosp 解决同源策略 它会将会从xiaodi这个网站中去获取资源&#xff0c;然后发送给localhost这个网站 就获取到了资源 jsonp 就是这个网站的回调信息有个人的数据 就看callback有没有回调信息 域名接管 当右边两个域名过期&#xff0c;就可以注册它的域名…

Git 安装及配置教程(Windows)【安装】

文章目录 一、 下载1. 官网下载2. 其它渠道 二、 安装三、 配置四、 更新 软件 / 环境安装及配置目录 一、 下载 1. 官网下载 安装地址&#xff1a;https://git-scm.com/download 2. 其它渠道 &#xff08;1&#xff09;百度网盘&#xff08;2.44.0 版本&#xff09; 链接…

使用FPGA实现逐级进位加法器

介绍 逐级进位加法器就是将上一位的输出作为下一位的进位输入&#xff0c;依次这样相加。下面以一个8位逐级进位加法器给大家展示。 我增加了电路结构&#xff0c;应该很容易理解吧。 下面我也列举了一位加法器&#xff0c;可以看下。 电路结构 设计文件 1位加法器 librar…

【Python爬虫】爬取淘宝商品数据——新手教程

大数据时代&#xff0c; 数据收集不仅是科学研究的基石&#xff0c; 更是企业决策的关键。 然而&#xff0c;如何高效地收集数据 成了摆在我们面前的一项重要任务。 本文将为你揭示&#xff0c; 一系列实时数据采集方法&#xff0c; 助你在信息洪流中&#xff0c; 找到…

SpringCloud基础 Consul的引入

前言 首先是为什么引入consul这个组件 我们知道微服务分为很多个模块,这里模块中相互调用,我使用硬编码的模式是不好的 比如微服务模块需要更新的时候,我们使用硬编码的方式可能需要修改很多个地方 但是使用consul之后,就引入了注册中心,我们只需要将对应的服务注册为节点 这样…

重生奇迹MU召唤术师攻略(重生奇迹MU召唤技能)

1、召唤术师&#xff0c;重生奇迹MU的强力职业之一。跟格斗家一样&#xff0c;需要RMB购买资格证才能够使用的一个职业&#xff0c;召唤术师可以说是对于玩家将职业玩法的一种总结性职业&#xff0c;这个职业虽然叫做召唤术师&#xff0c;但是整个重生奇迹MU里唯一能够召唤宝宝…

SpringBoot3 + Kotlin + mybatis-plus + Swagger3后端开发样例

前言&#xff1a; Kotlin 是一种在 JVM&#xff08;Java 虚拟机&#xff09;、Android 和浏览器端运行的静态类型编程语言。以下是关于 Kotlin 的总结介绍&#xff1a; 1、语言特性&#xff1a; 简洁性&#xff1a;Kotlin 旨在提供简洁且安全的代码&#xff0c;同时保持与 Jav…

解决“ImportError: DLL load failed while importing _rust: 找不到指定的程序的问题

运行 scrapy startproject wikiSpider 报错&#xff1a;ImportError: DLL load failed while importing _rust: 找不到指定的程序。 经过尝试 可以更换Python解释器版本来解决 1、点击crtlalts打开设置 点击项目>解释器 选择3.11解释器 &#xff08;我原来报错用的3.9的解…

C++11(第一篇)【C/C++复习版】

目录 1、统一的列表初始化 2、所有容器新增initializer_list构造 3、auto、decltype和typeid 4、nullptr 5、 范围for 6、STL中的变化 array&#xff08;新容器&#xff09; forward_list&#xff08;新容器&#xff09; cbegin、cend、crbegin、crend&#xff08;新方法…

WEB网站服务器安全漏洞扫描环境搭建及漏洞工具扫描

一、适用环境 1、企业自建有门户网站&#xff1b; 2、使用Struts框架的WEB网站&#xff1b; 3、网站服务器涉及有数据库之类的项目&#xff0c;如&#xff1a;微信登录、手机登录、充值、收费等。 4、使用安卓版、苹果版、电脑版结合的缴费类网站平台。 5、方便但需提高安全性…

linux-centos修改时区时间

修改时区为北京时间 先输入tzselect&#xff0c;输入5&#xff0c;再输入9&#xff0c;再输入1&#xff0c;最后再输入1就行了 修改系统时间和硬件时间 查看当前时间 命令date修改系统时间 命令date -s "2024-04-21 18:30:30"查看硬件时间 命令hwclock --show修改…

AIGC Chat GPT 用思维导图总结,数据分析所需要掌握的Excel知识

你还不会制作思维导图吗&#xff1f; 现在已经可以零门槛一键生成&#xff0c;只需跟AI说一句话&#xff0c;就能完成&#xff01;&#xff01;&#xff01; 生成一个思维导图&#xff0c;主题是数据分析师需要掌握的Excel知识&#xff0c;在新窗口生成思念导图。 AIGC ChatG…

ONES 功能上新|ONES Wiki 新功能一览

支持在 ONES Wiki 页面中使用分栏进行横向排版&#xff0c;丰富排版方式&#xff0c;帮助用户以更丰富的版式展示内容。 应用场景&#xff1a; 页面的布局对内容的阅读有很大的影响。当页面中有图文混排的需求时&#xff0c;可以通过分栏来组织页面结构&#xff0c;以更清晰、更…