0
点赞
收藏
分享

微信扫一扫

高性能伪事务之Lua in Redis

EVAL简介

Redis2.6加入了对Lua脚本的支持。Lua脚本可以被用来扩展Redis的功能,并提供更好的性能。

在《​​Redis拾遗​​​》中曾经引用了《Redis in Action》中的一套悲观锁的实现,使用Lua脚本实现同样的功能,性能提高1倍以上。在另一个自动补全的例子中,使用Lua脚本比​​WATH/MULTI/EXEC​​快了20倍。

EVAL 和 EVALSHA 命令是从 Redis 2.6.0 版本开始的,使用内置的 Lua 解释器,可以对 Lua 脚本进行求值。

EVAL的第一个参数是一段 Lua 5.1 脚本程序。 这段Lua脚本不需要(也不应该)定义函数。它运行在 Redis 服务器中。

EVAL的第二个参数是参数的个数,后面的参数(从第三个参数),表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。

在命令的最后,那些不是键名参数的附加参数 arg [arg …] ,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。

举例说明:

> eval(232, 232, 232); background: rgb(249, 249, 249);">

redis.call()
redis.pcall()

redis.call() 与 redis.pcall()很类似, 他们唯一的区别是当redis命令执行结果返回错误时, redis.call()将返回给调用者一个错误,而redis.pcall()会将捕获的错误以Lua表的形式返回

redis.call() 和 redis.pcall() 两个函数的参数可以是任意的 Redis 命令:

> eval('set','foo','bar')" 0
OK

需要注意的是,上面这段脚本的确实现了将键 foo 的值设为 bar 的目的,但是,它违反了 EVAL 命令的语义,因为脚本里使用的所有键都应该由 KEYS 数组来传递,就像这样:

> eval('set',KEYS[1],'bar')" 1 foo
OK

要求使用正确的形式来传递键(key)是有原因的,因为不仅仅是 EVAL 这个命令,所有的 Redis 命令,在执行之前都会被分析,籍此来确定命令会对哪些键进行操作。

因此,对于 EVAL 命令来说,必须使用正确的形式来传递键,才能确保分析工作正确地执行。 除此之外,使用正确的形式来传递键还有很多其他好处,它的一个特别重要的用途就是确保 Redis 集群可以将你的请求发送到正确的集群节点。 (对 Redis 集群的工作还在进行当中,但是脚本功能被设计成可以与集群功能保持兼容。)不过,这条规矩并不是强制性的, 从而使得用户有机会滥用(abuse) Redis 单实例配置(single instance configuration),代价是这样写出的脚本不能被 Redis 集群所兼容。

Lua 脚本能返回一个值,这个值能按照一组转换规则从Lua转换成redis的返回类型。

Lua 数据类型和 Redis 数据类型之间转换

当 Lua 通过 call() 或 pcall() 函数执行 Redis 命令的时候,命令的返回值会被转换成 Lua 数据结构。 同样地,当 Lua 脚本在 Redis 内置的解释器里运行时,Lua 脚本的返回值也会被转换成 Redis 协议(protocol),然后由 EVAL 将值返回给客户端。

数据类型之间的转换遵循这样一个设计原则:如果将一个 Redis 值转换成 Lua 值,之后再将转换所得的 Lua 值转换回 Redis 值,那么这个转换所得的 Redis 值应该和最初时的 Redis 值一样。

换句话说, Lua 类型和 Redis 类型之间存在着一一对应的转换关系。

Redis 到 Lua 的转换表。

  • Redis integer reply -> Lua number / Redis 整数转换成 Lua 数字
  • Redis bulk reply -> Lua string / Redis bulk 回复转换成 Lua 字符串
  • Redis multi bulk reply -> Lua table (may have other Redis data types nested) / Redis 多条 bulk 回复转换成 Lua 表,表内可能有其他别的 Redis 数据类型
  • Redis status reply -> Lua table with a single ok field containing the status / Redis 状态回复转换成 Lua 表,表内的 ok 域包含了状态信息
  • Redis error reply -> Lua table with a single err field containing the error / Redis 错误回复转换成 Lua 表,表内的 err 域包含了错误信息
  • Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type / Redis 的 Nil 回复和 Nil 多条回复转换成 Lua 的布尔值 false

