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

文章目录

  • 一.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 thenprint("未成年人")
elseif age >= 18 and age < 66 thenprint("成年人")
elseprint("老年人")
end
-- 未成年人

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

for循环

for i = 1, 5 doprint(i)
end

while循环

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

repeat…until循环

local count = 6
repeatprint("至少执行一次")
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 {@Autowiredprivate 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 {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate 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.mzph.cn/web/10650.shtml

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

相关文章

感知机和神经网络

引入 什么是神经网络&#xff1f; 我们今天学习的神经网络&#xff0c;不是人或动物的神经网络&#xff0c;但是又是模仿人和动物的神经网络而定制的神经系统&#xff0c;特别是大脑和神经中枢&#xff0c;定制的系统是一种数学模型或计算机模型&#xff0c;神经网络由大量的人…

图像处理:图像噪声添加

文章目录 前言一、高斯噪声二、椒盐噪声三、泊松噪声四、斑点噪声五、指数噪声六、均匀噪声总结 前言 本文主要介绍几种添加图像噪声的方法&#xff0c;用于数据增强等操作。 以下图为例。 一、高斯噪声 高斯噪声就是给图片添加一个服从高斯分布的噪声&#xff0c;可以通过调…

vLLM初探

vLLM是伯克利大学LMSYS组织开源的大语言模型高速推理框架&#xff0c;旨在极大地提升实时场景下的语言模型服务的吞吐与内存使用效率。vLLM是一个快速且易于使用的库&#xff0c;用于 LLM 推理和服务&#xff0c;可以和HuggingFace 无缝集成。vLLM利用了全新的注意力算法「Page…

Python+PySpark数据计算

1、map算子 对RDD内的元素进行逐个处理&#xff0c;并返回一个新的RDD&#xff0c;可以使用lambda以及链式编程&#xff0c;简化代码。 注意&#xff1a;再python中的lambda只能有行&#xff0c;如果有多行&#xff0c;要写成外部函数&#xff1b;&#xff08;T&#xff09;-&…

train_gpt2_fp32.cu - cudaCheck

源码 // CUDA error checking void cudaCheck(cudaError_t error, const char *file, int line) {if (error ! cudaSuccess) {printf("[CUDA ERROR] at file %s:%d:\n%s\n", file, line,cudaGetErrorString(error));exit(EXIT_FAILURE);} }; 解释 该函数用于检查CU…

无人机路径规划:基于鲸鱼优化算法WOA的复杂城市地形下无人机避障三维航迹规划,可以修改障碍物及起始点(Matlab代码)

一、部分代码 close all clear clc rng(default); %% 载入数据 data.S[50,950,12]; %起点位置 横坐标与纵坐标需为50的倍数 data.E[950,50,1]; %终点点位置 横坐标与纵坐标需为50的倍数 data.Obstaclexlsread(data1.xls); data.numObstacleslength(data.Obstacle(:,1)); …

TypeError: can only concatenate str (not “int“) to str

TypeError: can only concatenate str (not "int") to str a 窗前明月光&#xff0c;疑是地上霜。举头望明月&#xff0c;低头思故乡。 print(str_len len(str_text) : len(a)) 试图打印出字符串 a 的长度&#xff0c;但是在 Python 中拼接字符串和整数需要使用字符…

【微服务】spring aop实现接口参数变更前后对比和日志记录

目录 一、前言 二、spring aop概述 2.1 什么是spring aop 2.2 spring aop特点 2.3 spring aop应用场景 三、spring aop处理通用日志场景 3.1 系统日志类型 3.2 微服务场景下通用日志记录解决方案 3.2.1 手动记录 3.2.2 异步队列es 3.2.3 使用过滤器或拦截器 3.2.4 使…

triton编译学习

一 流程 Triton-MLIR: 从DSL到PTX - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/671434808Superjomns blog | OpenAI/Triton MLIR 迁移工作简介https://superjom

基于STM32单片机的环境监测系统设计与实现

基于STM32单片机的环境监测系统设计与实现 摘要 随着环境污染和室内空气质量问题的日益严重&#xff0c;环境监测系统的应用变得尤为重要。本文设计并实现了一种基于STM32单片机的环境监测系统&#xff0c;该系统能够实时监测并显示室内环境的温湿度、甲醛浓度以及二氧化碳浓…

新iPadPro是怎样成为苹果史上最薄产品的|Meta发布AI广告工具全家桶| “碾碎一切”,苹果新广告片引争议|生成式AI,苹果倾巢出动

Remini走红背后&#xff1a;AI生图会是第一个超级应用吗&#xff1f;新iPadPro是怎样成为苹果史上最薄产品的生成式AI&#xff0c;苹果倾巢出动Meta发布AI广告工具全家桶&#xff0c;图像文本一键生成解放打工人苹果新iPadPro出货量或达500万台&#xff0c;成中尺寸OLED发展关键…

8、QT——QLabel使用小记2

前言&#xff1a;记录开发过程中QLabel的使用&#xff0c;持续更新ing... 开发平台&#xff1a;Win10 64位 开发环境&#xff1a;Qt Creator 13.0.0 构建环境&#xff1a;Qt 5.15.2 MSVC2019 64位 一、基本属性 技巧&#xff1a;对于Qlabel这类控件的属性有一些共同的特点&am…

QToolButton的特殊使用

QToolButton的特殊使用 介绍通过QSS取消点击时的凹陷效果点击时的凹陷效果通过QSS取消点击时的凹陷效果 介绍 该篇文章记录QToolButton使用过程中的特殊用法。 通过QSS取消点击时的凹陷效果 点击时的凹陷效果 通过QSS取消点击时的凹陷效果 #include <QToolButton> #i…

【深耕 Python】Quantum Computing 量子计算机(5)量子物理概念(二)

写在前面 往期量子计算机博客&#xff1a; 【深耕 Python】Quantum Computing 量子计算机&#xff08;1&#xff09;图像绘制基础 【深耕 Python】Quantum Computing 量子计算机&#xff08;2&#xff09;绘制电子运动平面波 【深耕 Python】Quantum Computing 量子计算机&…

ios 开发如何给项目安装第三方库,以websocket库 SocketRocket 为例

1.brew 安装 cococapods $ brew install cocoapods 2、找到xcode项目 的根目录&#xff0c;如图&#xff0c;在根目录下创建Podfile 文件 3、在Podfile文件中写入 platform :ios, 13.0 use_frameworks! target chat_app do pod SocketRocket end project ../chat_app.x…

Python实战开发及案例分析(18)—— 逻辑回归

逻辑回归是一种广泛用于分类任务的统计模型&#xff0c;尤其是用于二分类问题。在逻辑回归中&#xff0c;我们预测的是观测值属于某个类别的概率&#xff0c;这通过逻辑函数&#xff08;或称sigmoid函数&#xff09;来实现&#xff0c;该函数能将任意值压缩到0和1之间。 逻辑回…

【linux】详解linux基本指令

目录 cat more less head tail 时间 cal find grep zip/unzip tar bc uname –r 关机 小编一共写了两篇linux基本指令&#xff0c;这两篇涵盖了大部分初学者的必备指令&#xff0c;这是第二篇&#xff0c;第一篇详见http://t.csdnimg.cn/HRlVt cat 适合查看小文…

网站localhost和127.0.0.1可以访问,本地ip不可访问解决方案

部署了一个网站, 使用localhost和127.0.0.1加端口号可以访问, 但是使用本机的ip地址加端口号却不行. 原因可能有多种. 可能的原因: 1 首先要确认是否localhost对应的端口是通的(直接网址访问), 以及你无法访问的那个本机ip是否正确(使用ping测试)&#xff1b; 2 检查本机的防火…

从头理解transformer,注意力机制(下)

交叉注意力 交叉注意力里面q和KV生成的数据不一样 自注意力机制就是闷头自学 解码器里面的每一层都会拿着编码器结果进行参考&#xff0c;然后比较相互之间的差异。每做一次注意力计算都需要校准一次 编码器和解码器是可以并行进行训练的 训练过程 好久不见输入到编码器&…

docker部署springboot+Vue项目

项目介绍&#xff1a;后台springboot项目&#xff0c;该项目环境mysql、redis 。前台Vue&#xff1a;使用nginx反向代理 方法一&#xff1a;docker run 手动逐个启动容器 1.docker配置nginx代理 将vue项目打包上传到服务器上。创建文件夹存储数据卷&#xff0c;html存放打包…