Redis从入门到精通之Lua 脚本

本文涉及的产品
性能测试 PTS,5000VUM额度
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: Lua 是一种轻量级的脚本语言,被广泛应用于游戏开发、嵌入式系统、Web 开发、科学计算等领域。Redis 内置了 Lua 解释器,使得用户可以通过编写 Lua 脚本来扩展 Redis 的功能。在 Redis 中,可以使用 EVAL 和 EVALSHA 命令执行 Lua 脚本。

image.png

1.Redis Lua基础概念

1.1 Lua 脚本基本概念

Lua 是一种轻量级的脚本语言,被广泛应用于游戏开发、嵌入式系统、Web 开发、科学计算等领域。Redis 内置了 Lua 解释器,使得用户可以通过编写 Lua 脚本来扩展 Redis 的功能。在 Redis 中,可以使用 EVAL 和 EVALSHA 命令执行 Lua 脚本。

1.2 初始化 Lua 环境

Redis 初始化 Lua 环境的步骤如下:

  1. 调用 lua_open 函数,创建一个新的 Lua 环境。
  2. 载入指定的 Lua 函数库,包括基础库、表格库、字符串库、数学库、调试库、cjson 库、struct 库和 cmsgpack 库。
  3. 屏蔽一些可能对 Lua 环境产生安全问题的函数,比如 loadfile。
  4. 创建一个 Redis 字典,保存 Lua 脚本,并在复制(replication)脚本时使用。字典的键为 SHA1 校验和,字典的值为 Lua 脚本。
  5. 创建一个 redis 全局表格到 Lua 环境,表格中包含了各种对 Redis 进行操作的函数,包括用于执行 Redis 命令的 redis.call 和 redis.pcall 函数、用于发送日志(log)的 redis.log 函数,以及相应的日志级别(level)、用于计算 SHA1 校验和的 redis.sha1hex 函数、用于返回错误信息的 redis.error_reply 函数和 redis.status_reply 函数,以及对 Redis 自己定义的随机生成函数的替换。
  6. 创建一个对 Redis 多批量回复(multi bulk reply)进行排序的辅助函数。
  7. 对 Lua 环境中的全局变量进行保护,以免被传入的脚本修改。
  8. 创建一个无网络连接的伪客户端,专门用于执行 Lua 脚本中包含的 Redis 命令。
  9. 将 Lua 环境的指针记录到 Redis 服务器的全局状态中,等候 Redis 的调用。

除了上述步骤,Redis 还会将部分 C 函数注册到 Lua 中,例如 redis.replicate_commands 和 redis.sha1hex 函数。此外,Redis 还会将 Lua 脚本编译为 Lua 字节码,并计算出 SHA1 校验和。如果 Redis 中已经存在相同 SHA1 校验和的 Lua 脚本,则不需要再次编译,可以直接使用之前的字节码。

通过以上步骤,Redis 将 Lua 环境和 Redis 命令紧密结合在一起,为用户提供了更加灵活和强大的数据处理能力。

在 Redis 中,可以使用 SCRIPT LOAD 命令将 Lua 脚本加载到 Redis 的 Lua 解释器中,然后使用 EVALSHA 命令执行已经加载的脚本。以下是一个简单的 Lua 脚本示例:

return redis.call('get', KEYS[1])

在这个脚本中,使用 redis.call 方法调用 Redis 的 GET 命令来获取指定键的值,然后返回该值。

可以使用以下 Java 代码将这个 Lua 脚本加载到 Redis 的 Lua 解释器中:

import redis.clients.jedis.Jedis;

public class LuaScript {
   
   
    public static void main(String[] args) {
   
   
        Jedis jedis = new Jedis("localhost");
        String script = "return redis.call('get', KEYS[1])";
        String sha1 = jedis.scriptLoad(script);
        System.out.println(sha1);
    }
}

使用 scriptLoad 方法将 Lua 脚本加载到 Redis 的 Lua 解释器中,并返回该脚本的 SHA1 值。

1.2 脚本的安全性

