Redis 使用lua脚本最全教程

收录于墨的2020~2021开发经验总结

1、redis 使用lua脚本的语法

Redis Eval 命令 - 执行 Lua 脚本

redis 127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

其中
script: 参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。
numkeys: 用于指定键名参数的个数。
key [key …]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
arg [arg …]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。

可以直接通过 redis-cli --eval执行写好的lua脚本:

redis-cli --eval /test.lua 0

2、Lua

lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

下载

print('hello world') 

-- 注释

a=1
b="abc"
c={}
d=print
c={"a","b","c"}

print(type(a))
print(type(b))
print(type(c))
print(type(d))

-- 多行注释
[[
-------- Output ------
number
string
table
function
]]

a="single 'quoted' string and double \"quoted\" string inside"
b='single \'quoted\' string and double "quoted" string inside'
c= [[ multiple line
with 'single'
and "double" quoted strings inside.]]

print(a)
print(b)
print(c)

[[
-------- Output ------

single 'quoted' string and double "quoted" string inside
single 'quoted' string and double "quoted" string inside
 multiple line
with 'single'
and "double" quoted strings inside.
]]

a,b,c,d,e = 1, 2, "three", "four", 5

a,b,c,d,e = 1, 1.123, 1E9, -123, .0008
print("a="..a, "b="..b, "c="..c, "d="..d, "e="..e)


[[
-------- Output ------

a=1     b=1.123 c=1000000000    d=-123  e=0.0008
]]

address={} -- empty address
address.Street="Wyman Street"
address.StreetNumber=360
address.AptNumber="2a"
address.City="Watertown"
address.State="Vermont"
address.Country="USA"
print(address.StreetNumber, address["AptNumber"])

-- end 结束
a=1
if a==1 then
    print ("a is one")
end

c=3
if c==1 then
    print("c is 1")
elseif c==2 then
    print("c is 2")
else
    print("c isn't 1 or 2, c is "..tostring(c))
end

a=1
b=(a==1) and "one" or "not one"
print(b)

-- b = ((a==1) ? "one" : "not one")

-- 循环
a=1
while a~=5 do -- Lua uses ~= to mean not equal
    a=a+1
    io.write(a.." ")
end

a=0
repeat
    a=a+1
    print(a)
until a==5

for a=1,6,3 do io.write(a) end

-- 14
[[ 
for (int i = 1; i < 6; i += 3) {
	printf(i);
}
]]

for key,value in pairs({1,2,3,4}) do print(key, value) end

[[
-------- Output ------

1       1
2       2
3       3
4       4
]]

a={1,2,3,4,"five","elephant", "mouse"}

for i,v in pairs(a) do print(i,v) end

[[
-------- Output ------

1       1
2       2
3       3
4       4
5       five
6       elephant
7       mouse
]]

-- break
a=0
while true do
    a=a+1
    if a==10 then
        break
    end
end

-- 函数
function myFirstLuaFunctionWithMultipleReturnValues(a,b,c)
    return a,b,c,"My first lua function with multiple return values", 1, true
end

a,b,c,d,e,f = myFirstLuaFunctionWithMultipleReturnValues(1,2,"three")
print(a,b,c,d,e,f)

[[
-------- Output ------

1       2       three   My first lua function with multiple return values       1       true

]]

-- local 局部变量
function myfunc()
    local b=" local variable"
    a="global variable"
    print(a,b)
end

function printf(fmt, ...)
    io.write(string.format(fmt, ...))
end

printf("Hello %s from %s on %s\n",
       os.getenv"USER" or "there", _VERSION, os.date())

-- Math functions:
-- math.abs, math.acos, math.asin, math.atan, math.atan2,
-- math.ceil, math.cos, math.cosh, math.deg, math.exp, math.floor,
-- math.fmod, math.frexp, math.huge, math.ldexp, math.log, math.log10,
-- math.max, math.min, math.modf, math.pi, math.pow, math.rad,
-- math.random, math.randomseed, math.sin, math.sinh, math.sqrt,
-- math.tan, math.tanh

-- String functions:
-- string.byte, string.char, string.dump, string.find, string.format,
-- string.gfind, string.gsub, string.len, string.lower, string.match,
-- string.rep, string.reverse, string.sub, string.upper