Lua 到 Redis 的转换表。

  • Lua number -> Redis integer reply (the number is converted into an integer) / Lua 数字转换成 Redis 整数
  • Lua string -> Redis bulk reply / Lua 字符串转换成 Redis bulk 回复
  • Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any) / Lua 表(数组)转换成 Redis 多条 bulk 回复
  • Lua table with a single ok field -> Redis status reply / 一个带单个 ok 域的 Lua 表,转换成 Redis 状态回复
  • Lua table with a single err field -> Redis error reply / 一个带单个 err 域的 Lua 表,转换成 Redis 错误回复
  • Lua boolean false -> Redis Nil bulk reply. / Lua 的布尔值 false 转换成 Redis 的 Nil bulk 回复

从 Lua 转换到 Redis 有一条额外的规则,这条规则没有和它对应的从 Redis 转换到 Lua 的规则:

  • Lua boolean true -> Redis integer reply with value of 1. / Lua 布尔值 true 转换成 Redis 整数回复中的 1

还有下面两点需要重点注意:

  • lua中整数和浮点数之间没有什么区别。因此,我们始终Lua的数字转换成整数的回复,这样将舍去小数部分。如果你想从Lua返回一个浮点数,你应该将它作为一个字符串(见比如ZSCORE命令)。
  • There is no simple way to have nils inside Lua arrays, this is a result of Lua table semantics, so when Redis converts a Lua array into Redis protocol the conversion is stopped if a nil is encountered.

以下是几个类型转换的例子:

> eval(integer) 10

> eval(integer) 1
2) (integer) 2
3) 1) (integer) 3
2) "Hello World!"

> eval('get','foo')" 0
"bar"

最后一个例子展示如果是Lua直接命令调用它是如何可以从redis.call()或redis.pcall()接收到准确的返回值。

下面的例子我们可以看到浮点数和nil将怎么样处理:

> eval(integer) 1
2) (integer) 2
3) (integer) 3
4) "foo"

正如你看到的 3.333 被转换成了3,并且 nil后面的字符串bar没有被返回回来。

  • 返回redis类型的辅助函数

有两个辅助函数从Lua返回Redis的类型。

  • redis.error_reply(error_string) returns an error reply. This function simply returns the single field table with the err field set to the specified string for you.
  • redis.status_reply(status_string) returns a status reply. This function simply returns the single field table with the ok field set to the specified string for you.

There is no difference between using the helper functions or directly returning the table with the specified format, so the following two forms are equivalent:

return {err="My Error"}
return redis.error_reply("My Error")

脚本的原子性

Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行: 当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。 这和使用 ​​MULTI​​​ / ​​EXEC​​ 包围的事务很类似。 在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。 另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难, 因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心, 因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。

错误处理

前面的命令介绍部分说过, redis.call() 和 redis.pcall() 的唯一区别在于它们对错误处理的不同。

当 redis.call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因:

> del foo
(integer) 1
> lpush foo a
(integer) 1
> eval('get','foo')" 0
(error) ERR Error running script (call to f_6b1bf486c81ceb7edf3c093f4c48582e38c0e791): ERR Operation against a key holding the wrong kind of value

和 redis.call() 不同, redis.pcall() 出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误:

redis 127.0.0.1:6379> EVAL "return redis.pcall('get', 'foo')" 0
(error) ERR Operation against a key holding the wrong kind of value

带宽和 EVALSHA

​EVAL​​ 命令要求你在每次执行脚本的时候都发送一次脚本主体(script body)。Redis 有一个内部的缓存机制,因此它不会每次都重新编译脚本,不过在很多场合,付出无谓的带宽来传送脚本主体并不是最佳选择。

为了减少带宽的消耗, Redis 实现了 ​​EVAL​​ 一样,都用于对脚本求值,但它接受的第一个参数不是脚本,而是脚本的 SHA1 校验和(sum)。

EVALSHA 命令的表现如下:

