doc: 阅读redis lua文档
This commit is contained in:
@@ -173,6 +173,7 @@
|
|||||||
- [Script Command](#script-command)
|
- [Script Command](#script-command)
|
||||||
- [Script Replication](#script-replication)
|
- [Script Replication](#script-replication)
|
||||||
- [Replicating commands instead of Scripts](#replicating-commands-instead-of-scripts)
|
- [Replicating commands instead of Scripts](#replicating-commands-instead-of-scripts)
|
||||||
|
- [Scripts with deterministic writes](#scripts-with-deterministic-writes)
|
||||||
|
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
@@ -2835,4 +2836,100 @@ redis> EVALSHA ffffffffffffffffffffffffffffffffffffffff 0
|
|||||||
- 当启用script effects replication时,`non-determinstic function`校验将会被移除。故而,可以在脚本中自由的使用`TIME`或`SRANDMEMBER`这类非确定性的指令
|
- 当启用script effects replication时,`non-determinstic function`校验将会被移除。故而,可以在脚本中自由的使用`TIME`或`SRANDMEMBER`这类非确定性的指令
|
||||||
- The Lua PRNG in this mode is seeded randomly on every call
|
- 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 = <<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)])
|
||||||
|
```
|
||||||
|
每次上述程序运行,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 = <<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))
|
||||||
|
```
|
||||||
|
上述示例中,通过`math.randomseed`方法来将ruby生成的随机数作为了lua随机数生成器的种子,从而产生了不同的数值序列。
|
||||||
|
|
||||||
|
当然,上述脚本的内容仍然是确定的,当传递的`ARGV[2]`相同时,lua随机数生成器生成的数值序列仍然是固定的。
|
||||||
|
|
||||||
|
该seed是由client生成的,作为参数被传递给脚本,并且会作为参数被传播给replicas和AOF。这样,能够确保同步给AOF和replicas的changes相同。
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user