From 3baa0caff2024964d62694667d3aa7616c8bdd5d Mon Sep 17 00:00:00 2001 From: asahi Date: Thu, 25 Sep 2025 11:29:35 +0800 Subject: [PATCH] =?UTF-8?q?doc:=20=E9=98=85=E8=AF=BBredis=20lua=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 中间件/redis/redis.md | 97 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/中间件/redis/redis.md b/中间件/redis/redis.md index 36a690c..5ca8325 100644 --- a/中间件/redis/redis.md +++ b/中间件/redis/redis.md @@ -173,6 +173,7 @@ - [Script Command](#script-command) - [Script Replication](#script-replication) - [Replicating commands instead of Scripts](#replicating-commands-instead-of-scripts) + - [Scripts with deterministic writes](#scripts-with-deterministic-writes) # redis @@ -2835,4 +2836,100 @@ redis> EVALSHA ffffffffffffffffffffffffffffffffffffffff 0 - 当启用script effects replication时,`non-determinstic function`校验将会被移除。故而,可以在脚本中自由的使用`TIME`或`SRANDMEMBER`这类非确定性的指令 - The Lua PRNG in this mode is seeded randomly on every call +除非通过server配置或默认启用了effect replication,否则若需令脚本同步按照effect replication的方式进行同步,必须在脚本执行任何write command之前执行如下lua命令: +```lua +redis.replicate_commands() +``` +在调用`redis.replicate_commands`方法时: +- 如果effect replication被启用,那么返回值为true +- 如果在脚本已经执行过write command之后再调用该方法,那么该方法返回值为false,并且会使用normal whole script replication + +该方法在Redis 7.0中被标记为废弃,如果仍然调用它,那么其返回值一直为true + +#### Scripts with deterministic writes +从redis 5.0开始,script replication默认情况下为effect-based而不是verbatim;而在redis 7.0,verbatim script replication的方式被完全移除。故而,如下内容只针对版本低于7.0并且没有使用effect-based的script replication。 + +在使用verbatim script replication的情况下,主要注意`only change the database in a deterministic way`。在redis实例执行script时,一直到redis 5.0,默认都是通过`sending the script itself`方式来传播脚本的执行到replicas和AOF中的。在replica上,被传递的脚本会被重新执行,脚本对database的修改必须可重现。 + +通常,发送脚本本身占用的带宽要比发送`脚本产生的命令`占用的带宽要小,cpu消耗也更小。 + +但是,`sending the script itself`这种replication方式并非对所有的场景都是可行的。 + +`verbatim scripts replication`要求脚本拥有如下属性: +- 在`arguments相同,input data set相同`的场景下,脚本必须产生相同的redis write commands +- 脚本执行的操作不能依赖于任何`hidden(non-explicit) information`或`state that may change as the script execution proceeds or between different executions of the script` +- 脚本的执行也不能依赖于任何io设备的外部输入 + +例如,`使用系统时间、调用返回随机值的redis命令、使用redis的随机数生成器`都会导致`scripts that will not evaluate consistently` + +为了保证脚本的确定性行为,redis做了如下处理: +- lua不会export任何`访问系统时间或外部状态的命令` +- 如果脚本在调用`random command`(例如`RANDOMKEY, SRANDMEMBER, TIME`)之后,又调用了`Redis command able to alter the data set`,那么redis将会`block the script with error`。 + - 上述即代表`read-only scripts that don't modify dataset can call those commands` + - `random command`并不代表使用随机数的command,而是代表`non-deterministic command`(例如TIME) +- 在redis 4.0中,`例如SMEMBERS这类以随机顺序返回元素的命令`,在`被lua脚本调用时`表现出的行为不同,在将数据返回给lua脚本时会根据字典序进行排序。 + - 故而,在redis 4.0环境下,lua脚本中调用`redis.call("SMEMBERS", KEYS[1])`时,总是会按相同的顺序来返回Set中的元素。 + - 但是,从redis 5.0开始,又不会执行该排序,因为可以`effect replication`被设置为默认 +- lua的伪随机数生成function `math.random`已经被redis修改,故而在每次执行时都会使用相同的seed。 + - 故而,每次script执行时,调用`match.random`总是会生成相同序列的数字 + +由上述描述可知,redis修改了lua的伪随机数生成器(只在verbatim replication下成立),故而每次运行lua脚本时,随机数生成器返回的数值序列都相同。 + +但是,仍然可以通过一定的技巧来生成随机数,示例如下所示: +```ruby +require 'rubygems' +require 'redis' + +r = Redis.new + +RandomPushScript = < 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)]) +``` +每次上述程序运行,resulting list都会拥有相同的元素: +```redis-cli +redis> 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" +``` + +为了让脚本确定,并且让其产生不同的random elements,可以像脚本中添加额外参数,用于对lua的伪随机数生成器进行seed。脚本示例如下所示: +```ruby +RandomPushScript = < 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)) +``` +上述示例中,通过`math.randomseed`方法来将ruby生成的随机数作为了lua随机数生成器的种子,从而产生了不同的数值序列。 + +当然,上述脚本的内容仍然是确定的,当传递的`ARGV[2]`相同时,lua随机数生成器生成的数值序列仍然是固定的。 + +该seed是由client生成的,作为参数被传递给脚本,并且会作为参数被传播给replicas和AOF。这样,能够确保同步给AOF和replicas的changes相同。 +