如果服务器还记得给定的 SHA1 校验和所指定的脚本,那么执行这个脚本 如果服务器不记得给定的 SHA1 校验和所指定的脚本,那么它返回一个特殊的错误,提醒用户使用 EVAL 代替 EVALSHA 以下是示例:

> set foo bar
OK

> eval('get','foo')" 0
"bar"

> eval(error) `NOSCRIPT` No matching script. Please use [EVAL](/commands/eval(231, 243, 237); padding: 0px 3px; border-radius: 4px; overflow-wrap: break-word; text-indent: 0px;">​EVAL​​​ ,并期望着要使用的脚本已经保存在服务器上了,只有当 NOSCRIPT 错误发生时,才使用 ​​EVAL​​ 命令重新发送脚本,这样就可以最大限度地节省带宽。

这也说明了执行 ​​EVAL​​ 命令时,使用正确的格式来传递键名参数和附加参数的重要性:因为如果将参数硬写在脚本中,那么每次当参数改变的时候,都要重新发送脚本,即使脚本的主体并没有改变,相反,通过使用正确的格式来传递键名参数和附加参数,就可以在脚本主体不变的情况下,直接使用 EVALSHA 命令对脚本进行复用,免去了无谓的带宽消耗。

脚本缓存

Redis 保证所有被运行过的脚本都会被永久保存在脚本缓存当中,这意味着,当 ​​EVAL​​​ 命令在一个 Redis 实例上成功执行某个脚本之后,随后针对这个脚本的所有 ​Redis 提供了以下几个 SCRIPT 命令,用于对脚本子系统(scripting subsystem)进行控制:

​SCRIPT FLUSH​​​ :清除所有脚本缓存 ​​SCRIPT EXISTS​​​ :根据给定的脚本校验和,检查指定的脚本是否存在于脚本缓存 ​​SCRIPT LOAD​​​ :将一个脚本装入脚本缓存,但并不立即运行它 ​​SCRIPT KILL​​ :杀死当前正在运行的脚本

纯函数脚本

在编写脚本方面,一个重要的要求就是,脚本应该被写成纯函数(pure function)。

也就是说,脚本应该具有以下属性:

使用系统时间(system time),调用像 RANDOMKEY 那样的随机命令,或者使用 Lua 的随机数生成器,类似以上的这些操作,都会造成脚本的求值无法每次都得出同样的结果。

为了确保脚本符合上面所说的属性, Redis 做了以下工作:

尽管有那么多的限制,但用户还是可以用一个简单的技巧写出带随机行为的脚本(如果他们需要的话)。

假设现在我们要编写一个 Redis 脚本,这个脚本从列表中弹出 N 个随机数。一个 Ruby 写的例子如下:

require 'rubygems'
require 'redis'

r = Redis.new

RandomPushScript = <<EOF
local i = tonumber(ARGV[1])
local res
while (i > 0) do
res = redis.call('lpush',KEYS[1],math.random())
i = i-1
end
return res
EOF

r.del(:mylist)
puts r.eval(RandomPushScript,[:mylist],[10,rand(2**32)])

这个程序每次运行都会生成带有以下元素的列表:

> lrange mylist 0 -1
1) "0.74509509873814"
2) "0.87390407681181"
3) "0.36876626981831"
4) "0.6921941534114"
5) "0.7857992587545"
6) "0.57730350670279"
7) "0.87046522734243"
8) "0.09637165539729"
9) "0.74990198051087"
10) "0.17082803611217"

上面的 Ruby 程序每次都只生成同样的列表,用途并不是太大。那么,该怎样修改这个脚本,使得它仍然是一个纯函数(符合 Redis 的要求),但是每次调用都可以产生不同的随机元素呢?

一个简单的办法是,为脚本添加一个额外的参数,让这个参数作为 Lua 的随机数生成器的 seed 值,这样的话,只要给脚本传入不同的 seed ,脚本就会生成不同的列表元素。

以下是修改后的脚本:

RandomPushScript = <<EOF
local i = tonumber(ARGV[1])
local res
math.randomseed(tonumber(ARGV[2]))
while (i > 0) do
res = redis.call('lpush',KEYS[1],math.random())
i = i-1
end
return res
EOF

