Redian新闻
>
Redis 和 SpringBoot 的绝佳组合:Lua 脚本的黑科技!

Redis 和 SpringBoot 的绝佳组合:Lua 脚本的黑科技!

公众号新闻

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入芋道快速开发平台知识星球。下面是星球提供的部分资料: 

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn

来源:blog.csdn.net/Mrxiao_bo/
article/details/133783127


曾经有一位魔术师,他擅长将Spring Boot和Redis这两个强大的工具结合成一种令人惊叹的组合。他的魔法武器是Redis的Lua脚本。

今天,我们将揭开这个魔术师的秘密,探讨如何在Spring Boot项目中使用Lua脚本,以解锁新的可能性和提高性能。如果你一直在寻找提升你的应用程序的方法,那么这篇博客将为你揭示其中的神奇之处。

第一部分:Lua脚本简介

当涉及Lua编程时,以下是对前述12个关键概念的详细说明,附带Lua代码示例以帮助你更深入了解这门编程语言:

注释:

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

-- 这是一条单行注释

--[[ 
    这是一个多行注释
    可以跨越多行
]]

变量:

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

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

数据类型:

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

表是一种非常灵活的数据结构。

local num = 42
local str = "Hello, Lua!"
local flag = true
local empty = nil
local person = { name = "John", age = 30 }

控制结构:

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

if age < 18 then
    print("未成年")
elseif age >
18 and age < 65 then
    print("成年")
else
    print("老年")
end

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

for i = 15 do
    print(i)
end

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

repeat
    print("至少执行一次")
until count > 5

函数:

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

function add(a, b)
    return a + b
end

local result 
= add(53)
print("5 + 3 = " .. result)

表(table):

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

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

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

模块:

Lua支持模块化编程,允许将相关功能封装在独立的模块中,并通过require关键字加载它们。示例略显复杂,请参考Lua模块的标准用法以获得详细示例。

字符串操作:

Lua提供了许多字符串处理函数,例如string.sub用于截取子串,string.find用于查找字符串中的子串等。

local text = "Lua programming"
local sub = string.sub(text, 13)
print(sub) -- 输出 "Lua"

错误处理:

错误处理通常使用pcall函数来包裹可能引发异常的代码块,以捕获并处理错误。这通常与assert一起使用。

local success, result = pcall(function()
    error("出错了!")
end)

if success then
    print("执行成功")
else
    print("错误信息: " .. result)
end

标准库:

Lua标准库包含丰富的功能,如文件操作、网络编程、正则表达式、时间处理等。你可以通过内置的模块来使用这些功能,如io、socket等。

总之,Lua是一种灵活的编程语言,其简洁性和强大的表格数据结构使其在各种应用中具有广泛的用途。这些示例代码应该有助于更好地理解Lua的基本概念和语法。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

第二部分:为什么选择Lua脚本

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

  • 性能:

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

  • 事务:

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

  • 复杂操作:

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

  • 原子锁:

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

  • 减少网络开销:

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

  • 减少服务器负载:

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

  • 原生支持:

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

  • 可读性和维护性:

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

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

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

第三部分:lua脚本的应用场景

Lua脚本在Redis中有广泛的应用场景,以下是一些示例场景,展示了Lua脚本的实际用途:

1. 缓存更新:

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

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

local cacheKey = KEYS[1] -- 获取缓存键
local data = redis.call('GET', cacheKey) -- 尝试从缓存获取数据
if not data then
    -- 数据不在缓存中,重新计算并设置
    data = calculateData()
    redis.call('SET', cacheKey, data)
end
return data

2. 原子操作:

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

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

local key = KEYS[1] -- 获取键名
local value = ARGV[1] -- 获取参数值
local current = redis.call('GET', key) -- 获取当前值
if not current or tonumber(current) < tonumber(value) then
    -- 如果当前值不存在或新值更大,设置新值
    redis.call('SET', key, value)
end

3. 数据处理:

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

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

local keyPattern = ARGV[1] -- 获取键名的匹配模式
local keys = redis.call('KEYS', keyPattern) -- 获取匹配的键
local result = {}
for i, key in ipairs(keys) do
    local data 
= redis.call('GET', key) -- 获取每个键对应的数据
    -- 处理数据并添加到结果中
    table.insert(result, processData(data))
