Redis之Lua脚本讲解-CSDN博客

1 Lua

1.1 简介

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

1.1.1 注释

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

-- 这是一条单行注释

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

1.1.2 变量

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

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

1.1.3 数据类型

基本数据类型包括整数、浮点数、字符串、布尔值和nil
其中表是一种非常灵活的数据结构使用花括号 {} 或者 table 构造函数。

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

表是Lua的核心数据结构使用花括号 {} 或者 table 构造函数。
表可以包含键值对键和值可以是任何数据类型。

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

1.1.4 控制结构

条件语句使用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 = 1, 5 do
    print(i)
end

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

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

1.1.5 函数

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

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

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

1.1.6 模块

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

1.1.7 字符串操作

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

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

1.1.8 错误处理

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

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

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

1.1.9 标准库

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

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

1.2 Redis和Lua脚本结合优点

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

  • 性能
    Lua脚本在Redis中执行避免了多次的客户端与服务器之间的通信。这可以减少网络开销提高性能特别是在需要执行多个Redis命令以完成一个操作时。
    原子性Redis保证Lua脚本的原子性执行无需担心竞态条件或并发问题。
  • 事务
    Lua脚本可以与Redis事务一起使用确保一系列命令的原子性执行。这允许将多个操作视为一个单一的事务要么全部成功要么全部失败。
  • 复杂操作
    Lua脚本提供了一种在Redis中执行复杂操作的方法允许在一个脚本中组合多个Redis命令。这对于处理复杂的业务逻辑非常有用例如计算和更新分布式计数器、实现自定义数据结构等。
  • 原子锁
    使用Lua脚本你可以实现复杂的原子锁而不仅仅是使用RedisSETNX(set if not exists)命令。这对于分布式锁的实现非常重要。
  • 减少网络开销
    对于大批量的数据处理Lua脚本可以减少客户端和服务器之间的往返次数从而显著减少网络开销。
  • 减少服务器负载
    通过将复杂的计算移至服务器端可以减轻客户端的负担降低服务器的负载。
  • 原生支持
    Redis天生支持Lua脚本因此不需要额外的插件或扩展。
  • 可读性和维护性
    Lua脚本是一种常见的脚本语言易于编写和维护。将复杂逻辑封装在脚本中有助于提高代码的可读性。

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

1.3 Lua脚本应用和调试

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

1.3.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

1.3.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

1.3.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

1.3.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的强大工具用于处理各种分布式系统需求。

1.3.5 Redis中调试Lua

RedisLua 脚本中KEYSARGV 是两个特殊的全局变量用于获取传递给脚本的键和参数。

  • KEYS变量
    KEYS 是一个数组包含了传递给脚本的所有键。可以使用 KEYS 变量来访问这些键并执行相应的操作如获取值、修改值等。
    例如local value = redis.call("GET", KEYS[1])
    在例中使用 KEYS[1] 来获取传递给脚本的第一个键并使用 redis.call 函数来获取该键的值。
  • ARGV 变量
    ARGV 是一个数组包含了传递给脚本的所有参数。可以使用 ARGV 变量来访问这些参数并执行相应的操作如解析参数、计算参数等。

redis中验证 lua脚本的两种方式

  • 登录redis后执行eval命令EVAL script numkeys key [key ...] arg [arg ...]
    例如EVAL "local key = KEYS[1]\nlocal value = ARGV[1]\nredis.call('SET', key, value)" 1 mykey myvalue
    • script是要执行的Lua脚本
    • numkeys是脚本中用到的键的数量
    • key [key ...]是脚本中用到的键的名称
    • arg [arg ...]是脚本中用到的参数
  • 不登录执行 --eval命令如果lua脚本较长可以使用redis-cli --eval的方式新建lua.lua文件在文件中输入return KEYS[1]..ARGV[1]
    linux中执行redis-cli --eval 文件路径 keys , argvs
    key和参数间需要使用逗号,隔开并且逗号前后需要占用空格

1.4 Lua脚本在Spring Boot中的实现

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

1.4.1 pom.xml和配置

首先在Spring Boot项目的pom.xml中添加Spring Data RedisLettuce或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.propertiesapplication.yml中配置Redis连接属性包括主机、端口、密码等。

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

1.4.2 创建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脚本字符串另一种是运行脚本文件。以下是这两种示例

1.4.2.1 运行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[]{10, 20}; // 传递给Lua脚本的参数
        Integer result = stringRedisTemplate.execute(script, keys, args);
        return result;
    }
}
1.4.2.2 运行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[]{10, 20}; // 传递给Lua脚本的参数
        Integer result = stringRedisTemplate.execute(script, keys, args);
        return result;
    }
}

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

