【SpringBoot】Redis Lua脚本实战指南:简单高效的构建分布式多命令原子操作、分布式锁

news2024/11/15 6:49:11

文章目录

  • 一.Lua脚本
    • 1.Lua特性
    • 2.Lua优势
  • 二.Lua语法
    • 1.注释
    • 2.变量
    • 3.数据类型:
      • 3.1.基本类型
      • 3.2.对象类型:表(table)
    • 4.控制结构:
      • 4.1.条件语句: 使用if、else和elseif来实现条件分支。
      • 4.2.循环结构:Lua支持for循环、while循环和repeat...until循环。
    • 5.函数
      • 5.1.函数的定义与调用
      • 5.2.匿名函数与闭包
    • 6.模块
      • 1. 创建模块
      • 2.使用模块
      • 3.注意事项
    • 6.字符串操作
    • 7.错误处理
      • 7.1.pcall
      • 7.2.xpcall
      • 7.3.自定义错误
    • 8.创建线程和协程
    • 9.如何使用lua标准库
  • 三.Lua脚本的应用场景
    • 1. 缓存更新:
    • 2. 原子操作:
    • 3. 数据处理:
    • 4. 分布式锁:
  • 四.Spring Boot中集成Lua脚本
    • 1. 添加依赖
    • 2.修改配置文件
    • 3.创建Lua脚本
    • 4.编写Java代码以加载和执行Lua脚本
      • 4.1.直接运行内嵌在Java代码中Lua脚本字符串
      • 4.2.加载和运行Lua脚本文件

一.Lua脚本

Lua是一种轻量级、可嵌入的,自带原子性脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。广泛应用于游戏开发、Web开发和其他领域。``

1.Lua特性

  • 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
  • 可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
  • 数据类型丰富,包括数字、字符串、布尔值、表(数组和字典的集合)等。这些数据类型使得Lua语言能够处理各种复杂的数据结构和算法。
  • 支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
  • 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
  • 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
  • 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。

2.Lua优势

Lua脚本在Redis中的使用有许多优势,使其成为执行复杂操作的理想选择。以下是一些主要原因:

性能

  • Lua脚本在Redis中执行,避免了多次的客户端与服务器之间的通信。这可以减少网络开销,提高性能,特别是在需要执行多个Redis命令以完成一个操作时。原子性:Redis保证Lua脚本的原子性执行,无需担心竞态条件或并发问题。

事务

  • Lua脚本可以与Redis事务一起使用,确保一系列命令的原子性执行。这允许你将多个操作视为一个单一的事务,要么全部成功,要么全部失败

复杂操作

  • Lua脚本提供了一种在Redis中执行复杂操作的方法,允许你在一个脚本中组合多个Redis命令。这对于处理复杂的业务逻辑非常有用,例计算和更新分布式计数器、实现自定义数据结构等。

实现复杂的原子锁

  • Lua脚本的执行是原子的,这意味着在Lua脚本执行期间,没有其他客户端可以插入其他操作。这使得Lua脚本在实现诸如分布式锁、计数器、排行榜等需要原子操作的情况下非常有用。

  • 使用Lua脚本,你可以实现复杂的原子锁,而不仅仅是使用Redis的SETNX(set if not exists)命令。这对于分布式锁的实现非常重要。

减少网络开销

  • 对于大批量的数据处理,Lua脚本可以减少客户端和服务器之间的往返次数,从而显著减少网络开销。

减少服务器负载
通过将复杂的计算移至服务器端,可以减轻客户端的负担,降低服务器的负载。

原生支持

  • Redis天生支持Lua脚本,因此不需要额外的插件或扩展。

可读性和维护性

  • Lua脚本是一种常见的脚本语言,易于编写和维护。将复杂逻辑封装在脚本中有助于提高代码的可读性。

Lua脚本在Redis中的优势在于它可以原子性地执行复杂操作、减少网络通信、提高性能、减轻服务器负载,以及提高代码的可读性。这使得它成为执行一系列复杂操作的理想选择,尤其是在分布式系统中需要高性能和可伸缩性的场景下。通过Lua脚本,Redis不仅成为一个键值存储,还能执行复杂的数据操作。

二.Lua语法

在线运行lua脚本网站https://www.bejson.com/runcode/lua/

1.注释