r.del(:mylist)
puts r.eval(RandomPushScript,1,:mylist,10,rand(2**32))

尽管对于同样的 seed ,上面的脚本产生的列表元素是一样的(因为它是一个纯函数),但是只要每次在执行脚本的时候传入不同的 seed ,我们就可以得到带有不同随机元素的列表。

Seed 会在复制(replication link)和写 AOF 文件时作为一个参数来传播,保证在载入 AOF 文件或附属节点(slave)处理脚本时, seed 仍然可以及时得到更新。

注意,Redis 实现保证 math.random 和 math.randomseed 的输出和运行 Redis 的系统架构无关,无论是 32 位还是 64 位系统,无论是小端(little endian)还是大端(big endian)系统,这两个函数的输出总是相同的。

全局变量保护

为了防止不必要的数据泄漏进 Lua 环境, Redis 脚本不允许创建全局变量。如果一个脚本需要在多次执行之间维持某种状态,它应该使用 Redis key 来进行状态保存。

企图在脚本中访问一个全局变量(不论这个变量是否存在)将引起脚本停止, ​​EVAL​​命令会返回一个错误:

redis 127.0.0.1:6379> eval(error) ERR Error running script (call to f_933044db579a2f8fd45d8065f04a8d0249383e57): user_script:1: Script attempted to create global variable 'a'

Lua 的 debug 工具,或者其他设施,比如打印(alter)用于实现全局保护的 meta table ,都可以用于实现全局变量保护。

实现全局变量保护并不难,不过有时候还是会不小心而为之。一旦用户在脚本中混入了 Lua 全局状态,那么 AOF 持久化和复制(replication)都会无法保证,所以,请不要使用全局变量。

避免引入全局变量的一个诀窍是:将脚本中用到的所有变量都使用 local 关键字定义为局部变量。

使用选择内部脚本

在正常的客户端连接里面可以调用​​SELECT​​选择内部的Lua脚本,但是Redis 2.8.11和Redis 2.8.12在行为上有一个微妙的变化。在2.8.12之前,会将脚本传送到调用脚本的当前数据库。从2.8.12开始Lua脚本只影响脚本本身的执行,但不修改当前客户端调用脚本时选定的数据库。

从补丁级发布的语义变化是必要的,因为旧的行为与Redis复制层固有的不相容是错误的原因。

可用库

Redis Lua解释器可用加载以下Lua库:

每一个Redis实例都拥有以上的所有类库,以确保您使用脚本的环境都是一样的。

struct, CJSON 和 cmsgpack 都是外部库, 所有其他库都是标准。 Lua库。

struct 库

struct 是一个Lua装箱/拆箱的库

Valid formats:
> - big endian
< - little endian
![num] - alignment
x - pading
b/B - signed/unsigned byte
h/H - signed/unsigned short
l/L - signed/unsigned long
T - size_t
i/In - signed/unsigned integer with size `n' (default is size of int)
cn - sequence of `n' chars (from/to a string); when packing, n==0 means
the whole string; when unpacking, n==0 means use the previous
read number as the string length
s - zero-terminated string
f - float
d - double
' ' - ignored

例子:

127.0.0.1:6379> eval("HH", 1, 2)' 0
"\x01\x00\x02\x00"
127.0.0.1:6379> eval("HH", ARGV[1])}' 0 "\x01\x00\x02\x00"
1) (integer) 1
2) (integer) 2
3) (integer) 5
127.0.0.1:6379> eval("HH")' 0
(integer) 4

CJSON 库

CJSON 库为Lua提供极快的JSON处理

例子:

redis 127.0.0.1:6379> eval({["foo"]= "bar"})' 0
"{\"foo\":\"bar\"}"
redis 127.0.0.1:6379> eval(ARGV[1])["foo"]' 0 "{\"foo\":\"bar\"}"
"bar"

cmsgpack 库

cmsgpack 库为Lua提供了简单、快速的MessagePack操纵

例子:

127.0.0.1:6379> eval({"foo", "bar", "baz"})' 0
"\x93\xa3foo\xa3bar\xa3baz"
127.0.0.1:6379> eval(ARGV[1])' 0 "\x93\xa3foo\xa3bar\xa3baz"
1) "foo"
2) "bar"
3) "baz"