end
return result

4. 分布式锁:

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

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

local lockKey = KEYS[1] -- 获取锁的键名
local lockValue = ARGV[1] -- 获取锁的值
local lockTimeout = ARGV[2] -- 获取锁的超时时间
if redis.call('SET', lockKey, lockValue, 'NX''PX', lockTimeout) then
    -- 锁获取成功,执行关键操作
    -- ...
    redis.call('DEL', lockKey) -- 释放锁
    return true
else
    return false -- 无法获取锁

这些场景只是Lua脚本在Redis中的应用之一。Lua脚本允许你在Redis中执行更复杂的操作,而无需进行多次的网络通信,从而提高性能和可伸缩性,同时确保数据的一致性和原子性。这使得Lua成为Redis的强大工具,用于处理各种分布式系统需求。

第四部分:Lua脚本在Spring Boot中的实现

在Spring Boot中实现Lua脚本的执行主要涉及Spring Data Redis和Lettuce(或Jedis)客户端的使用。以下是编写、加载和执行Lua脚本的步骤和示例:

  • 添加依赖:

首先,在Spring Boot项目的pom.xml中,添加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> <!-- 或使用Jedis -->
</dependency>
  • 配置Redis连接:

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

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=yourPassword
  • 创建Lua脚本:

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

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

local a = tonumber(ARGV[1])
local b = tonumber(ARGV[2])
return a + b
  • 编写Java代码:

在Spring Boot应用中,编写Java代码以加载和执行Lua脚本。使用Spring Data Redis提供的StringRedisTemplateLettuceConnectionFactory

提供两种不同的示例来执行Lua脚本,一种是直接运行Lua脚本字符串,另一种是运行脚本文件。以下是这两种示例:

  • 运行Lua脚本字符串:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

@Service
public class LuaScriptService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public Integer executeLuaScriptFromString() {
        String luaScript = "local a = tonumber(ARGV[1])\nlocal b = tonumber(ARGV[2])\nreturn a + b";
        RedisScript<Integer> script = new DefaultRedisScript<>(luaScript, Integer.class);
        String[] keys = new String[0]; // 通常情况下,没有KEYS部分
        Object[] args = new Object[]{1020}; // 传递给Lua脚本的参数
        Integer result = stringRedisTemplate.execute(script, keys, args);
        return result;
    }
}
  • 运行Lua脚本文件:

首先,将Lua脚本保存到文件,例如myscript.lua。

然后,创建一个Java类来加载和运行该脚本文件:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

@Service
public class LuaScriptService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private ResourceLoader resourceLoader;

    public Integer executeLuaScriptFromFile() {
        Resource resource = resourceLoader.getResource("classpath:myscript.lua");
        String luaScript;
        try {
            luaScript = new String(resource.getInputStream().readAllBytes());
        } catch (Exception e) {
            throw new RuntimeException("Unable to read Lua script file.");
        }
        
        RedisScript<Integer> script = new DefaultRedisScript<>(luaScript, Integer.class);
        String[] keys = new String[0]; // 通常情况下,没有KEYS部分
        Object[] args = new Object[]{1020}; // 传递给Lua脚本的参数
        Integer result = stringRedisTemplate.execute(script, keys, args);
        return result;
    }
}

通过这两种示例,你可以选择要执行Lua脚本的方式,是直接在Java代码中定义脚本字符串,还是从文件中读取脚本。

  • 运行应用程序:

启动Spring Boot应用程序,然后可以调用LuaScriptService中的executeLuaScript方法来执行Lua脚本。


这个示例中,我们首先注入了StringRedisTemplate,然后创建了一个RedisScript对象,传递Lua脚本和期望的结果类型。在execute方法中,我们传递了Lua脚本中需要的参数。这个方法将加载并执行Lua脚本,并返回结果。

通过这些步骤,你可以在Spring Boot应用程序中实现Lua脚本的编写、加载和执行。这使你能够在Redis中执行自定义操作,从而更好地控制和扩展你的应用程序。

第五部分:Lua脚本来提高Spring Boot应用程序的性能

使用Lua脚本可以显著提高Spring Boot应用程序的性能,尤其是在与Redis交互方面。以下是如何使用Lua脚本来实现性能优化的几种方法:

