Redis从入门到精通之Lua 脚本

本文涉及的产品
服务治理 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
目录
相关文章
|
4天前
|
存储 缓存 NoSQL
深入浅出Redis(十):Redis的Lua脚本
深入浅出Redis(十):Redis的Lua脚本
|
4天前
|
NoSQL 关系型数据库 MySQL
redis 入门01
redis 入门01
8 0
|
11天前
|
缓存 NoSQL Java
【Redis系列笔记】Redis入门
本文介绍了Redis常用命令,以及SpringBoot集成Spring Data Redis和Spring Cache。Spring Data Redis 提供了对 Redis 的操作方法,而 Spring Cache 则提供了基于注解的缓存功能,可以方便地将方法的返回值缓存到 Redis 中,以提高性能和减少对数据源的访问次数。这样的集成可以帮助开发者更便捷地利用 Redis 来管理应用程序的数据和缓存。
86 4
|
17天前
|
存储 缓存 NoSQL
Redis入门到通关之Redis内存淘汰(内存过期)策略
Redis入门到通关之Redis内存淘汰(内存过期)策略
31 3
|
17天前
|
存储 NoSQL Linux
Redis入门到通关之多路复用详解
Redis入门到通关之多路复用详解
20 1
|
17天前
|
存储 NoSQL Linux
Redis入门到通关之Redis5种网络模型详解
Redis入门到通关之Redis5种网络模型详解
32 1
|
17天前
|
NoSQL Ubuntu 关系型数据库
Redis入门到通关之Redis网络模型-用户空间和内核态空间
Redis入门到通关之Redis网络模型-用户空间和内核态空间
22 1
|
17天前
|
存储 NoSQL 算法
Redis入门到通关之Redis数据结构-Hash篇
Redis入门到通关之Redis数据结构-Hash篇
22 1
|
17天前
|
存储 NoSQL Redis
Redis入门到通关之Redis数据结构-Set篇
Redis入门到通关之Redis数据结构-Set篇
20 1
|
17天前
|
存储 NoSQL Redis
Redis入门到通关之Redis数据结构-ZSet篇
Redis入门到通关之Redis数据结构-ZSet篇
22 1