-- Table functions:
-- table.concat, table.insert, table.maxn, table.remove, table.sort

-- IO functions:
-- io.close , io.flush, io.input, io.lines, io.open, io.output, io.popen,
-- io.read, io.stderr, io.stdin, io.stdout, io.tmpfile, io.type, io.write,
-- file:close, file:flush, file:lines ,file:read,
-- file:seek, file:setvbuf, file:write

print(io.open("file doesn't exist", "r"))

-- OS functions:
-- os.clock, os.date, os.difftime, os.execute, os.exit, os.getenv,
-- os.remove, os.rename, os.setlocale, os.time, os.tmpname

-- require导入包
require( "iuplua" )
ml = iup.multiline
    {
    expand="YES",
    value="Quit this multiline edit app to continue Tutorial!",
    border="YES"
    }
dlg = iup.dialog{ml; title="IupMultiline", size="QUARTERxQUARTER",}
dlg:show()
print("Exit GUI app to continue!")
iup.MainLoop()

Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。

Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。

3、redis使用Lua

通过return 返回结果,通过redis.call执行redis命令:

eval "return redis.call('keys','*')" 0

以上命令返回所有的key,类似于直接执行 keys *

以下命令删除dict*格式的所有key值

eval "local redisKeys = redis.call('keys',KEYS[1]..'*');for i,k in pairs(redisKeys) do redis.call('del',k);end;return redisKeys;" 1 dict

展开如下

local redisKeys = redis.call('keys',KEYS[1]..'*');
for i,k in pairs(redisKeys) do 
	redis.call('del',k);
end;
return redisKeys;

以下命令删除所有key值

eval "local sum = 0;for i,k in pairs(redis.call('keys','*')) do redis.call('del', k);sum=sum+1;end; return 'clear '..sum..' key'" 0

像这样:
在这里插入图片描述

批量生产key值,设置过期时间,参数: 2、 key个数、 key前缀、 key的值、 key的过期时间(可选)

 eval "for i=1,KEYS[1],1 do local k=KEYS[2]..i; redis.call('set',k,ARGV[1]);if ARGV[2] then redis.call('expire',k,ARGV[2]) end;end;return redis.call('keys',KEYS[2]..'*');" 2 10 test 0 20

在这里插入图片描述

删除所有值为0的key,参数:0、值X

 eval "local ks = {};for i,k in pairs(redis.call('keys','*')) do local v = redis.call('get',k);if v==ARGV[1] then redis.call('del',k);table.insert(ks,k); end;end;return ks;" 0 0

删除所有永不过期的key

 eval "local ks = {};for i,k in pairs(redis.call('keys','*')) do local ttl = redis.call('ttl',k);if ttl==-1 then redis.call('del',k);table.insert(ks,k); end;end;return ks;" 0

获取所有值为0,并以test为前缀的key列表,参数:2、x、y

 eval "local ks = {};for i,k in pairs(redis.call('keys',KEYS[1]..'*')) do local v = redis.call('get',k);if v==ARGV[1] then table.insert(ks,k); end;end;return ks;" 1 test 0

redis分布式锁实现,之加锁。如果不存在lock,则设置local233,并设置过期时间为60,如果返回1表示加锁成功,返回0则加锁失败,该操作是原子操作,可以由等效命令 set lock 233 nx ex 60代替:

eval "if redis.call('get',KEYS[1]) then return 0;else redis.call('set',KEYS[1],ARGV[1]);redis.call('expire',KEYS[1],ARGV[2]);return  1;end;" 1 lock 233 60

展开如下

if redis.call('get',KEYS[1]) 
	then return 0;
else 
	redis.call('set',KEYS[1],ARGV[1]);
	redis.call('expire',KEYS[1],ARGV[2]);
	return  1;
end

redis分布式锁实现,之释放锁。如果不存在lock,则无需释放,如果存在lock并且值和传入的值一致,那么删除lock,释放成功,其他情况返回释放失败。成功:1,失败0。

eval "local v = redis.call('get',KEYS[1]);if v then if v~=ARGV[1] then return 0;end;redis.call('del',KEYS[1]);end;return 1;" 1 lock 233

