Redis 从 2.6 开始内嵌了 Lua 环境来支持用户扩展功能. 经过 Lua 脚本, 咱们能够原子化
地执行多条 Redis 命令.redis
在 Redis 中执行 Lua 脚本须要用到 EVAL
和 EVALSHA
和 SCRIPT ***
这几个命令, 下面分别来介绍一下:shell
EVAL
: 执行 Lua 脚本数据库
EVAL script numkeys key[key ...] arg [arg ...]
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
复制代码
在 Lua 脚本中能够经过 KEYS[]
数组加上脚标访问具体的 Redis Key, 经过 ARGV[]
数据加脚标访问传入的参数(变量). 注意, 脚标都是从 1 开始的.数组
EVALSHA
: 从缓存中执行 Lua 脚本缓存
EVAL sha1 numkeys key[key ...] arg [arg ...]
127.0.0.1:6379> SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
"a42059b356c875f0717db19a51f6aaca9ae659ea"
127.0.0.1:6379> evalsha a42059b356c875f0717db19a51f6aaca9ae659ea 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
复制代码
EVALSHA
和 EVAL
的参数差很少. 只是把脚本改为了缓存中脚本的 sha1 值, 其他没有区别.bash
SCRIPT LOAD
: 将脚本缓存到服务器中.服务器
SCRIPT FLUSH
: 清空服务器中的全部脚本并发
SCRIPT EXISTS
: 判断脚本是否存在于服务器中app
SCRIPT KILL
: 中止当前正在执行的脚本dom
在 Redis 中执行的 Lua 脚本必须是纯函数形式. 也就是说, 给定一段脚本而且传入相同的参数, 写入 Redis 中的数据也必须是一致的. Redis 会拒绝随机性的写入, 由于这会形成数据的不一致性.
Redis 容许在 Lua 脚本中经过 redis.call()
和 redis.pcall()
来执行 Redis 命令. 若是 Lua 脚本对 Redis 中的数据进行了更改, 那么除了更新数据库中的数据以外, 还会执行另外两个操做:
127.0.0.1:6379> eval "redis.call('set', KEYS[1], ARGV[1]); return redis.call('get', KEYS[1])" 1 mykey myvalue
"myvalue"
# 查看 AOF 文件
➜ cat appendonly.aof
*5
$4
eval
$70
redis.call('set', KEYS[1], ARGV[1]); return redis.call('get', KEYS[1])
$1
1
$5
mykey
$7
myvalue
复制代码
因此, 若是 Redis 接受随机性写入的话, Redis 在重启先后或者在主从库之间就会存在数据不一致的现象, 固然, 这是不被容许的.
好比, 我在 Lua 脚本中获取当前时间并将当前时间 SET 到一个 KEY 中, Redis 就会拒绝操做并抛出一个异常; 也就是说, Redis 会拒绝存在随机写入的 Lua 脚本执行.
127.0.0.1:6379> eval "local now = redis.call('time')[1]; redis.call('set','now',now); return redis.call('get','now')" 0
Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.
复制代码
Redis 中一共有 10 个随机类命令: SPOP
, SRANDMEMBER
, SSCAN
, ZSCAN
, HSCAN
, SCAN
, RANDOMKEY
, LASTSAVE
, PUBSUB
, TIME
.
当一些返回数据是无序的命令, 好比 SMEMBERS
在 Lua 中被调用时, 返回的数据都是进行过排序后返回的, 因此获得的数据顺序都是一致的.
而且 Redis 修改了 Lua 脚本中的随机数生成函数(math.random
, math.randomseed
)使得新脚本执行的时候, 返回的种子都是同样的. 因此在 Lua 脚本中, 若是未使用 math.randomseed
,仅仅使用 math.random
, 生成的随机数序列都是同样的.
等下, 不是说为了保证 Redis 重启先后和主从之间的数据一致性, Redis 会拒绝执行执行存在随机写入的 Lua 脚本执行吗? 怎么又能够了呢?
5.0 版本以前 Redis 3.2 提供了 redis.replicate_commands()
, 可是须要在执行 Lua 脚本的时候的手动开启.
127.0.0.1:6379> eval "redis.replicate_commands(); local now = redis.call('time')[1]; redis.call('set','now',now); return redis.call('get','now')" 0
"1552060128"
复制代码
在 Lua 脚本中, 从调用 redis.replicate_commands()
开始到脚本结束, 这一部分脚本所产生的 Redis 命令会被包在一个 MULTI / EXEC
事务中, 并发给 AOF 或者从库. 固然, 只有对数据库中的数据产生变化的 Redis 命令才会被生成并包装进 MULTI / EXEC
事务.
# AOF 文件
➜ cat appendonly.aof
*1
$5
MULTI
*3
$3
set
$3
now
$10
1552114016
*1
$4
EXEC
复制代码
注意: Redis 只是会将调用 redis.replicate_commands()
后面的部分放进事务中. 在其前面的部分若是调用了写操做是会破坏数据的一致性的, 此时, redis.replicate_commands()
并不会生效. 见🌰:
127.0.0.1:6379> eval "redis.call('set', 'key', 'value') redis.replicate_commands(); local now = redis.call('time')[1]; redis.call('set','now',now); return redis.call('get','now')" 0
(error) ERR Error running script (call to f_a8c3ce5ccbfc3074b49ea277b7370ded0c2d354b): @user_script:1: @user_script: 1: Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.
127.0.0.1:6379> keys *
1) "now"
2) "key"
# AOF 文件
➜ cat appendonly.aof
*3
$4
eval
$156
redis.call('set', 'key', 'value') redis.replicate_commands(); local now = redis.call('time')[1]; redis.call('set','now',now); return redis.call('get','now')
$1
0
复制代码
因此, 若是在 Lua 脚本中须要进行随机写入的话, 建议在脚本的开头就调用 redis.replicate_commands()
5.0 版本及之后版本默认开启
Redis 3.2 还引入了另外一个机制: 能够自行决定是否持久化或者进行主从复制, 能够经过 redis.set_repl(***)
设置, 参数能够为:
redis.REPL_ALL
: 开启 AOF 持久化和主从复制(默认)redis.REPL_AOF
: 仅开启 AOF 持久化redis.REPL_REPLICA
: 仅开启主从复制redis.REPL_SLAVE
: 同 redis.REPL_REPLICA
redis.REPL_NONE
: 都不开启 通常 redis.set_repl(***)
不多用到, 由于这会形成重启先后和主从库之间数据不一致. 保留默认的 redis.REPL_ALL
就能够了.