Lua 脚本在 Redis 中的执行具有一定的安全风险,因为它可以直接访问 Redis 的数据和命令,并且可以执行任意的 Lua 代码。为了避免安全问题,可以采取以下措施:

  • 使用 EVALSHA 命令执行已经加载的 Lua 脚本,避免在网络上传输明文脚本。
  • 限制 Lua 脚本的执行权限,只允许执行一些安全的操作,比如读取指定键的值、设置指定键的值等等。
  • 对 Lua 脚本进行审核和检测,避免脚本中包含恶意代码或者不安全的操作。

1.3 脚本的执行

在 Redis 中,可以使用 EVAL 命令执行 Lua 脚本。EVAL 命令的语法如下:

EVAL script numkeys key [key ...] arg [arg ...]

其中,script 参数是 Lua 脚本的内容,numkeys 参数是传递给脚本的键的数量,key 参数是键的名字,arg 参数是传递给脚本的其他参数。在脚本中可以通过 KEYS 数组和 ARGV 数组来访问传递给脚本的键和参数。

以下是一个简单的 Java 代码示例,用于执行上述 Lua 脚本:

import redis.clients.jedis.Jedis;

public class LuaScript {
   
   
    public static void main(String[] args) {
   
   
        Jedis jedis = new Jedis("localhost");
        String script = "return redis.call('get', KEYS[1])";
        String key = "mykey";
        String result = jedis.eval(script, 1, key);
        System.out.println(result);
    }
}

创建了一个 Jedis 实例,然后定义了 Lua 脚本的内容和键的名字,最后使用 eval 方法执行 Lua 脚本并打印执行结果。

1.4 EVAL 命令的实现

在 Redis 中,EVAL 命令的实现主要分为以下几个步骤:

  1. 解析 EVAL 命令的参数,包括 Lua 脚本、键的数量、键的名字和其他参数。
  2. 检查 Lua 脚本是否已经被加载到 Redis 的 Lua 解释器中,如果没有则返回错误。
  3. 将传递给脚本的键和参数组成 KEYS 数组和 ARGV 数组,传递给 Lua 脚本中的 KEYS 和 ARGV 变量。
  4. 在 Redis 的 Lua 解释器中执行 Lua 脚本,并将执行结果返回给客户端。

定义 Lua 函数

在 Lua 脚本中,可以使用 function 关键字定义一个函数,例如:

function add(x, y)
    return x + y
end

在这个示例中,定义了一个名为 add 的函数,它有两个参数 x 和 y,返回值为 x+y。

执行 Lua 函数

在 Redis 中,可以使用 EVAL 命令执行 Lua 函数。以下是一个简单的 Java 代码示例,用于执行上述 Lua 函数:

import redis.clients.jedis.Jedis;

public class LuaScript {
   
   
    public static void main(String[] args) {
   
   
        Jedis jedis = new Jedis("localhost");
        String script = "function add(x, y)\nreturn x + y\nend\nreturn add(1, 2)";
        String result = jedis.eval(script);
        System.out.println(result);
    }
}

在这个示例中,首先创建了一个 Jedis 实例,然后定义了 Lua 函数的内容,最后使用 eval 方法执行 Lua 函数并打印执行结果。

EVALSHA 命令的实现

在 Redis 中,EVALSHA 命令用于执行已经加载到 Redis 的 Lua 脚本。EVALSHA 命令的实现与 EVAL 命令类似,主要区别在于 EVALSHA 命令需要传递 SHA1 值作为参数,用于指定已经加载的 Lua 脚本。以下是 EVALSHA 命令的语法:

EVALSHA sha1 numkeys key [key ...] arg [arg ...]

其中,sha1 参数是已经加载的 Lua 脚本的 SHA1 值。其他参数的含义与 EVAL 命令相同。

2.使用场景和注意事项

2.1 Redis Lua注意事项

Redis Lua 脚本使用时需要注意以下几点:

  1. 尽量避免使用全局变量,避免和 Redis 全局变量冲突。
  2. 避免使用 Lua 中的死循环,会导致 Redis 服务器停止响应。
  3. 避免使用 Lua 中的长时间执行操作,会导致 Redis 服务器停止响应。
  4. 在使用 Lua 脚本时,应该尽量减少对 Redis 的调用次数,以提高脚本的性能。
  5. 由于 Redis 在执行 Lua 脚本时会将其编译为字节码,因此如果多次执行相同的 Lua 脚本,则只需要编译一次即可,可以提高脚本执行的效率。

    2.2 RedisLua 脚本代码示例

    下面是一些 Redis Lua 脚本的常用代码示例:

2.2.1. 计数器

-- 将 key 的值加 1,如果 key 不存在,则将其初始值设为 0
if redis.call('exists', KEYS[1]) == 1 then
    return redis.call('incr', KEYS[1])
else
    return redis.call('set', KEYS[1], 0)
end

2.2.2. 基于 Redis 的分布式锁

-- 尝试获取锁,如果成功,则返回 1,否则返回 0
if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then
    return 1
end

-- 如果锁已经被其他客户端获取,则检查锁是否已经过期
if redis.call('ttl', KEYS[1]) == -1 then
    -- 如果锁没有过期,则说明锁已经被其他客户端获取
    return 0
end

-- 如果锁已经过期,则尝试获取锁
redis.call('set', KEYS[1], ARGV[1])
return redis.call('ttl', KEYS[1])

2.2.3. 基于 Redis 的限流器

-- 获取当前时间戳
local now = tonumber(redis.call('time')[1])

-- 删除所有时间戳小于 now - limit 的记录
redis.call('zremrangebyscore', KEYS[1], '-inf', now - tonumber(ARGV[1]))

-- 获取当前记录数
local count = tonumber(redis.call('zcard', KEYS[1]))

-- 如果记录数小于 limit,则添加一条新记录
if count < tonumber(ARGV[2]) then
    redis.call('zadd', KEYS[1], now, now)
    return 1
else
    return 0
end

3.小结

通过 Redis 的 Lua 解释器,用户可以编写 Lua 脚本来扩展 Redis 的功能。在使用 Lua 脚本时,需要注意以下几点:

  • 使用 SCRIPT LOAD 命令将 Lua 脚本加载到 Redis 的 Lua 解释器中,然后使用 EVALSHA 命令执行已经加载的脚本,避免在网络上传输明文脚本。
  • 限制 Lua 脚本的执行权限,只允许执行一些安全的操作,比如读取指定键的值、设置指定键的值等等。
  • 对 Lua 脚本进行审核和检测,避免脚本中包含恶意代码或者不安全的操作。
  • 在 Lua 脚本中可以定义函数,然后通过 EVAL 命令或 EVALSHA 命令来执行函数。
  • 在 Redis 中,EVAL 命令和 EVALSHA 命令的实现主要包括 Lua 解释器的加载和执行、传递参数等操作。
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
25天前
|
缓存 NoSQL Redis
Redis 脚本
10月更文挑战第18天
31 3
|
3月前
|
NoSQL Redis
Redis 执行 Lua保证原子性原理
Redis 执行 Lua 保证原子性原理
328 1
|
1月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
59 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
|
1月前
|
缓存 NoSQL Java
springboot的缓存和redis缓存,入门级别教程
本文介绍了Spring Boot中的缓存机制,包括使用默认的JVM缓存和集成Redis缓存,以及如何配置和使用缓存来提高应用程序性能。
95 1
springboot的缓存和redis缓存,入门级别教程
|
1月前
|
存储 消息中间件 NoSQL
Redis 入门 - C#.NET Core客户端库六种选择
Redis 入门 - C#.NET Core客户端库六种选择
59 8
|
1月前
|
缓存 分布式计算 NoSQL
大数据-43 Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
大数据-43 Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
29 2
|
1月前
|
NoSQL Java 关系型数据库
阿里 P7二面:Redis 执行 Lua,到底能不能保证原子性?
Redis 和 Lua,两个看似风流马不相及的技术点,为何能产生“爱”的火花,成为工作开发中的黄金搭档?技术面试中更是高频出现,Redis 执行 Lua 到底能不能保证原子性?今天就来聊一聊。 
79 1
|
2月前
|
存储 JSON Ubuntu
如何使用 Lua 脚本进行更复杂的网络请求,比如 POST 请求?
如何使用 Lua 脚本进行更复杂的网络请求,比如 POST 请求?
|
6月前
|
存储 NoSQL Redis
Redis的Lua脚本有什么作用?
Redis Lua脚本用于减少网络开销、实现原子操作及扩展指令集。它能合并操作降低网络延迟,保证原子性,替代不支持回滚的事务。通过脚本,代码复用率提高,且可自定义指令,如实现分布式锁,增强Redis功能和灵活性。
248 1
|
5月前
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
227 0