展开如下

local v = redis.call('get',KEYS[1]);
if v then 
	-- 如果和传入的值不同,返回0表示失败
	if v~=ARGV[1] then 
		return 0;
	end;
	-- 删除key
	redis.call('del',KEYS[1]);
end;
return 1;

1、A程序加锁lock_a,设置值233,加锁600秒,返回1成功
在这里插入图片描述
2、B程序尝试给lock_a加锁,返回0,失败
在这里插入图片描述
3、B程序尝试释放A的锁,(这当然是不允许的),B不知道lock_a的值,释放锁失败,返回0
在这里插入图片描述
4、A程序释放锁,返回1,释放成功
在这里插入图片描述
5、B程序尝试再给lock_a加锁,加锁成功
在这里插入图片描述

4、redisTemplate执行脚本的方法封装


@Component
public class RedisUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 执行 lua 脚本
     * @author hengyumo
     * @since 2021-06-05
     *
     * @param luaScript  lua 脚本
     * @param returnType 返回的结构类型
     * @param keys       KEYS
     * @param argv       ARGV
     * @param <T>        泛型
     *           
     * @return 执行的结果
     */
    public <T> T executeLuaScript(String luaScript, Class<T> returnType, String[] keys, String... argv) {
        return redisTemplate.execute(RedisScript.of(luaScript, returnType),
                new StringRedisSerializer(),
                new GenericToStringSerializer<>(returnType),
                Arrays.asList(keys),
                (Object[])argv);
    }
}

使用很简单,以下用上边使用过的两个脚本作为示例:


    @Resource
    private RedisUtil redisUtil;

    @Test
    @SuppressWarnings("unchecked")
    public void testExecuteLuaScript() {
        String script = "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}";
        List<Object> list = (List<Object>)redisUtil.executeLuaScript(script,
                List.class, new String[] {"a", "b"}, "a", "b");
        list.forEach(x -> System.out.println(x.toString()));

        script = "for i=1,KEYS[1],1 do local k=KEYS[2]..i; redis.call('set',k,ARGV[1]);" +
                "if ARGV[2] then redis.call('expire',k,ARGV[2]) end;end;" +
                "return redis.call('keys',KEYS[2]..'*');";
        list = (List<Object>)redisUtil.executeLuaScript(script,
                List.class, new String[] {"10", "test"}, "0", "60");
        list.forEach(x -> System.out.println(x.toString()));

    }


输出结果,返回的结果是List<List>:

[a]
[b]
[a]
[b]
[test1]
[test10]
[test2]
[test3]
[test4]
[test5]
[test6]
[test7]
[test8]
[test9]

查看redis:
在这里插入图片描述

封装方法:删除以key为前缀的所有键值


    // 以下命令删除xxx*格式的所有key值
    private final static String LUA_SCRIPT_CLEAR_WITH_KEY_PRE =
            "local redisKeys = redis.call('keys',KEYS[1]..'*');" +
                    "for i,k in pairs(redisKeys) do redis.call('del',k);end;" +
                    "return redisKeys;";
    
    /**
     * @author hengyumo
     * @since 2021-06-05
     * 
     * 删除以key为前缀的所有键值
     * @param keyPre 前缀
     * @return  返回删除掉的所有key
     */
    public List<String> deleteKeysWithPre(String keyPre) {
        @SuppressWarnings("unchecked")
        List<Object> result = executeLuaScript(LUA_SCRIPT_CLEAR_WITH_KEY_PRE, List.class, new String[] {keyPre});
        return result.stream().map(x -> {
            if (x instanceof List) {
                @SuppressWarnings("unchecked")
                List<String> list = (List<String>) x;
                if (list.size() > 0) {
                    return list.get(0);
                }
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

使用很简单:


    @Test
    public void testDeleteKeysWithPre() {
        List<String> list = redisUtil.deleteKeysWithPre("DAWN");
        list.forEach(System.out::println);
    }

END

写作不易,您的小小一个👍会让我更加有动力。


那在终点之前,我愿意再爱一遍。 ——墨

  • 46
    点赞
  • 122
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值