1.4.3 使用Lua脚本限流

1.4.3.1 自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RedisLimitAnnotation {
 
    /**
     * key
     */
    String key() default "";
    /**
     * Key的前缀
     */
    String prefix() default "";
    /**
     * 一定时间内最多访问次数
     */
    int count();
    /**
     * 给定的时间范围 单位(秒)
     */
    int period(); 
 
}
1.4.3.2 自定义redis配置类
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;
 
import java.io.Serializable;
 
@Configuration
public class RedisConfiguration {
 
     @Bean
    public DefaultRedisScript<Long> redisluaScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limit.lua")));
        redisScript.setResultType(Long.class);
        return redisScript;
    }
 
    @Bean("redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //设置value的序列化方式为JSOn
//        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //设置key的序列化方式为String
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
 
        return redisTemplate;
    }
 
}
1.4.3.3 自定义限流AOP类
import cn.annotation.RedisLimitAnnotation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

@Slf4j
@Configuration
public class LimitRestAspect {
 
 
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
 
    @Autowired
    private DefaultRedisScript<Long> redisluaScript;
 
 
    @Pointcut(value = "@annotation(com.congge.config.limit.RedisLimitAnnotation)")
    public void rateLimit() {
 
    }
 
    @Around("rateLimit()")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        RedisLimitAnnotation rateLimit = method.getAnnotation(RedisLimitAnnotation.class);
        if (rateLimit != null) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ipAddress = getIpAddr(request);
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(ipAddress).append("-")
                    .append(targetClass.getName()).append("- ")
                    .append(method.getName()).append("-")
                    .append(rateLimit.key());
            List<String> keys = Collections.singletonList(stringBuffer.toString());
            //调用lua脚本获取返回结果这里即为请求的次数
            Long number = redisTemplate.execute(
                    redisluaScript,
                    // 此处传参只要能转为Object就行(因为数字不能直接强转为String所以不能用String序列化)
					//new GenericToStringSerializer<>(Object.class),
					// 结果的类型需要根据脚本定义此处是数字--定义的是Long类型
                	//new GenericToStringSerializer<>(Long.class)
                    keys,
                    rateLimit.count(),
                    rateLimit.period()
            );
            if (number != null && number.intValue() != 0 && number.intValue() <= rateLimit.count()) {
                logger.info("限流时间段内访问了第{} 次", number.toString());
                return joinPoint.proceed();
            }
        } else {
            return joinPoint.proceed();
        }
        throw new RuntimeException("访问频率过快被限流了");
    }
 
    /**
     * 获取请求的IP方法
     * @param request
     * @return
     */
    private static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
            }
            // 对于通过多个代理的情况第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) {
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress = "";
        }
        return ipAddress;
    } 
}

该类要做的事情和上面的两种限流措施类似不过在这里核心的限流是通过读取lua脚步通过参数传递给lua脚步实现的。

1.4.3.4 自定义lua脚本

在工程的 resources 目录下添加如下的lua脚本

local key = "rate.limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or "0")
 
if current + 1 > limit then
  return 0
else
   -- 没有超阈值将当前访问数量+1并设置2秒过期可根据自己的业务情况调整
   redis.call("INCRBY", key,"1")
   redis.call("expire", key,"2")
   return current + 1
end
1.4.3.5 添加测试接口
@RestController
public class RedisController {

    @GetMapping("/redis/limit")
    @RedisLimitAnnotation(key = "queryFromRedis",period = 1, count = 1)
    public String queryFromRedis(){
        return "success";
    } 
}

为了模拟效果这里将QPS设置为1 启动工程后提前启动redis服务调用一下接口正常的效果如下如果快速刷接口超过每秒1次的请求时报错

1.5 使用Lua提高SpringBoot性能

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

1.5.1 减少网络开销

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

1.5.2 原子操作

Lua脚本的执行是原子的这意味着在Lua脚本执行期间没有其他客户端可以插入其他操作。这使得Lua脚本在实现诸如分布式锁、计数器、排行榜等需要原子操作的情况下非常有用。
例如考虑一个计数器的场景多个客户端需要原子性地增加计数。使用Lua脚本你可以实现原子递增

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

1.5.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

1.5.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交互时实现性能优化的有力工具。

1.6 错误处理和安全性

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

1.6.1 错误处理

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

1.6.2 安全性

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

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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: redis

“Redis之Lua脚本讲解-CSDN博客” 的相关文章