bitop 库

bitop库为Lua的位运算模块增加了按位操作数。 它是Redis 2.8.18开始加入的。

例子:

127.0.0.1:6379> eval(1)' 0
(integer) 1
127.0.0.1:6379> eval(1,2,4,8,16,32,64,128)' 0
(integer) 255
127.0.0.1:6379> eval(422342)' 0
"000671c6"

它支持几个其他功能: ​​bit.tobit​​​, ​​bit.tohex​​​, ​​bit.bnot​​​, ​​bit.band​​​, ​​bit.bor​​​, ​​bit.bxor​​​, ​​bit.lshift​​​, ​​bit.rshift​​​, ​​bit.arshift​​​, ​​bit.rol​​​, ​​bit.ror​​​, ​​bit.bswap​​​. 所有可用的功能请参考​​Lua BitOp documentation​​

​redis.sha1hex​

对字符串执行SHA1算法

例子:

127.0.0.1:6379> eval(ARGV[1])' 0 "foo"
"0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"

使用脚本散发 Redis 日志

在 Lua 脚本中,可以通过调用 ​​redis.log​​ 函数来写 Redis 日志(log):

redis.log(loglevel,message)

其中, message 参数是一个字符串,而 loglevel 参数可以是以下任意一个值:

上面的这些等级(level)和标准 Redis 日志的等级相对应。

对于脚本散发(emit)的日志,只有那些和当前 Redis 实例所设置的日志等级相同或更高级的日志才会被散发。

以下是一个日志示例:

redis.log(redis.LOG_WARNING, "Something is wrong with this script.")

执行上面的函数会产生这样的信息:

[32343] 22 Mar 15:21:39 # Something is wrong with this script.

沙箱(sandbox)和最大执行时间

脚本应该仅仅用于传递参数和对 Redis 数据进行处理,它不应该尝试去访问外部系统(比如文件系统),或者执行任何系统调用。

除此之外,脚本还有一个最大执行时间限制,它的默认值是 5 秒钟,一般正常运作的脚本通常可以在几分之几毫秒之内完成,花不了那么多时间,这个限制主要是为了防止因编程错误而造成的无限循环而设置的。

最大执行时间的长短由 lua-time-limit 选项来控制(以毫秒为单位),可以通过编辑 redis.conf 文件或者使用 ​​CONFIG GET​​​ 和 ​​CONFIG SET​​ 命令来修改它。

当一个脚本达到最大执行时间的时候,它并不会自动被 Redis 结束,因为 Redis 必须保证脚本执行的原子性,而中途停止脚本的运行意味着可能会留下未处理完的数据在数据集(data set)里面。

因此,当脚本运行的时间超过最大执行时间后,以下动作会被执行:

流水线(pipeline)上下文(context)中的 EVALSHA

在流水线请求的上下文中使用 ​​NOSCRIPT​​ 错误,那么这个流水线就再也没有办法重新执行了,否则的话,命令的执行顺序就会被打乱。

为了防止出现以上所说的问题,客户端库实现应该实施以下的其中一项措施:

基本用法

在Redis中使用​​EVAL​​命令来运行Lua脚本。其参数分三个部分,分别为Lua脚本、操作的键的个数与键值、其他参数。例如:

1

> eval('set',KEYS[1],ARGV[1])" 1 foo bar

上边的命令相当于运行​​set foo bar​​。在参数中指定键值并不是必须的,但是在集群环境中,Redis通过分析参数中的键来确定脚本需要运行在哪些节点上。

在Lua脚本中调用Redis命令有两种方式,一种是如上边例子中的​​redis.call​​​,另一种是​​redis.pcall​​​。两者的区别是,当发生异常时,​​call​​​会抛出异常终止程序,并返回错误信息。而​​pcall​​​则会捕获异常并返回一个使用Lua Table表示的错误信息,但脚本会继续运行。在下边的例子中,将​​set​​​误写为​​se​​​,​​call​​​抛出异常,而​​pcall​​会捕获异常并继续执行。