注释在Lua中用于添加说明和注解。单行注释以–开始,多行注释则使用–[[ … ]]。

-- 这是一条单行注释

--[[ 
    这是一个多行注释
    可以跨越多行
]]
  • 单行注释:以2个连续短横线--开始,直到该行结束
  • 多行注释:使用2个方括号[[开始,并以2个方括号]]结束

2.变量

变量在Lua中无需显式声明类型。使用local关键字创建局部变量,全局变量直接声明。

local age = 30
name = "John" -- 全局变量

3.数据类型:

3.1.基本类型

基本数据类型包括整数、浮点数、字符串、布尔值和nil。

  • 表tabile是一种非用 {}存储键值对类型数据,类似java的对象
-- 声明不同类型的变量  
local num = 42  -- 整数  
local num2 = 3.14  -- 浮点数
local bool1 = true  -- true  
local bool2 = false  -- false
local str1 = "Hello, World!"  -- 双引号字符串  
local str2 = 'Lua is great!'  -- 单引号字符串

3.2.对象类型:表(table)

表是Lua的核心数据结构,用花括号{}定义。

  • 表可以包含键值对,键和值可以是任何数据类型。

    local person = { name = "John", age = 30, hobbies = {"Reading", "Gaming"} }
    print("姓名:" .. person.name)
    print("年龄:" .. person.age)
    

4.控制结构:

4.1.条件语句: 使用if、else和elseif来实现条件分支。

local age = 1
if age < 18 then
    print("未成年人")
elseif age >= 18 and age < 66 then
    print("成年人")
else
    print("老年人")
end
-- 未成年人

4.2.循环结构:Lua支持for循环、while循环和repeat…until循环。

for循环

for i = 1, 5 do
    print(i)
end

while循环

local count = 0
while count < 3 do
    print("循环次数: " .. count)
    count = count + 1
end

repeat…until循环

local count = 6
repeat
    print("至少执行一次")
until count > 5
-- 至少执行一次

5.函数

函数在Lua中使用function+end关键字定义,可以接收参数并返回值

function functionName(arg1, arg2, ...)  
    -- 函数体  
    -- 这里是函数要执行的代码  
    -- 可以使用参数arg1, arg2, ...  
    return result -- (可选)返回结果  
end

5.1.函数的定义与调用

	--  定义函数
	function add(a, b)  
	    local sum = a + b  
	    return sum  
	end
	-- 调用add函数并打印结果  
	local result = add(3, 4)  
	print("The sum is: " .. result) -- 输出 "The sum is: 7"

5.2.匿名函数与闭包

-- 定义一个函数,它接受一个函数作为参数并调用它  
function callFunction(func)  
    func() -- 调用传递进来的匿名函数  
end  
  
-- 创建一个匿名函数,并作为参数传递给callFunction  
callFunction(function()  
    print("Hello from an anonymous function!")  
end)  
  
-- 输出:  
-- Hello from an anonymous function!
local outerVariable = "I'm outside!"  
  
-- 创建一个闭包  
local function createClosure()  
    return function()  
        print(outerVariable) -- 访问外部变量  
    end  
end  
  
-- 获取闭包并调用它  
local closure = createClosure()  
closure() -- 输出:I'm outside!

6.模块

Lua支持模块化编程,允许将相关功能封装在独立的模块中,并通过require关键字加载它们。

1. 创建模块

首先,我们创建一个名为myModule.lua的模块文件。在这个文件中,我们定义了一些函数和变量,并在文件末尾返回一个表,该表包含了模块提供的所有公开功能和数据。

-- myModule.lua  
  
-- 私有变量  
local privateVar = "This is a private variable"  
  
-- 私有函数  
local function privateFunction()  
    print("This is a private function")  
end  
  
-- 公开函数  
function myModule.publicFunction()  
    print("This is a public function")  
    print("Private variable is: " .. privateVar)  -- 可以访问私有变量  
end  
  
-- 返回公开部分  
return myModule
  • 注意:在这个示例中,我们使用了myModule这个表来存储公开的函数。这不是必须的,但这样做可以使模块的结构更清晰。

2.使用模块

在另一个Lua脚本中,我们可以使用require函数来加载并使用myModule模块。

-- main.lua  
  
-- 加载模块  
local myModule = require("myModule")  
  
-- 调用模块中的公开函数  
myModule.publicFunction()  
  
