Redis之Lua脚本讲解
这里写自定义目录标题
- 1 Lua
- 1.1 简介
- 1.1.1 注释
- 1.1.2 变量
- 1.1.3 数据类型
- 1.1.4 控制结构
- 1.1.5 函数
- 1.1.6 模块
- 1.1.7 字符串操作
- 1.1.8 错误处理
- 1.1.9 标准库
- 1.2 Redis和Lua脚本结合优点
- 1.3 Lua脚本应用和调试
- 1.3.1 缓存更新
- 1.3.2 原子操作
- 1.3.3 数据处理
- 1.3.4 分布式锁
- 1.3.5 Redis中调试Lua
- 1.4 Lua脚本在Spring Boot中的实现
- 1.4.1 pom.xml和配置
- 1.4.2 创建Lua脚本
- 1.4.2.1 运行Lua脚本字符串
- 1.4.2.2 运行Lua脚本文件
- 1.4.3 使用Lua脚本限流
- 1.4.3.1 自定义注解
- 1.4.3.2 自定义redis配置类
- 1.4.3.3 自定义限流AOP类
- 1.4.3.4 自定义lua脚本
- 1.4.3.5 添加测试接口
- 1.5 使用Lua提高SpringBoot性能
- 1.5.1 减少网络开销
- 1.5.2 原子操作
- 1.5.3 复杂操作
- 1.5.4 事务
- 1.6 错误处理和安全性
- 1.6.1 错误处理
- 1.6.2 安全性
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 thenprint("未成年")
elseif age >= 18 and age < 65 thenprint("成年")
elseprint("老年")
end
循环结构:Lua支持for循环、while循环和repeat…until循环。
for i = 1, 5 doprint(i)
endlocal count = 0
while count < 3 doprint("循环次数: " .. count)count = count + 1
endrepeatprint("至少执行一次")
until count > 5
1.1.5 函数
函数在Lua中使用function关键字定义,可以接受参数并返回值。
function add(a, b)return a + b
endlocal 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 thenprint("执行成功")
elseprint("错误信息: " .. 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脚本,你可以实现复杂的原子锁,而不仅仅是使用Redis的SETNX(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) dolocal 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
elsereturn false -- 无法获取锁
这些场景只是Lua脚本在Redis中的应用之一。Lua脚本允许你在Redis中执行更复杂的操作,而无需进行多次的网络通信,从而提高性能和可伸缩性,同时确保数据的一致性和原子性。这使得Lua成为Redis的强大工具,用于处理各种分布式系统需求。
1.3.5 Redis中调试Lua
在 Redis 的 Lua 脚本中,KEYS 和 ARGV 是两个特殊的全局变量,用于获取传递给脚本的键和参数。
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 myvaluescript:是要执行的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 Redis和Lettuce(或Jedis)客户端的使用。以下是编写、加载和执行Lua脚本的步骤和示例:
1.4.1 pom.xml和配置
首先,在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
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提供的StringRedisTemplate或LettuceConnectionFactory。
提供两种不同的示例来执行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 {@Autowiredprivate 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 {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate 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 {@Beanpublic 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的序列化方式为StringredisTemplate.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 {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate 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 thenreturn 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) dolocal 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交互时不会引入潜在的问题,并提高应用程序的稳定性和安全性
相关文章:
Redis之Lua脚本讲解
这里写自定义目录标题 1 Lua1.1 简介1.1.1 注释1.1.2 变量1.1.3 数据类型1.1.4 控制结构1.1.5 函数1.1.6 模块1.1.7 字符串操作1.1.8 错误处理1.1.9 标准库 1.2 Redis和Lua脚本结合优点1.3 Lua脚本应用和调试1.3.1 缓存更新1.3.2 原子操作1.3.3 数据处理1.3.4 分布式锁1.3.5 Re…...
redis archive github
https://github.com/redis/redis/releases/tag/7.2.2https://github.com/redis/redis/releases/tag/7.2.2...
3台Centos7快速部署Kafka集群
首先,我要说,Kafka 是强依赖于 ZooKeeper 的,所以在设置 Kafka 集群之前,我们首先需要设置一个 ZooKeeper 集群。 部署ZooKeeper需要安装jdk yum install java-1.8.0-openjdk 安装完以后 下面是详细的步骤: 1. 安装和…...
最小栈(C++解法)
题目 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。i…...
Python 算法高级篇:堆排序的优化与应用
Python 算法高级篇:堆排序的优化与应用 引言 1. 什么是堆?2. 堆的性质3. 堆排序的基本原理4. 堆排序的 Python 实现5. 堆排序的性能和优化6. 堆排序的实际应用7. 总结 引言 堆排序是一种高效的排序算法,它基于数据结构中的堆这一概念。堆排序…...
视频下载软件 Downie4 mac中文介绍
Downie mac是一款Mac平台上非常实用的视频下载工具。它支持下载各种视频网站上的视频,并且具有快速、稳定、易于使用的特点。 Downie支持下载各种视频网站上的视频,包括YouTube、Vimeo、Netflix、Hulu、Amazon等等。它具有快速、稳定的下载速度ÿ…...
计算机操作系统重点概念整理-第一章 计算机系统概述【期末复习|考研复习】
第一章 计算机系统概述 【期末复习|考研复习】 计算机操作系统系列文章传送门: 第一章 计算机系统概述 第二章 进程管理 第三章 进程同步 第四章 内存管理 第五章 文件管理 第六章 输出输出I/O管理 文章目录 第一章 计算机系统概述 【期末复习|考研复习】前言一、计…...
树莓派基金会近日发布了新版基于 Debian 的树莓派操作系统
树莓派基金会(Raspberry Pi Foundation)近日发布了新版基于 Debian 的树莓派操作系统(Raspberry Pi OS),为树莓派单板电脑带来了新的书虫基础和一些重大变化。 新版 Raspberry Pi OS 的最大变化是它现在基于最新的 Deb…...
Web项目如何做单元测试
你可能会用单元测试框架,python的unittest、pytest,Java的Junit、testNG等。 那么你会做单元测试么!当然了,这有什么难的? test_demo.py def inc(x):return x 1def test_answer():assert inc(3) 4 inc() 是定义的…...
MySQL主从复制(基于GTID--事务ID方式)
目录 一、GTID相关概念1.GTID 是什么?2.GTID主从复制方式概念3.GTID的优缺点 二、GTID工作原理三、部署主从复制四、测试同步1.主库上新建数据库2.从库上查看是否同步成功 五、重设从库六、常见故障七、故障切换八、GTID的一些疑问1.为什么基于GTID的同步也要打开bi…...
3.72 Command Buffer及URP概述
一、Command Buffer 1.概念 CommandBuffer携带一系列的渲染命令,依赖相机,用来拓展渲染管线的渲染效果。而且可以指定在相机渲染的某个点执行本身的拓展渲染。Command buffers也可以结合屏幕后期效果使用。 简单来说就是可以在渲染流程中插入一些自定…...
分布式理论和分布式锁知识点总结
文章目录 (一) 分布式理论算法和协议1)CAP理论总结 2)BASE理论BASE 理论的核心思想基本可用软状态最终一致性 3)Paxos算法Basic Paxos 算法4) Raft算法1 拜占庭将军 5)Gossip协议 (二) 分布式锁分布式锁应该具备哪些条…...
IOC课程整理-17 Spring事件
1. Java 事件/监听器编程模型 2. 面向接口的事件/监听器设计模式 3. 面向注解的事件/监听器设计模式 4. Spring 标准事件-ApplicationEvent 5. 基于接口的 Spring 事件监听器 6. 基于注解的 Spring 事件监听器 7. 注册 Spring ApplicationListener 8. Spring 事件发布器 9. Spr…...
大数据Flink(一百零五):SQL性能调优
文章目录 SQL性能调优 一、 MiniBatch 聚合...
ESP8266,手机与电脑之间的TCP通讯
电脑端运行通讯猫调试助手,作为服务端: 电脑端 电脑的IP地址是: 192.168.2.232 手机与电脑之间的TCP通讯 手机端运行网络调试精灵,作为客户端: 手机端 如果从手机端点击"发送"按钮,则也会将"ghhh东方红广场"几个字发送到电脑上(服务端). ESP8266作为客户…...
vue的数据监听是如何实现的?
Vue的数据监听是通过数据劫持和发布订阅模式来实现的。 数据劫持:Vue通过使用Object.defineProperty()方法来劫持数据对象的属性,并使用getter和setter来监听属性的变化。当属性被修改时,setter方法会被调用,从而触发相应的监听函…...
埋点日志解决方案——Golang+Gin+Sarama VS Java+SpringCloudGateway+ReactorKafka
埋点日志解决方案——GolangGinSarama VS JavaSpringCloudGatewayReactorKafka 之前我就写过几篇OpenRestylua-kafka-client将埋点数据写入Kafka的文章,如下: Lua将Nginx请求数据写入Kafka——埋点日志解决方案 python定时任务执行shell脚本切割Nginx…...
LeetCode 541 反转字符串 II 简单
题目 - 点击直达 1. 541 反转字符串 II 简单1. 题目详情1. 原题链接2. 题目要求3. 基础框架 2. 解题思路1. 思路分析2. 时间复杂度3. 代码实现 1. 541 反转字符串 II 简单 1. 题目详情 给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个…...
从入门到精通:深入了解CSS中的Grid网格布局技巧和应用!
🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 ⭐ 专栏简介 📘 文章引言 一…...
Android Studio Giraffe 添加 maven { url “https://jitpack.io“ }报错
Android Studio Giraffe 添加 maven { url “https://jitpack.io” }报错 settings.gradle.kts:13:21: Unexpected tokens (use ; to separate expressions on the same line)解决方法 新版maven写法发生了改变: maven { url uri("https://jitpack.io"…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
