Redis Lua脚本语法示例
0. 前言
在学习本文之前,我是强烈建议大家了解一下我的其他两篇博客
- 《Redis 从入门到精通【进阶篇】一文学会Lua脚本》
- 《Redis 从入门到精通【进阶篇】之Lua脚本详解》
Redis通过嵌入Lua解释器,实现了对Lua脚本的执行。在执行过程中,Redis保证了脚本的原子性和阻塞性,同时通过脚本缓存和访问限制来提高性能和安全性。这使得Lua脚本成为在Redis中实现复杂逻辑的有效手段
-
嵌入式Lua解释器:Redis内部集成了Lua解释器,这使得Redis可以直接执行Lua脚本。Lua是一种轻量级的脚本语言,性能较高,且易于嵌入到其他程序中。通过嵌入Lua解释器,Redis可以实现更复杂的逻辑,同时保持高性能。
-
原子性:Redis执行Lua脚本时,会保证脚本的原子性。这意味着在执行脚本期间,Redis不会处理其他客户端的请求。这样可以确保脚本执行过程中的数据一致性,避免并发问题。
这个很关键,他保证了,我们执行命令的时候是原子执行,这个是实现分布式锁的前提。
-
阻塞性:由于Redis是单线程模型,执行Lua脚本时会阻塞其他客户端的请求。因此,为了保持Redis的高性能,需要确保Lua脚本的执行时间尽量短。如果脚本执行时间过长,可能会导致Redis性能下降。
-
脚本缓存:为了提高执行效率,Redis会缓存已执行过的Lua脚本。当客户端再次请求执行相同的脚本时,Redis可以直接从缓存中获取脚本,而无需重新加载和编译。这有助于减少脚本执行的开销。
-
限制访问:出于安全和性能的考虑,Redis对Lua脚本的访问权限进行了限制。在Lua脚本中,只能访问到传入的键和参数,不能访问Redis的全局状态。此外,脚本中不能执行一些可能导致阻塞或影响性能的命令,如
BLPOP
、SUBSCRIBE
等。
参考资料
- Redis官方文档:https://redis.io/
- Redis Lua脚本教程:https://redis.io/commands/eval
- 分布式锁的各种实现方式:https://www.cnblogs.com/linjiqin/p/8003838.html
- Redis分布式锁的正确实现方式(Redlock):http://redis.io/topics/distlock
- 分布式系统中的锁:https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
- 如何在Redis中实现分布式锁:https://www.cnblogs.com/liuyang0/p/6747257.html
- 使用Redis和Lua脚本实现分布式锁:https://segmentfault.com/a/1190000012919740
- 使用Redis实现分布式锁:https://www.cnblogs.com/0201zcr/p/5942748.html
1. Redis 执行Lua脚本原理
Redis 执行Lua脚本的核心源码位于src/scripting.c
地址:https://github.com/redis/redis/blob/6.0/src/scripting.c
Redis嵌入Lua解释器是通过将Lua源码与Redis源码结合,进行封装和适配实现的。Redis通过注册特殊的Lua命令,并调用Lua解释器的API函数,实现了Lua脚本在Redis中的执行和与Redis的交互。这样,用户可以在Redis中编写复杂的Lua脚本,利用Lua的强大功能进行数据处理和业务逻辑的实现。
1.1. 对Redis源码中嵌入Lua解释器的简要解析:
我们通过官网和源码浅析一下Redis可以执行Lua脚本的原理,其实和nginx 可以执行lua脚本的原理类似。
-
Lua源码和头文件:Redis使用的是Lua编程语言的官方实现,即LuaJIT或Lua。Redis的源代码中包含了Lua源码文件和相关的头文件,这些文件位于
src/scripting/
目录下。 -
Redis对Lua的封装:Redis通过封装Lua的API,提供了一套与Redis命令交互的接口。这些封装函数位于
src/scripting.c
文件中。
-
Redis命令的注册:Redis将Lua脚本作为一种特殊类型的命令进行处理。在
src/scripting.c
文件中,Redis通过redisCommandTable
数组注册了与Lua相关的命令,例如EVAL
、EVALSHA
等。这些命令与Lua脚本的执行和管理相关。 -
Redis命令的执行:当执行Lua脚本相关的命令时,Redis首先会解析命令参数,获取Lua脚本的内容和参数。然后,Redis将Lua脚本传递给Lua解释器进行解析和执行。
-
Lua解释器的初始化:在Redis的服务器初始化过程中,会调用
luaInit()
函数进行Lua解释器的初始化。该函数位于src/scripting.c
文件中。在初始化过程中,Redis会创建Lua解释器实例,并进行相关的设置和配置。 -
Lua脚本的执行:当执行Lua脚本相关的命令时,Redis会调用
evalGenericCommand()
函数来处理命令。该函数位于src/scripting.c
文件中。在该函数中,Redis会将Lua脚本和参数传递给Lua解释器,并调用Lua的API执行脚本。 -
Redis与Lua的交互:在Lua脚本执行期间,Redis与Lua之间可以进行数据的交互。Redis提供了一些API函数,例如
redis.call()
和redis.pcall()
,用于在Lua脚本中调用Redis命令。这些API函数允许Lua脚本直接访问Redis的键值存储和其他功能。 -
evalGenericCommand()
:执行Lua脚本相关的命令(如EVAL
和EVALSHA
)。该方法接收Lua脚本和参数,并将它们传递给Lua解释器进行执行。 -
luaCreateScriptingCommand()
:注册Lua脚本相关的命令。该方法通过调用Redis的命令注册函数,将Lua脚本命令添加到Redis的命令表中。 -
evalCommand()
:解析和执行EVAL
命令。该方法会解析命令参数,获取Lua脚本和参数,并将它们传递给evalGenericCommand()
方法进行执行。 -
loadCachedScript()
:加载缓存的Lua脚本。在执行EVALSHA
命令时,Redis会尝试从缓存中获取已加载的Lua脚本,以提高执行效率。
1.2. Redis Lua 脚本缓存机制
Redis的Lua脚本缓存机制主要包含以下步骤:
-
当一个Lua脚本被发送到Redis服务器并执行时,Redis不仅会执行这个脚本,同时它还会计算该脚本的SHA1哈希值,并将这个哈希值和对应的Lua脚本内容添加至缓存中。这个过程通常是在使用
EVAL
命令执行脚本时自动完成的。 -
当我们再次执行相同的Lua脚本时,可以使用
EVALSHA
命令,只需要发送之前计算出的SHA1哈希值,而无需再次发送整个脚本内容。 -
Redis会根据
EVALSHA
命令中的哈希值在缓存中搜索对应的Lua脚本,如果找到则直接执行,如果未找到则返回错误。 -
通过这种方式,我们可以避免重复发送大型的Lua脚本,从而减少网络传输的开销。同时,因为Redis直接执行缓存中的脚本,也无需再次解析脚本,从而提高了执行效率。
注意,虽然Redis的缓存可以提高Lua脚本的执行效率,但是它并不会缓存脚本的执行结果。这意味着,即使是相同的Lua脚本,每次执行时都会根据当前的数据重新计算结果。当前Redis并没有提供直接的方式来查看缓存的Lua脚本,也没有提供直接的方式来清除脚本缓存。当我们需要修改脚本时,只能重新计算修改后的脚本的哈希值,并使用新的哈希值来执行脚本。
2. Redis Lua脚本示例
Lua脚本是在Redis服务器上执行的脚本,它可以通过使用Redis提供的特定命令和库函数来与Redis进行交互。以下是一些常见的Redis Lua脚本示例:
1.1. 场景示例
1. 请求限流
使用Lua脚本完成原子递增并设置过期时间,用于API请求限流。
local current = tonumber(redis.call('get', KEYS[1]) or "0") -- 获取当前计数
local max_limit = tonumber(ARGV[1]) -- 最大计数限制
if current + 1 > max_limit then -- 如果超过最大限制则返回0
return 0
else
redis.call('incrby', KEYS[1], ARGV[2]) -- 否则计数+1
redis.call('expire', KEYS[1], ARGV[3]) -- 设置过期时间
return current + 1
end
2. 原子性地从一个list移动元素到另一个list:
local element = redis.call('rpop', KEYS[1]) -- 从list1右侧pop出元素
if element then
redis.call('lpush', KEYS[2], element) -- 如果元素存在则push到list2
end
return element -- 返回被移动的元素
3. 原子性地更新hash值并返回旧值:
local old_val = redis.call('hget', KEYS[1], ARGV[1]) -- 获取旧值
redis.call('hset', KEYS[1], ARGV[1], ARGV[2]) -- 设置新值
return old_val -- 返回旧值
4. 如果键不存在,则设置键值:
local exists = redis.call('exists', KEYS[1]) -- 检查键是否存在
if exists == 0 then
redis.call('set', KEYS[1], ARGV[1]) -- 如果不存在则设置键值
end
return exists -- 返回键是否存在
5. 检查一个值是否在set中,并在不存在时添加:
local exists = redis.call('sismember', KEYS[1], ARGV[1]) -- 检查值是否在set中
if exists == 0 then
redis.call('sadd', KEYS[1], ARGV[1]) -- 如果不存在则添加
end
return exists -- 返回值是否在set中
6. 删除Sorted Set中的一些元素:
local count = 0
for i, member in ipairs(ARGV) do
count = count + redis.call('zrem', KEYS[1], member) -- 删除Sorted Set中的元素
end
return count -- 返回被删除元素的数量
7. 原子性地为多个hash字段设置值:
for i = 1, #ARGV, 2 do
redis.call('hset', KEYS[1], ARGV[i], ARGV[i + 1]) -- 设置多个hash字段的值
end
return true
8. 列出并删除所有具有特定前缀的键:
local keys = redis.call('keys', ARGV[1]) -- 列出所有具有特定前缀的键
for i, key in ipairs(keys) do
redis.call('del', key) -- 删除这些键
end
return keys -- 返回被删除的键
9. 在不超过最大数量的情况下,向list中添加元素:
local size = redis.call('llen', KEYS[1]) -- 获取list的长度
if size < tonumber(ARGV[2]) then
redis.call('rpush', KEYS[1], ARGV[1]) -- 如果未超过最大数量则添加元素
end
return size -- 返回list的长度
10. 在Sorted Set中添加元素,但是元素的数量不能超过特定的数量,如果超过则删除分数最低的元素:
redis.call('zadd', KEYS[1], ARGV[2], ARGV[1]) -- 在Sorted Set中添加元素
if redis.call('zcard', KEYS[1]) > tonumber(ARGV[3]) then -- 如果元素的数量超过最大数量
return redis.call('zremrangebyrank', KEYS[1], 0, 0) -- 则删除分数最低的元素
end
return true
1.2. 常用示例
1. 批量设置多个键的值:
local keysValues = {'key1', 'value1', 'key2', 'value2', 'key3', 'value3'}
redis.call('MSET', unpack(keysValues))
2. 使用Lua的math库函数进行数学计算:
local result = math.sqrt(16)
3. 遍历集合的元素:
local members = redis.call('SMEMBERS', 'set')
for i, member in ipairs(members) do
-- 处理元素
end
4. 使用Lua的string库函数进行字符串操作:
local substring = string.sub('hello world', 1, 5)
5. 获取有序集合中指定成员的排名:
local rank = redis.call('ZRANK', 'zset', 'member')
6. 使用Lua的table库函数对Lua表进行操作:
local myTable = {key1 = 'value1', key2 = 'value2', key3 = 'value3'}
table.insert(myTable, 'value4')
7. 遍历有序集合的成员和分数(按分数递减排序):
local members = redis.call('ZREVRANGE', 'zset', 0, -1, 'WITHSCORES')
for i = 1, #members, 2 do
local member = members[i]
local score = members[i + 1]
-- 处理成员和分数
end
8. 获取哈希表的字段数量:
local count = redis.call('HLEN', 'hash')
9. 集合运算(求差集):
local difference = redis.call('SDIFF', 'set1', 'set2')
10. 使用Lua的os库函数进行操作系统相关操作:
local currentTime = os.date('%Y-%m-%d %H:%M:%S')
11. 对Redis键进行模式匹配:
local keys = redis.call('KEYS', 'pattern*')
for i, key in ipairs(keys) do
-- 处理匹配到的键
end
12. 获取有序集合指定范围内的成员和分数(按分数递减排序):
local members = redis.call('ZREVRANGEBYSCORE', 'zset', maxScore, minScore, 'WITHSCORES')
13. 设置哈希表中不存在的字段的值:
redis.call('HSETNX', 'hash', 'field', 'value')
14. 获取有序集合指定范围内的成员和分数(按分数递减排序,并限制数量):
local members = redis.call('ZREVRANGE', 'zset', 0, 10, 'WITHSCORES')
15. 使用Redis Lua脚本计算布隆过滤器的插入和查询操作:
redis.call('EVAL', 'local bf = redis.call("BFINSERT", "filter", "item"); local result = redis.call("BFCHECK", "filter", "item"); return result;', 0)
16. 使用Redis Lua脚本实现简单的计数器:
local count = redis.call('INCR', 'counter')
17. 获取Redis服务器信息:
local info = redis.call('INFO')
18. 对列表进行批量插入操作:
local values = {'value1', 'value2', 'value3'}
redis.call('RPUSH', 'list', unpack(values))
19. 使用Lua的coroutine库进行协程操作:
local co = coroutine.create(function()
-- 执行协程操作
end)
coroutine.resume(co)
20. 执行Lua脚本时传递参数:
local key = KEYS[1]
local value = ARGV[1]
redis.call('SET', key, value)
21. 使用Redis Lua脚本计算哈希表的字段数量:
local fieldsCount = 0
local fields = redis.call('HGETALL', 'hash')
for i = 1, #fields, 2 do
fieldsCount = fieldsCount + 1
end
return fieldsCount
22. 使用Lua的io库进行文件操作:
local file = io.open('/path/to/file', 'r')
local content = file:read('*a')
file:close()
23. 使用Redis Lua脚本计算集合的并集的数量:
local union = redis.call('SUNION', 'set1', 'set2')
return #union
24. 使用Lua的debug库进行调试操作:
local info = debug.getinfo(2)
25. 使用Redis Lua脚本计算有序集合的成员总数:
local count = redis.call('ZCARD', 'zset')
26. 使用Lua的os库进行系统命令执行:
local result = os.execute('command')
27. 在有序集合中插入成员的同时设置其分数:
redis.call('ZADD', 'zset', score, 'member')
28. 使用Lua的string库进行字符串分割操作:
local str = 'hello world'
local parts = {}
for part in string.gmatch(str, '%S+') do
table.insert(parts, part)
end
29. 设置Redis键的过期时间(以毫秒为单位):
redis.call('PEXPIRE', 'key', 1000)
30. 使用Redis Lua脚本计算有序集合指定分数范围内的成员数量:
local count = redis.call('ZCOUNT', 'zset', minScore, maxScore)
31. 使用Lua的bit库进行位操作:
local result = bit.band(7, 3)
32. 迭代哈希表的字段和值:
local fieldsValues = redis.call('HGETALL', 'hash')
for i = 1, #fieldsValues, 2 do
local field = fieldsValues[i]
local value = fieldsValues[i + 1]
-- 处理字段和值
end
33. 获取有序集合指定范围内的成员(按分数递减排序,并限制数量):
local members = redis.call('ZREVRANGE', 'zset', 0, 10)
34. 使用Lua的math库函数生成随机数:
local random = math.random(1, 100)
35. 获取列表的长度:
local length = redis.call('LLEN', 'list')
36. 使用Lua的string库函数进行字符串查找操作:
local index = string.find('hello world', 'world')
37. 从有序集合中移除指定分数范围内的成员:
redis.call('ZREMRANGEBYSCORE', 'zset', minScore, maxScore)
38. 使用Lua的table库函数进行表的深度复制:
local tableCopy = table.deepcopy(originalTable)
39. 检查Redis键是否存在:
local exists = redis.call('EXISTS', 'key')
40. 使用Lua的os库函数进行时间戳转换:
local timestamp = os.time()
local date = os.date('%Y-%m-%d %H:%M:%S', timestamp)
41. 获取列表中指定范围内的元素:
local elements = redis.call('LRANGE', 'list', start, stop)
42. 使用Lua的debug库函数获取当前函数信息:
local info = debug.getinfo(1, 'n')
43. 判断字符串是否为数字:
local isNumber = tonumber('123')
44. 获取有序集合指定成员的分数:
local score = redis.call('ZSCORE', 'zset', 'member')
45. 使用Lua的string库函数进行字符串替换操作:
local replacedString = string.gsub('hello world', 'world', 'redis')
46. 遍历哈希表的字段:
local fields = redis.call('HKEYS', 'hash')
for i, field in ipairs(fields) do
-- 处理字段
end
47. 获取集合的基数(元素数量):
local cardinality = redis.call('SCARD', 'set')
48. 使用Lua的io库进行文件写入操作:
local file = io.open('/path/to/file', 'w')
file:write('content')
file:close()
49. 使用Redis Lua脚本计算有序集合指定分数范围内的成员(包括分数):
local members = redis.call('ZRANGEBYSCORE', 'zset', minScore, maxScore, 'WITHSCORES')
50. 获取哈希表的所有值:
local values = redis.call('HVALS', 'hash')
51. 使用Lua的string库函数进行字符串大小写转换:
local lowercase = string.lower('HELLO')
local uppercase = string.upper('world')
52. 获取有序集合指定范围内的成员(按分数递增排序):
local members = redis.call('ZRANGE', 'zset', minScore, maxScore)
53. 使用Lua的table库函数对Lua表进行遍历:
local myTable = {key1 = 'value1', key2 = 'value2', key3 = 'value3'}
for key, value in pairs(myTable) do
-- 处理键和值
end
54. 移除列表中的元素:
redis.call('LREM', 'list', count, 'element')
55. 使用Lua的string库函数进行字符串连接操作:
local concatenatedString = string.concat('hello', 'world')
56. 迭代有序集合的成员和分数(按分数递增排序):
local members = redis.call('ZRANGE', 'zset', 0, -1, 'WITHSCORES')
for i = 1, #members, 2 do
local member = members[i]
local score = members[i + 1]
-- 处理成员和分数
end
57. 获取列表指定索引位置的元素:
local element = redis.call('LINDEX', 'list', index)
58. 使用Lua的coroutine库进行协程操作:
local co = coroutine.create(function()
-- 执行协程操作
end)
coroutine.resume(co)
59. 获取哈希表的所有字段和值:
local fieldsValues = redis.call('HGETALL', 'hash')
60. 使用Lua的bit库进行位移操作:
local result = bit.lshift(7, 2)
61. 执行Redis事务操作:
redis.call('MULTI')
redis.call('SET', 'key1', 'value1')
redis.call('SET', 'key2', 'value2')
redis.call('EXEC')
62. 获取有序集合指定成员的排名(按分数递增排序):
local rank = redis.call('ZRANK', 'zset', 'member')
63. 使用Lua的os库函数获取当前时间:
local currentTime = os.time()
64. 使用Redis Lua脚本计算列表的所有元素之和:
local sum = 0
local elements = redis.call('LRANGE', 'list', 0, -1)
for i, element in ipairs(elements) do
sum = sum + tonumber(element)
end
return sum
65. 使用Lua的string库函数进行字符串截取操作:
local substring = string.sub('hello world', 7)
66. 获取有序集合的指定排名范围内的成员(按分数递增排序):
local members = redis.call('ZRANGEBYRANK', 'zset', startRank, endRank)
67. 使用Lua的math库函数进行四舍五入操作:
local roundedNumber = math.round(3.7)
68. 删除哈希表的字段:
redis.call('HDEL', 'hash', 'field')
69. 使用Lua的string库函数进行字符串比较操作:
local result = string.compare('hello', 'world')
70. 获取有序集合中指定成员的分数排名:
local rank = redis.call('ZREVRANK', 'zset', 'member')
分布式锁
不同的语言和类库已经基于Redis实现了分布式锁,大家可以用作参考。
Redlock-rb(Ruby实现)。还有一个Redlock-rb的分支,增加了一个gem以便于分发。
Redlock-py(Python实现)。
Pottery(Python实现)。
Aioredlock(Asyncio Python实现)。
Redlock-php(PHP实现)。
PHPRedisMutex(进一步的PHP实现)。
cheprasov/php-redis-lock(PHP锁库)。
rtckit/react-redlock(异步PHP实现)。
Redsync(Go实现)。
Redisson(Java实现)。
Redis::DistLock(Perl实现)。
Redlock-cpp(C++实现)。
Redis-plus-plus(C++实现)。
Redlock-cs(C#/.NET实现)。
RedLock.net(C#/.NET实现)。包括异步和锁扩展支持。
ScarletLock(C# .NET实现,具有可配置的数据存储)。
Redlock4Net(C# .NET实现)。
node-redlock(NodeJS实现)。包括对锁扩展的支持。
Deno DLM(Deno实现)
Rslock(Rust实现)。包括异步和锁扩展支持。文章来源:https://www.toymoban.com/news/detail-663314.html
3. Redis从入门到精通系列文章
- 《Redis 从入门到精通【进阶篇】之Lua脚本详解》
- 《Redis 从入门到精通【实践篇】SpringBoot Redis 配置多数据源》
- 《Redis 从入门到精通【进阶篇】三分钟了解Redis地理位置数据结构GeoHash》
- 《Redis 从入门到精通【进阶篇】一文学会Lua脚本》
- 《Redis使用Lua脚本和Redisson来保证库存扣减中的原子性和一致性》
- 《SpringBoot Redis 使用Lettuce和Jedis配置哨兵模式》
- 《Redis【应用篇】之RedisTemplate基本操作》
- 《Redis 从入门到精通【实践篇】之SpringBoot配置Redis多数据源》
- 《Redis 从入门到精通【进阶篇】之三分钟了解Redis HyperLogLog 数据结构》
- 《Redis 从入门到精通【进阶篇】之三分钟了解Redis地理位置数据结构GeoHash》
- 《Redis 从入门到精通【进阶篇】之高可用哨兵机制(Redis Sentinel)详解》
- 《Redis 从入门到精通【进阶篇】之redis主从复制详解》
- 《Redis 从入门到精通【进阶篇】之Redis事务详解》
- 《Redis从入门到精通【进阶篇】之对象机制详解》
- 《Redis从入门到精通【进阶篇】之消息传递发布订阅模式详解》
- 《Redis从入门到精通【进阶篇】之持久化 AOF详解》
- 《Redis从入门到精通【进阶篇】之持久化RDB详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
-
《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》
大家好,我是冰点,今天的Redis Lua脚本执行原理和语法示例,全部内容就是这些。如果你有疑问或见解可以在评论区留言。
文章来源地址https://www.toymoban.com/news/detail-663314.html
到了这里,关于Redis Lua脚本执行原理和语法示例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!