1
2

> eval('se', 'foo', 'bar');return 1" 0
(error) ERR Error running script (call to f_d6ca96827cc8fb5e8cdeacf0ccabcee83fb23513): @user_script:1: @user_script: 1: Unknown Redis command called from Lua script

1
2

> eval('se', 'foo', 'bar');return 1" 0
(integer) 1

Redis中的Lua环境载入的库有:​​base​​​、​​table​​​、​​string​​​、​​math​​​、​​struct​​​、​​cjson​​​、​​cmsgpack​​​、​​bitop​​。

数据类型转换

在Lua脚本中使用​​call​​​与​​pcall​​调用Redis命令时,就需要将Lua的数据类型转成Redis的数据类型,同时Redis调用的返回值又需要转回到Lua的数据类型。下边两张表是他们互相转换的规则:

Redis类型到Lua类型的转换表:

Redis

Lua

integer reply

number

bulk reply

string

multi bulk reply

table (may have other Redis data types nested)

status reply

table with a single ok field containing the status

error reply

table with a single err field containing the error

Nil bulk reply and Nil multi bulk reply

false boolean type

Lua类型到Redis类型转换表:

Lua

Redis

number

integer reply (the number is converted into an integer)

string

bulk reply

table (array)

multi bulk reply (truncated to the first nil inside the Lua array if any)

table with a single ok field

status reply

table with a single err field

error reply

boolean false

Nil bulk reply

boolean true

integer reply with value of 1

另外两条重要的规则:

在下边的例子中可以看到,Lua的table类型被转成了Redis的multi bulk reply,并且浮点数3.3333的小数位被省略了,同时在第一个nil处停止了转换:

1
2
3
4
5

> eval(integer) 1
2) (integer) 2
3) (integer) 3
4) "foo"

Redis提供了帮助生成状态与错误值的方法,分别为​​redis.status_reply​​​与​​redis.error_reply​​​。脚本​​return {err="My Error"}​​​与​​return redis.error_reply("My Error")​​的结果是相同的。

原子性

Redis一次只运行一个命令,Lua脚本的运行与其他的Redis命令相同,都是原子操作。在Lua脚本运行的过程中,不会有其他命令运行,因此数据也不会被其他操作修改和读取。这和前边《​​Redis拾遗​​​》中提到的,实现事务的​​MULTI/EXEC​​​操作很像。所以,Lua脚本可以用来实现事物,它也是官方推荐的实现事物的方式,因为在复杂情境下,完全在服务端运行的Lua脚本的性能要优于需要多次网络交互的​​MULTI/EXEC​​操作。

在Lua脚本运行期间,Redis不能处理其他请求。所以,确保脚本轻量和快速运行非常重要。如果一个脚本运行的时间过长,就会超时,Redis默认的脚本运行超时是5秒钟,可以使用配置文件中的​​lua-time-limit​​进行调整。

如果脚本运行超时了,Redis并不是简单的杀死脚本,并继续提供服务,这样违反其原子性。超时后,Redis会记录超时的日志,并开始接受新的请求,但是对​​SCRIPT KILL​​​与​​SHUTDOWN NOSAVE​​​之外的命令都只返回BUSY的错误。如果运行的脚本只是读取数据,还没有写入数据,这时就可以用​​SCRIPT KILL​​​将其杀死,否则只能使用​​SHUTDOWN NOSAVE​​关闭服务器并放弃之前一段时间的更改,保证数据的一致性。

调试

Redis 3.2版加入Lua脚本的调试器。Lua调试器运行在服务器上,可以在客户端使用Redis Cli进行远程调试。默认情况下,调试会话不会阻塞服务器的正常运行,并且在同一个服务器上可以打开多个调试会话,数据在调试会话结束后会回滚。同时也提供了同步的调试会话,会阻塞服务器,并且不会回滚数据。调试器支持步近、断点、获取Lua变量值、跟踪Redis命令调用、无限循环与超时运行检测等功能。

使用​​ldb​​参数打开调试器:

1

redis-cli --ldb --eval(0)" target="_blank">​sunsky303​​    


举报

相关推荐

0 条评论