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
就能夠了.