-- 尝试访问模块的私有部分(会失败)  
-- print(myModule.privateVar)  -- 这将引发错误,因为privateVar是私有的  
-- myModule.privateFunction()  -- 这也会引发错误,因为privateFunction是私有的

3.注意事项

  • 当使用require加载一个模块时,Lua会首先检查模块是否已经被加载过。如果是,则直接返回之前加载的模块,而不是重新加载。这有助于避免重复加载和初始化模块。

  • 模块的路径可以是相对路径或绝对路径。在上面的示例中,假设myModule.lua和main.lua在同一目录下,所以只使用了模块名作为参数。如果模块在其他位置,你需要提供正确的路径

  • Lua的模块系统相对简单,没有像一些其他语言那样复杂的包管理系统。但是,你可以使用像LuaRocks这样的第三方工具来管理和安装Lua包

6.字符串操作

Lua提供了丰富的字符串操作功能,包括字符串连接、查找、替换、模式匹配

字符串连接

  • Lua中的字符串连接操作非常简单,只需将两个字符串相邻放置即可。

    local str1 = "Hello"  
    local str2 = "World"  
    local result = str1 .. str2  -- 连接字符串  
    print(result)  -- 输出:HelloWorld
    

字符串长度

  • 使用#操作符可以获取字符串的长度。

    local str = "Hello"  
    print(#str)  -- 输出:5
    

字符串查找

  • 使用string.find函数可以在字符串中查找子串。该函数返回两个值:子串开始的位置结束的位置(如果不存在则返回nil)。

    local str = "Hello, World!"  
    local start, end_ = string.find(str, "World")  
    
    if start then  
        print("Found 'World' at position", start)  
    else  
        print("Not found")  
    end
    

字符串替换

  • 使用string.gsub函数可以全局替换字符串中的子串。第一个参数是源字符串第二个参数是要被替换的模式第三个参数是替换成的字符串,第四个参数(可选)是一个计数器,表示替换的最大次数。

    local str = "apple, apple, apple pie"  
    local new_str = string.gsub(str, "apple", "orange")  
    print(new_str)  -- 输出:orange, orange, orange pie  
      
    -- 替换最多两次  
    local new_str = string.gsub(str, "apple", "orange", 2)  
    print(new_str)  -- 输出:orange, orange, apple pie
    

字符串切分

  • 虽然Lua标准库没有直接提供字符串切分函数,但你可以使用string.find和string.sub组合来实现。或者,你可以使用第三方库,如lua-string库中的split函数
    function split(str, delim)  
        local result = {}  
        local from = 1  
        local delim_from, delim_to = string.find(str, delim, from)  
        while delim_from do  
            table.insert(result, string.sub(str, from, delim_from - 1))  
            from = delim_to + 1  
            delim_from, delim_to = string.find(str, delim, from)  
        end  
        table.insert(result, string.sub(str, from))  
        return result  
    end  
      
    local str = "apple,banana,cherry"  
    local fruits = split(str, ",")  
    for _, fruit in ipairs(fruits) do  
        print(fruit)  
    end
    -- 执行结果:
    -- apple
    -- banana
    -- cherry
    

字符串格式化

  • Lua没有内置的字符串格式化函数,但你可以使用string.format函数来模拟这个功能。
    local name = "Alice"  
    local age = 30  
    local greeting = string.format("Hello, my name is %s and I'm %d years old.", name, age)  
    print(greeting)  -- 输出:Hello, my name is Alice and I'm 30 years old.
    

字符串模式匹配

  • Lua提供了强大的模式匹配功能,通过string.match、string.gmatch和string.find等函数可以实现复杂的字符串处理。Lua的模式匹配基于一种类似于Perl的正则表达式语法。

    local str = "apple 123 orange 456"  
    for number in string.gmatch(str, "%d+") do  
        print(number)  -- 输出:123 和 456  
    end
    

7.错误处理

在Lua中,错误处理通常涉及到使用pcall(protected call)或xpcall(extended protected call)函数来捕获和处理可能出现的错误。当在Lua代码中遇到错误时,它会抛出一个错误消息并终止当前的执行。通过使用pcall或xpcall,你可以捕获这些错误并继续执行后续的代码。

7.1.pcall

pcall函数接收一个函数和一个可选的参数列表,并尝试以“保护”模式调用该函数。

  • 如果调用成功,pcall返回true以及函数的返回值
  • 如果调用失败(即抛出了错误),pcall返回false以及错误消息
function riskyFunction()  
    error("Something went wrong!")  
end  
  
local status, result = pcall(riskyFunction)  
if not status then  
    print("An error occurred: " .. result)  
else  
    print("Function call was successful: " .. result)  
end
-- An error occurred: script.lua:2: Something went wrong!

7.2.xpcall

xpcall与pcall类似,但它允许你提供一个错误处理函数,该函数将在发生错误时被调用。这使得你可以进行更复杂的错误处理。

function myErrorHandler(err)  
    print("Caught an error: " .. err)  
    -- 这里可以进行更复杂的错误处理  
    return debug.traceback(err, 2) -- 返回错误跟踪信息  
end  
  
function riskyFunction()  
    error("Something went wrong!")  
end  
  
local status, traceback = xpcall(riskyFunction, myErrorHandler)  
if not status then  
    print("An error occurred:")  
    print(traceback)  
end

执行结果

Caught an error: script.lua:8: Something went wrong!
An error occurred:
script.lua:8: Something went wrong!
stack traceback:
	[C]: in function 'error'
	script.lua:8: in function 'riskyFunction'
	[C]: in function 'xpcall'
	script.lua:11: in main chunk
	[C]: in ?

7.3.自定义错误

在Lua中,你可以使用error函数来抛出一个自定义的错误。error函数接受一个字符串作为错误消息,并可以选择性地提供一个错误级别。

function checkNumber(n)  
    if type(n) ~= "number" then  
        error("Not a number!", 2) -- 抛出一个错误  
    end  
    print("The number is: " .. n)  
end  
  
checkNumber("hello") -- 这将抛出一个错误

执行结果

/usr/local/lua-5.3.5/lua53: script.lua:8: Not a number!
stack traceback:
	[C]: in function 'error'
	script.lua:3: in function 'checkNumber'
	script.lua:8: in main chunk
	[C]: in ?

8.创建线程和协程

-- 创建协程
local cdata = coroutine.create(function () print("Hello from coroutine!") end)  

-- 定义函数  
local function say_hello(name) print("Hello, " .. name) end  
-- 调用函数,输出 "Hello, Alice"
say_hello("Alice")  

-- 创建线程  
local thread = coroutine.create(function () print("Hello from thread!") end)  
-- 恢复线程,输出 "Hello from thread!"
coroutine.resume(thread) 

-- Hello, Alice
-- Hello from thread!

9.如何使用lua标准库

string库:

  • 提供了一系列用于处理字符串的函数,如查找、替换、连接、拆分、格式化等。
  • 例如:string.find(), string.gsub(), string.format() 等。

table库:
提供了一系列用于操作Lua表的函数,如表的插入、删除、排序、遍历等。
例如:table.insert(), table.remove(), table.sort(), table.concat() 等。

math库:

  • 提供了一系列数学函数,如三角函数、指数函数、对数函数、随机数生成等。
  • 例如:math.sin(), math.exp(), math.log(), math.random() 等。

io库:

  • 提供了文件操作的相关函数,用于读写文件、处理文件路径等。
  • 例如: io.open(), io.read(), io.write(), io.lines() 等。

os库:

  • 提供与操作系统相关的函数,如获取当前时间、执行系统命令、处理环境变量等。
  • 例如:os.time(), os.execute(), os.getenv(), os.setlocale() 等。

debug库:

  • 提供了一组用于调试的函数,如获取堆栈跟踪、设置和获取断点、操作局部变量等。
  • 需要注意的是,debug库中的一些功能在受限的环境中可能不可用或被禁用。

package库:

  • 提供了Lua模块加载和搜索路径管理的功能。
  • 例如,你可以使用package.path和package.cpath来设置模块的搜索路径。

coroutine库:

  • 提供了协程(coroutine)管理的函数,允许你创建、恢复、让出和销毁协程。
  • 协程是一种用户态的轻量级线程,可以在Lua中实现非阻塞的I/O操作或并发编程。

怎么使用

  • 在Lua中,使用标准库不需要使用关键字进行显式引入(import)或加载(load)。
    • Lua的标准库是预编译并内置在Lua解释器中的,因此在你的Lua脚本中可以直接调用这些库中的函数,而无需任何额外的步骤

你只需要直接调用标准库中的函数即可,例如:
在这里插入图片描述

三.Lua脚本的应用场景

1. 缓存更新:

场景:在缓存中存储某些数据,但需要定期或基于条件更新这些数据,同时确保在更新期间不会发生并发问题

  • 示例:使用Lua脚本,你可以原子性地检查数据的新鲜度,如果需要更新,可以在一个原子性操作中重新计算数据并更新缓存

    -- 获取键名,即从传入脚本的参数列表KEYS中获取第1个参数
    local key = KEYS[1] -- 获取缓存键
    -- 根据key从缓存中获取数据
    local value = redis.call('GET', key ) -- 尝试从缓存获取数据
    
    -- 如果数据不存在(即data为nil)  
    if not value then
        -- 数据不在缓存中,重新计算并设置
        --调用一个名为 calculateData 的函数(这个函数在脚本中没有定义,但应该是一个用于重新计算数据的函数)
        value = calculateData()
        -- 将新计算的数据设置到缓存中  
        redis.call('SET', key , value )
    end
    -- 返回数据(无论是从缓存中获取的还是新计算的)
    return value 
    

2. 原子操作:

Lua脚本的执行是原子的,这意味着在Lua脚本执行期间,没有其他客户端可以插入其他操作。这使得Lua脚本在实现诸如分布式锁、计数器、排行榜等需要原子操作的情况下非常有用。

场景:需要执行多个Redis命令作为一个原子操作,确保它们在多线程或多进程环境下不会被中断。

  • 示例:使用Lua脚本,你可以将多个命令组合成一个原子操作,如实现分布式锁、计数器、排行榜等。

    -- 获取键名,即从传入脚本的参数列表KEYS中获取第1个参数
    local key = KEYS[1]
    --  获取参数值,这是你想要设置的新值 ,即从传入脚本的参数列表ARGV 中获取第1个参数
    local value = ARGV[1] 
    --  获取当前键对应的值
    local current = redis.call('GET', key) 
    
    -- 如果当前值不存在(即current为nil),或者当前值(转换为数字后)小于给定的参数值(也转换为数字后)
    if not current or tonumber(current) < tonumber(value) then 
        -- 如果当前值不存在或新值更大,设置新值
        redis.call('SET', key, value)
    end
    

3. 数据处理:

场景:需要对Redis中的数据进行复杂的处理,如统计、筛选、聚合等。

  • 示例:使用Lua脚本,你可以在Redis中执行复杂的数据处理,而不必将数据传输到客户端进行处理,减少网络开销。

    -- 从传入脚本的参数列表中获取第1个参数
    local keyPattern = ARGV[1] 
     -- 调用Redis的KEYS命令,根据给定的模式keyPattern获取所有匹配的键。
    -- 需要注意的是,KEYS命令在生产环境中应谨慎使用,因为它可能会阻塞Redis服务器,尤其是在有大量键存在时。
    local keys = redis.call('KEYS', keyPattern)
    -- 初始化一个空的Lua表result,用于存储处理后的数据
    local result = {} 
    --遍历keys表中的每一个键
    for i, key in ipairs(keys) do
    	 -- 获取每个键对应的值 
        local data = redis.call('GET', key)
        -- 调用一个假设存在的processData函数处理这个值(注意:在脚本中并没有给出processData函数的定义,你需要根据实际需求来实现它)。
        -- 然后处理后数据并添加到result表中
        table.insert(result, processData(data))
    end
    --返回处理后的结果表。这个表包含了所有匹配键对应的处理后的数据。
    return result 
    

4. 分布式锁:

场景:实现分布式系统中的锁机制,确保只有一个客户端可以执行关键操作。

  • 示例:使用Lua脚本,你可以原子性地尝试获取锁避免竞态条件然后在完成后释放锁

    -- 获取锁的键名
    local lockKey = KEYS[1] 
    -- 获取锁的值,通常是一个唯一标识符(比如 UUID),用于在释放锁时验证锁的持有者。
    local lockValue = ARGV[1]
    -- 获取锁的超时时间,单位是毫秒,如果在这个时间内锁没有被释放(即 lockKey 没有被删除),则 Redis 会自动删除这个键,避免死锁。
    local lockTimeout = ARGV[2]
    
    -- 尝试使用 SET 命令来设置锁,NX 表示只有在 key 不存在时设置值,PX 表示 key 的过期时间(毫秒)  
    --  lockKey 不存在(NX),则设置其值为 lockValue,并设置其过期时间为 lockTimeout(PX)。如果设置成功,说明客户端成功获取到了锁。
    -- 如果 SET 命令执行成功,说明获取到了锁
    if redis.call('SET', lockKey, lockValue, 'NX', 'PX', lockTimeout) then
    	-- 锁获取成功,执行关键操作(这部分逻辑在脚本中没有给出)  
    	-- ...
        -- 执行完关键操作后,删除锁键,释放锁  
        redis.call('DEL', lockKey) -- 释放锁
        -- 返回 true,表示成功获取并释放了锁  
        return true
    else
       -- 锁获取失败,可能是因为有其他客户端已经持有了该锁  
        return false
    

通常用于在分布式系统中确保多个客户端在并发访问共享资源时能够正确地获取和释放锁,从而避免数据不一致的问题。

四.Spring Boot中集成Lua脚本

在Spring Boot中实现Lua脚本的执行主要涉及Spring Data RedisLettuce(或Jedis)客户端的使用。

1. 添加依赖

添加Spring Data Redis和Lettuce(或Jedis)的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>io.lettuce.core</groupId>
    <artifactId>lettuce-core</artifactId> 
</dependency>

2.修改配置文件

在application.properties或application.yml中配置Redis连接属性,包括主机、端口、密码等。

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=yourPassword

3.创建Lua脚本

创建一个Lua脚本,以执行你需要的操作。将脚本保存在Spring Boot项目的合适位置。

  1. 假设有一个Lua脚本文件myscript.lua,它实现了一个简单的计算:

    local a = tonumber(ARGV[1])
    local b = tonumber(ARGV[2])
    return a + b
    

4.编写Java代码以加载和执行Lua脚本

  • 使用Spring Data Redis提供的StringRedisTemplateLettuceConnectionFactory

4.1.直接运行内嵌在Java代码中Lua脚本字符串

  1. 运行Lua脚本字符串:
    @Service
    public class LuaScriptService {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
    	// 运行Lua脚本字符串
        public Integer executeLuaScriptFromString() {
            // Lua脚本字符串,该脚本接收两个参数(ARGV[1] 和 ARGV[2]),将它们转换为数字并相加  
            String luaScript = "local a = tonumber(ARGV[1])\nlocal b = tonumber(ARGV[2])\nreturn a + b";
            
    		// 创建一个Redis脚本对象,指定Lua脚本和期望的返回类型(Integer) 
            RedisScript<Integer> script = new DefaultRedisScript<>(luaScript, Integer.class);
    		// 创建一个空的keys数组,因为在这个Lua脚本中,我们不使用KEYS参数  
    		String[] keys = new String[0]; // 通常情况下,没有KEYS部分  
    		// 创建一个args数组,包含两个参数,这些参数将传递给Lua脚本  
    		Object[] args = new Object[]{10, 20}; // 传递给Lua脚本的参数  
    		
    		// 使用stringRedisTemplate(它应该是已经配置好的Spring Data Redis的StringRedisTemplate实例)  
    		// 执行Lua脚本,并传入keys和args数组  
    		Integer result = stringRedisTemplate.execute(script, keys, args);  
      
    		// 返回执行Lua脚本后得到的结果(两个数字的和)  
    		return result;
        }
    }
    

4.2.加载和运行Lua脚本文件

  • 将Lua脚本保存到文件,例如myscript.lua。然后创建一个Java类来·加载和运行该脚本文件·:

    @Service
    public class LuaScriptService {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Autowired
        private ResourceLoader resourceLoader;
    	
    	// 从文件中执行Lua脚本的方法  
        public Integer executeLuaScriptFromFile() {
    	    // 加载位于类路径下的myscript.lua资源  
    	    Resource resource = resourceLoader.getResource("classpath:myscript.lua");  
    	    String luaScript;  
    	  
    	    try {  
    	        // 尝试读取资源文件内容,并将其转换为字符串  
    	        luaScript = new String(resource.getInputStream().readAllBytes());  
    	    } catch (Exception e) {  
    	        // 如果无法读取Lua脚本文件,则抛出运行时异常  
    	        throw new RuntimeException("无法读取Lua脚本文件。");  
    	    }  
            
    		// 创建一个Redis脚本对象,指定Lua脚本和期望的返回类型(Integer) 
            RedisScript<Integer> script = new DefaultRedisScript<>(luaScript, Integer.class);
    		// 创建一个空的keys数组,因为在这个Lua脚本中,我们不使用KEYS参数  
    		String[] keys = new String[0]; // 通常情况下,没有KEYS部分  
    		// 创建一个args数组,包含两个参数,这些参数将传递给Lua脚本  
    		Object[] args = new Object[]{10, 20}; // 传递给Lua脚本的参数  
    		
    		// 使用stringRedisTemplate(它应该是已经配置好的Spring Data Redis的StringRedisTemplate实例)  
    		// 执行Lua脚本,并传入keys和args数组  
    		Integer result = stringRedisTemplate.execute(script, keys, args);  
      
    		// 返回执行Lua脚本后得到的结果(两个数字的和)  
    		return result;
        }
    }
    
  1. 运行应用程序

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

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

相关文章

大模型面试常考知识点2

文章目录 1. LLM推理attention优化技术KV CachePageAttention显存优化MHA\GQA\MQA优化技术FlashAttention优化技术稀疏Attention1. Atrous Self Attention2. Local Self Attention3. Sparse Self Attention 2. LLM数据处理关键去重多样性保证构造扩充数据充分利用数据 参考文献…

前端小程序调用 getLocation 实现地图位置功能,通过 纬度:latitude 经度: longitude 获取当前位置

1、首先登录一下 腾讯的位置服务 有账号就登录没账号就注册&#xff0c; 点击右上角的控制台点击左侧的应用管理 ---> 我的应用 ---->> 创建应用 1、创建应用 2、列表就会显示我们刚刚创建好的 key 3、点击添加 key 4、按照要求填写信息 我们用的是小程序 所以选择…

[python:django]:web框架搭建项目

文章目录 pip查看安装列表安装制定Django版本初始化django项目执行 python manage.py startapp projectName 生成app应用执行 python manage.py runserver 运行web项目配置django项目页面访问地址注意&#xff1a;再次访问地址&#xff0c;返回制定页面 pip查看安装列表 C:\Us…

通过 Java 操作 redis -- set 集合基本命令

目录 使用命令 sadd &#xff0c;smembers 使用命令 sismember 使用命令 scard 使用命令 spop 使用命令 sinter&#xff0c;sinterstore&#xff0c;sunion&#xff0c;sunionstore&#xff0c;sdiff&#xff0c;sdiffstore 关于 redis set 集合类型的相关命令推荐看Redis …

Vue3的CRUD模版(附Demo)

目录 前言模版 前言 用惯Vue2之后&#xff0c;在碰Vue3后&#xff0c;整体还是有所区别 此文主要做一个回顾总结 假设界面如下&#xff1a; 可CRUD&#xff0c;对应的新增 添加一些必选项&#xff1a; 其中数据库的设计如下&#xff1a; 模版 对应需要注意参数位置、初始…

基于遗传优化的双BP神经网络金融序列预测算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于遗传优化的双BP神经网络金融序列预测算法matlab仿真&#xff0c;采用的双BP神经网络结构如下&#xff1a; 2.测试软件版本以及运行结果展示 MATLAB2022A版本…

[蓝桥杯]真题讲解:合并数列(双指针+贪心)

[蓝桥杯]真题讲解&#xff1a;班级活动&#xff08;贪心&#xff09; 一、视频讲解二、正解代码1、C2、python33、Java 一、视频讲解 [蓝桥杯]真题讲解&#xff1a;合并数列&#xff08;双指针贪心&#xff09; 二、正解代码 1、C #include<bits/stdc.h> #define in…

一个优秀 Maven 项目,各 Model 间最佳继承设计方案

1.单一职责原则 (Single Responsibility Principle): 每个模块应该专注于执行一个清晰且明确定义的功能&#xff0c;遵循单一职责原则&#xff0c;以降低模块的复杂性。 2.高内聚性 (High Cohesion): 模块内的组件和类应该紧密相关&#xff0c;共同实现模块的目标。高内聚性…

5 个遥遥领先的大模型 RAG 工具

想象一下拥有一种超能力&#xff0c;让你能够对任何问题或提示生成类似人类的回答&#xff0c;同时还能够利用庞大的外部知识库确保准确性和相关性。这不是科幻小说&#xff0c;这就是检索增强生成&#xff08;RAG&#xff09;的力量。 在本文中&#xff0c;我们将介绍五大遥遥…

macOS上将ffmpeg.c编译成Framework

1 前言 本文介绍下在macOS上将ffmpeg的fftools目录下的ffmpeg.c程序&#xff0c;也就是ffmpeg的命令行程序&#xff0c;编译成framework的方法。编译成.a或.dylib的方法类似。 编译环境如下&#xff1a; xcode15.3&#xff1b;ffmpeg release/6.1; 2 编译ffmpeg 首先clone我们…

Sqlite在Mybatis Plus中关于时间字段的处理

我的个人项目中&#xff0c;使用Mybatis-Plus 和 Sqlite数据库&#xff0c; 但是在存储和查询时间字段的时候&#xff0c;总是出现问题&#xff0c;记录下我解决问题的过程。 Sqlite会默认把时间字段转成时间戳存储到数据库的字段中&#xff0c;看起来不直观&#xff0c;所以我…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑微电网联盟协调运行的用户侧共享储能多计费方式博弈定价方法》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

YOLOv8训练流程-原理解析[目标检测理论篇]

关于YOLOv8的主干网络在YOLOv8网络结构介绍-CSDN博客介绍了&#xff0c;为了更好地学习本章内容&#xff0c;建议先去看预测流程的原理分析YOLOv8原理解析[目标检测理论篇]-CSDN博客&#xff0c;再次把YOLOv8网络结构图放在这里&#xff0c;方便随时查看。 ​ 1.前言 YOLOv8训练…

geoserver SQL注入、Think PHP5 SQL注入、spring命令注入

文章目录 一、geoserver SQL注入CVE-2023-25157二、Think PHP5 SQL注入三、Spring Cloud Function SpEL表达式命令注入&#xff08;CVE-2022-22963&#xff09; 一、geoserver SQL注入CVE-2023-25157 介绍&#xff1a;GeoServer是一个开源的地理信息系统&#xff08;GIS&#…

[C/C++] -- 大数的加减法

大数加减法的问题主要产生于计算机基本数据类型的表示范围限制。通常情况下&#xff0c;计算机采用有限位数的数据类型&#xff08;如int、long&#xff09;来表示整数&#xff0c;这些数据类型的表示范围有限&#xff0c;无法表示超出范围的大整数。 例如超过了long类型的表示…

支持播放h265的插件

插件源码地址&#xff1a;GitCode - 开发者的代码家园https://gitcode.com/mirrors/nanguantong/flv-h265.js/overview 1. 下载代码 运行以下命令 npm i npm run build npm run build 后生成 flv.min.js 文件&#xff0c;引入使用 2. 调用 js 文件&#xff1a; let flvj…

【数据结构】链式栈

链式栈 一、链式栈的栈顶在哪里&#xff1f; 二、链式栈的结构&#xff1a; typedef struct LSNode {int data;struct LSNode* next; }LSNode, *PLStack; // 链栈的节点&#xff0c;由于栈顶在第一个数据节点&#xff0c;所以不需要top指针三、链式栈的实现 //初始化 void …

价格中间加横杠

<span></span>59 <span class"price">177</span>.price{ text-decoration:line-through; }

vue2和vue3区别: 探索关键差异

vue2和vue3区别&#xff1a; 探索关键差异 Vue.js 作为流行的前端框架&#xff0c;其版本 3 带来了许多令人兴奋的改进和新功能。虽然 Vue 3 保持了与 Vue 2 的相似性&#xff0c;但也存在一些关键差异需要开发者注意。本文将通过表格形式&#xff0c;清晰地展现 Vue 2 和 Vue …

清理缓存简单功能实现

在程序开发中&#xff0c;经常会用到缓存&#xff0c;最常用的后端缓存技术有Redis、MongoDB、Memcache等。 而有时候我们希望能够手动清理缓存&#xff0c;点一下按钮就把当前Redis的缓存和前端缓存都清空。 功能非常简单&#xff0c;创建一个控制器类CacheController&#xf…