• SpringBoot+Redis+Lua


    Lua脚本

    • Lua脚本在Redis中执行,避免了多次的客户端与服务器之间的通信。这可以减少网络开销,提高性能,特别是在需要执行多个Redis命令以完成一个操作时。原子性:Redis保证Lua脚本的原子性执行,无需担心竞态条件或并发问题。
    • Lua脚本可以与Redis事务一起使用,确保一系列命令的原子性执行。这允许你将多个操作视为一个单一的事务,要么全部成功,要么全部失败。
    • Lua脚本提供了一种在Redis中执行复杂操作的方法,允许你在一个脚本中组合多个Redis命令。这对于处理复杂的业务逻辑非常有用,例如计算和更新分布式计数器、实现自定义数据结构等。
    • 使用Lua脚本,你可以实现复杂的原子锁,而不仅仅是使用Redis的SETNX(set if not exists)命令。
    • 对于大批量的数据处理,Lua脚本可以减少客户端和服务器之间的往返次数,从而显著减少网络开销。
    • 通过将复杂的计算移至服务器端,可以减轻客户端的负担,降低服务器的负载。
    • Redis天生支持Lua脚本,因此不需要额外的插件或扩展。
    • Lua脚本是一种常见的脚本语言,易于编写和维护。
    -- 局部变量
    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 age < 18 then
        print("未成年")
    elseif age >= 18 and age < 65 then
        print("成年")
    else
        print("老年")
    end
    
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    -- 表数据结构,用{}定义,包含键值对,键值对可以是任何数据类型
    local person = {name = "jordan",age = 23,hobbies = {"basketball","baseball"}}
    print("姓名:"..person.name)
    print("年龄:"..person.age)
    
    -- 模块化,通过require关键字加载
    
    -- 标准库,文件操作,网络编程,正则表达式,事件处理等,通过内置模块,如io/socket等
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    -- 字符串处理
    local text = "Lua programming"
    local sub = string.sub(text,1,3)
    print(sub)
    
    -- 错误处理,使用pcall汉书来包裹可能引发异常的代码块,以捕获并处理错误,通常与assert一起使用
    local success,result = pcall(function()
    	error("出错了!")
    end)
    
    if success then
    	print("执行成功")
    else
    	print("错误信息:"..result)
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    应用场景

    一、缓存更新

    在缓存中存储某些数据,但需要定期或基于条件更新这些数据,同时确保在更新期间不会发生并发问题
    使用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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    二、原子操作

    需要执行多个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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    -- 考虑一个计数器的场景,多个客户端需要原子性地增加计数
    local key = KEYS[1]
    local increment = ARGV[1]
    return redis.call('INCRBY',key,increment)
    
    • 1
    • 2
    • 3
    • 4

    三、数据处理

    需要对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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    --[[
    	Lua脚本允许你在Redis服务器端执行复杂的数据处理。这减少了将数据传输到客户端进行处理的开销,并允许你在Redis中执行更复杂的逻辑,从而提高性能
    ]]
    local total = 0
    for _,key in ipairs(KEYS) do
    	local value = redis.call('GET',key)
    	total = total + tonumber(value)
    end
    return total
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    四、分布式锁

    使用Lua脚本,你可以在Redis中执行复杂的数据处理,而不必将数据传输到客户端进行处理,减少网络开销
    使用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 --无法获取锁
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    五、事务

    local key1 = KEYS[1]
    local key2 = KEYS[2]
    local value = ARGV[1]
    
    redis.call('SET', key1, value)
    redis.call('INCRBY', key2, value)
    
    -- 如果这里的任何一步失败,整个事务将回滚
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    SpringBoot实现Lua脚本

    Spring Data Redis和Lettuce(或Jedis)客户端的使用

    一、添加依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    <dependency>
        <groupId>io.lettuce.coregroupId>
        <artifactId>lettuce-coreartifactId> 
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二、application.properties或application.yml配置Redis连接

    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=yourPassword
    
    • 1
    • 2
    • 3

    三、创建Lua脚本

    myscript.lua

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

    四、编写代码执行Lua脚本

    使用Spring Data Redis提供的StringRedisTemplate或LettuceConnectionFactory

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

    /**
    	运行Lua脚本字符串
    */
    @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    创建一个文件myscript.lua,然后通过类运行该文件

    @Service
    public class LuaScriptService{
    	
    	@Autowired
    	private StringRedisTemplate.stringRedisTemplate;
    
    	@Autowired
    	private ResourceLoader resourceLoader;
    
    	public Integer executeLuaScriptFromFile(){
    	
    		Resource resource = resourceLoader.getResource("classpath:mysript.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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    R语言ggplot2可视化:使用ggpubr包的ggline函数可视化折线图(点线图、line plot)
    java8 stream list 操作
    【MySQL】索引特性
    解决 Zlibrary 卡死/找不到域名/达到限额问题,Zlibrary最新地址
    微服务技术栈-初识Docker
    122. 买卖股票的最佳时机 II
    图片OCR转表格:终极攻略,优缺点全解析
    前端批量下载文件(干货)
    [西湖论剑 2022]real_ez_node
    C++交叉编译grpc
  • 原文地址:https://blog.csdn.net/usa_washington/article/details/134387258