1.减少网络开销:

  • Redis是内存数据库,数据存储在内存中,而网络通信通常是Redis操作的性能瓶颈之一。通过使用Lua脚本,你可以将多个操作组合成一个原子操作,从而减少了多次的网络往返次数。这对于需要执行多个Redis命令以完成一个操作的情况非常有用。

2.原子操作:

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

例如,考虑一个计数器的场景,多个客户端需要原子性地增加计数。使用Lua脚本,你可以实现原子递增:

local key = KEYS[1]
local increment = ARGV[1]
return redis.call('INCRBY', key, increment)

3.复杂操作:

  • Lua脚本允许你在Redis服务器端执行复杂的数据处理。这减少了将数据传输到客户端进行处理的开销,并允许你在Redis中执行更复杂的逻辑,从而提高性能。

例如,你可以使用Lua脚本来处理存储在多个键中的数据并返回聚合结果:

local total = 0
for _, key in ipairs(KEYS) do
    local value 
= redis.call('GET', key)
    total = total + tonumber(value)
end
return total

4.事务:

  • 与Lua脚本一起使用事务可以确保一系列Redis命令的原子性执行。这对于需要一组操作要么全部成功,要么全部失败的情况非常重要。

例如,你可以使用Lua脚本在事务中执行一系列更新操作,如果其中一个操作失败,整个事务将回滚:

local key1 = KEYS[1]
local key2 = KEYS[2]
local value = ARGV[1]

redis.call('SET', key1, value)
redis.call('INCRBY', key2, value)

-- 如果这里的任何一步失败,整个事务将回滚

总之,使用Lua脚本可以大大提高Spring Boot应用程序与Redis之间的性能。它减少了网络开销,允许执行原子操作,执行复杂操作并实现事务,这些都有助于提高应用程序的性能和可伸缩性。因此,Lua脚本是在与Redis交互时实现性能优化的有力工具。

第六部分:错误处理和安全性

处理Lua脚本中的错误和确保安全性在与Redis交互时非常重要。以下是如何处理这些问题的一些建议:

错误处理:

  • 错误返回值: Lua脚本在执行期间可能会遇到错误,例如脚本本身存在语法错误,或者在脚本中的某些操作失败。Redis执行Lua脚本后,会返回脚本的执行结果。你可以检查这个结果以查看是否有错误,通常返回值是一个特定的错误标识。例如,如果脚本执行成功,返回值通常是OK,否则会有相应的错误信息。
  • 异常处理: 在Spring Boot应用程序中,你可以使用异常处理来捕获Redis执行脚本时可能抛出的异常。Spring Data Redis提供了一些异常类,如RedisScriptExecutionException,用于处理脚本执行期间的错误。你可以使用try-catch块来捕获这些异常并采取相应的措施,例如记录错误信息或执行备用操作。

安全性:

  • 参数验证: 在执行Lua脚本之前,始终验证传递给脚本的参数。确保参数是合法的,并且不包含恶意代码。避免将不受信任的用户输入直接传递给Lua脚本,因为它可能包含恶意的Lua代码。
  • 限制权限: 在Redis服务器上配置适当的权限,以限制对Lua脚本的执行。确保只有授权的用户能够执行脚本,并且不允许执行具有破坏性或不安全操作的脚本。
  • 白名单: 如果你允许动态加载Lua脚本,确保只有受信任的脚本可以执行。你可以创建一个白名单,只允许执行白名单中的脚本,防止执行未经审核的脚本。
  • 沙盒模式: 一些Redis客户端库支持将Lua脚本运行在沙盒模式下,以限制其访问和执行权限。在沙盒模式下,脚本无法执行危险操作,如文件访问。
  • 监控日志: 记录Redis执行Lua脚本的相关信息,包括谁执行了脚本以及执行的脚本内容。这有助于跟踪执行情况并发现潜在的安全问题。

总之,处理Lua脚本中的错误和确保安全性是非常重要的。通过适当的错误处理和安全措施,你可以确保Lua脚本在与Redis交互时不会引入潜在的问题,并提高应用程序的稳定性和安全性。

第七部分:最佳实践和建议

在Spring Boot项目中成功使用Lua脚本来实现Redis功能,以下是一些最佳实践和建议:

维护文档和注释:

  • 保持Lua脚本和相关代码的文档和注释清晰明了。这有助于其他开发人员理解脚本的目的和用法。

参数验证:

  • 始终验证传递给Lua脚本的参数。确保它们是合法的、安全的,并不包含恶意代码。

白名单:

  • 如果可能,建议创建一个白名单,只允许执行经过审核的脚本。这有助于防止执行未经授权的脚本。

错误处理:

  • 针对Lua脚本的执行,实施恰当的错误处理机制。捕获和处理执行期间可能出现的异常,以便记录错误信息或采取适当的措施。

测试:

  • 在实际应用之前,务必对Lua脚本进行彻底的单元测试。确保脚本按预期执行,并在各种情况下具有预期的行为。

权限控制:

  • 在Redis服务器上实施适当的权限控制,限制对Lua脚本的执行。只允许授权用户或应用程序执行脚本,并避免执行危险操作。

性能优化:

  • 使用Lua脚本来减少网络开销,执行原子操作,处理复杂操作以提高性能。确保脚本有效地减少了与Redis服务器的交互次数。

版本管理:

  • 对Lua脚本实施版本管理,以便能够轻松地追踪和回滚脚本的更改。

监控和日志:

  • 在Redis执行Lua脚本时,记录相关信息并监控执行情况。这有助于跟踪性能和安全问题。

备份方案:

  • 针对关键操作,考虑实现备份和容错方案,以防止脚本执行失败或Redis故障。

合理使用Lua脚本:

  • Lua脚本是一种强大的工具,但不应该被滥用。只在需要原子性、性能优化或复杂操作时使用它。

学习Lua编程:

  • 如果你不熟悉Lua编程语言,建议学习Lua的基础知识,以便更好地编写和理解Lua脚本。

通过遵循这些最佳实践和建议,你可以更安全、高效地使用Lua脚本来实现Redis功能,并确保你的Spring Boot项目与Redis的交互是可靠和可维护的。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
SpringBoot 动态定时任务,配置写死,太Low了SpringBoot 高效批量插入万级数据,哪种方式最强?SpringBoot AOP + Redis 延时双删功能实战Redis实战 | 使用Redis 的有序集合(Sorted Set)实现排行榜功能,和Spring Boot集成推荐35款 SpringBoot/SpringCloud 开源项目,附源码我在前端写Java SpringBoot项目忽视日志可要吃大亏,手把手教你玩转 SpringBoot 日志Netty+SpringBoot 打造一个 TCP 长连接通讯方案揭秘亚运十大黑科技!从AI、机器人到AR,杭州科技巨头们赢麻了SpringBoot 22 条最佳实践别再自己瞎写工具类了,SpringBoot内置工具类应有尽有,建议收藏!!SpringBoot 分布式验证码登录方案忽视日志吃大亏,手把手教你玩转 SpringBoot 日志!九十四 游二园微服务框架之争:Quarkus 是 SpringBoot 的替代品吗?忽视日志吃大亏,手把手教你玩转 SpringBoot 日志SpringBoot + Docker 实现一次构建到处运行~SpringBoot 接口签名校验实践教你用 SpringBoot+Redis BitMap 实现签到与统计功能SpringBoot:一个注解就能帮你下载任意对象微醉九十三 晚会牢记这16个SpringBoot 扩展接口,写出更加漂亮的代码SpringBoot 采用 JsonSerializer 和 Aop 实现可控制的数据脱敏苹果前员工造出颠覆手机的黑科技!没有屏幕,“戴”在身上也能操控一切...SpringBoot 2 种方式快速实现分库分表,轻松拿捏!中国代表团亚运会首赛!海上亚运+宝藏运动绝佳组合!第十章 美国社会的方方面面 (全文)Jenkins + Docker 一键自动化部署 SpringBoot 应用最精简流程SpringBoot+Redis BitMap 实现签到与统计功能能挣钱的,开源 SpringBoot 和 Vue 的企业级项目,代码很规范!告别繁琐:SpringBoot 拦截器与统一功能处理SpringBoot 实现动态切换数据源,这样做才更优雅!Spring Boot虚拟线程的性能还不如Webflux?故乡轶事(八)大学包的传奇
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。