# EVAL
**EVAL script numkeys key [key ...] arg [arg ...]**
從 Redis 2.6.0 版本開始,通過內置的 Lua 解釋器,可以使用 [EVAL](#eval) 命令對 Lua 腳本進行求值。
`script` 參數是一段 Lua 5.1 腳本程序,它會被運行在 Redis 服務器上下文中,這段腳本不必(也不應該)定義為一個 Lua 函數。
`numkeys` 參數用于指定鍵名參數的個數。
鍵名參數 `key [key ...]` 從 [EVAL](#eval) 的第三個參數開始算起,表示在腳本中所用到的那些 Redis 鍵(key),這些鍵名參數可以在 Lua 中通過全局變量 `KEYS` 數組,用 `1` 為基址的形式訪問( `KEYS[1]` , `KEYS[2]` ,以此類推)。
在命令的最后,那些不是鍵名參數的附加參數 `arg [arg ...]` ,可以在 Lua 中通過全局變量 `ARGV` 數組訪問,訪問的形式和 `KEYS` 變量類似( `ARGV[1]` 、 `ARGV[2]` ,諸如此類)。
上面這幾段長長的說明可以用一個簡單的例子來概括:
```
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
```
其中 `"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"` 是被求值的 Lua 腳本,數字 `2` 指定了鍵名參數的數量, `key1` 和 `key2` 是鍵名參數,分別使用 `KEYS[1]` 和 `KEYS[2]` 訪問,而最后的 `first` 和 `second` 則是附加參數,可以通過 `ARGV[1]` 和 `ARGV[2]` 訪問它們。
在 Lua 腳本中,可以使用兩個不同函數來執行 Redis 命令,它們分別是:
* `redis.call()`
* `redis.pcall()`
這兩個函數的唯一區別在于它們使用不同的方式處理執行命令所產生的錯誤,在后面的『錯誤處理』部分會講到這一點。
`redis.call()` 和 `redis.pcall()` 兩個函數的參數可以是任何格式良好(well formed)的 Redis 命令:
```
> eval "return redis.call('set','foo','bar')" 0
OK
```
需要注意的是,上面這段腳本的確實現了將鍵 `foo` 的值設為 `bar` 的目的,但是,它違反了 [EVAL](#eval) 命令的語義,因為腳本里使用的所有鍵都應該由 `KEYS` 數組來傳遞,就像這樣:
```
> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
```
要求使用正確的形式來傳遞鍵(key)是有原因的,因為不僅僅是 [EVAL](#eval) 這個命令,所有的 Redis 命令,在執行之前都會被分析,籍此來確定命令會對哪些鍵進行操作。
因此,對于 [EVAL](#eval) 命令來說,必須使用正確的形式來傳遞鍵,才能確保分析工作正確地執行。除此之外,使用正確的形式來傳遞鍵還有很多其他好處,它的一個特別重要的用途就是確保 Redis 集群可以將你的請求發送到正確的集群節點。(對 Redis 集群的工作還在進行當中,但是腳本功能被設計成可以與集群功能保持兼容。)不過,這條規矩并不是強制性的,從而使得用戶有機會濫用(abuse) Redis 單實例配置(single instance configuration),代價是這樣寫出的腳本不能被 Redis 集群所兼容。
## 在 Lua 數據類型和 Redis 數據類型之間轉換
當 Lua 通過 `call()` 或 `pcall()` 函數執行 Redis 命令的時候,命令的返回值會被轉換成 Lua 數據結構。同樣地,當 Lua 腳本在 Redis 內置的解釋器里運行時,Lua 腳本的返回值也會被轉換成 Redis 協議(protocol),然后由 [EVAL](#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 / Lua 數字轉換成 Redis 整數
* Lua string -> Redis bulk reply / Lua 字符串轉換成 Redis bulk 回復
* Lua table (array) -> Redis multi bulk reply / 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`
以下是幾個類型轉換的例子:
```
> eval "return 10" 0
(integer) 10
> eval "return {1,2,{3,'Hello World!'}}" 0
1) (integer) 1
2) (integer) 2
3) 1) (integer) 3
2) "Hello World!"
> eval "return redis.call('get','foo')" 0
"bar"
```
在上面的三個代碼示例里,前兩個演示了如何將 Lua 值轉換成 Redis 值,最后一個例子更復雜一些,它演示了一個將 Redis 值轉換成 Lua 值,然后再將 Lua 值轉換成 Redis 值的類型轉過程。
## 腳本的原子性
Redis 使用單個 Lua 解釋器去運行所有腳本,并且, Redis 也保證腳本會以原子性(atomic)的方式執行:當某個腳本正在運行的時候,不會有其他腳本或 Redis 命令被執行。這和使用 [_MULTI_](../transaction/multi.html#multi) / [_EXEC_](../transaction/exec.html#exec) 包圍的事務很類似。在其他別的客戶端看來,腳本的效果(effect)要么是不可見的(not visible),要么就是已完成的(already completed)。
另一方面,這也意味著,執行一個運行緩慢的腳本并不是一個好主意。寫一個跑得很快很順溜的腳本并不難,因為腳本的運行開銷(overhead)非常少,但是當你不得不使用一些跑得比較慢的腳本時,請小心,因為當這些蝸牛腳本在慢吞吞地運行的時候,其他客戶端會因為服務器正忙而無法執行命令。
## 錯誤處理
前面的命令介紹部分說過, `redis.call()` 和 `redis.pcall()` 的唯一區別在于它們對錯誤處理的不同。
當 `redis.call()` 在執行命令的過程中發生錯誤時,腳本會停止執行,并返回一個腳本錯誤,錯誤的輸出信息會說明錯誤造成的原因:
```
redis> lpush foo a
(integer) 1
redis> eval "return redis.call('get', 'foo')" 0
(error) ERR Error running script (call to f_282297a0228f48cd3fc6a55de6316f31422f5d17): 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](#eval) 命令要求你在每次執行腳本的時候都發送一次腳本主體(script body)。Redis 有一個內部的緩存機制,因此它不會每次都重新編譯腳本,不過在很多場合,付出無謂的帶寬來傳送腳本主體并不是最佳選擇。
為了減少帶寬的消耗, Redis 實現了 EVALSHA 命令,它的作用和 [EVAL](#eval) 一樣,都用于對腳本求值,但它接受的第一個參數不是腳本,而是腳本的 SHA1 校驗和(sum)。
EVALSHA 命令的表現如下:
* 如果服務器還記得給定的 SHA1 校驗和所指定的腳本,那么執行這個腳本
* 如果服務器不記得給定的 SHA1 校驗和所指定的腳本,那么它返回一個特殊的錯誤,提醒用戶使用 [EVAL](#eval) 代替 EVALSHA
以下是示例:
```
> set foo bar
OK
> eval "return redis.call('get','foo')" 0
"bar"
> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0
"bar"
> evalsha ffffffffffffffffffffffffffffffffffffffff 0
(error) `NOSCRIPT` No matching script. Please use [EVAL](/commands/eval).
```
客戶端庫的底層實現可以一直樂觀地使用 EVALSHA 來代替 [EVAL](#eval) ,并期望著要使用的腳本已經保存在服務器上了,只有當 `NOSCRIPT` 錯誤發生時,才使用 [EVAL](#eval) 命令重新發送腳本,這樣就可以最大限度地節省帶寬。
這也說明了執行 [EVAL](#eval) 命令時,使用正確的格式來傳遞鍵名參數和附加參數的重要性:因為如果將參數硬寫在腳本中,那么每次當參數改變的時候,都要重新發送腳本,即使腳本的主體并沒有改變,相反,通過使用正確的格式來傳遞鍵名參數和附加參數,就可以在腳本主體不變的情況下,直接使用 EVALSHA 命令對腳本進行復用,免去了無謂的帶寬消耗。
## 腳本緩存
Redis 保證所有被運行過的腳本都會被永久保存在腳本緩存當中,這意味著,當 [EVAL](#eval) 命令在一個 Redis 實例上成功執行某個腳本之后,隨后針對這個腳本的所有 EVALSHA 命令都會成功執行。
刷新腳本緩存的唯一辦法是顯式地調用 `SCRIPT FLUSH` 命令,這個命令會清空運行過的所有腳本的緩存。通常只有在云計算環境中,Redis 實例被改作其他客戶或者別的應用程序的實例時,才會執行這個命令。
緩存可以長時間儲存而不產生內存問題的原因是,它們的體積非常小,而且數量也非常少,即使腳本在概念上類似于實現一個新命令,即使在一個大規模的程序里有成百上千的腳本,即使這些腳本會經常修改,即便如此,儲存這些腳本的內存仍然是微不足道的。
事實上,用戶會發現 Redis 不移除緩存中的腳本實際上是一個好主意。比如說,對于一個和 Redis 保持持久化鏈接(persistent connection)的程序來說,它可以確信,執行過一次的腳本會一直保留在內存當中,因此它可以在流水線中使用 EVALSHA 命令而不必擔心因為找不到所需的腳本而產生錯誤(稍候我們會看到在流水線中執行腳本的相關問題)。
## SCRIPT 命令
Redis 提供了以下幾個 SCRIPT 命令,用于對腳本子系統(scripting subsystem)進行控制:
* [_SCRIPT FLUSH_](script_flush.html#script-flush) :清除所有腳本緩存
* [_SCRIPT EXISTS_](script_exists.html#script-exists) :根據給定的腳本校驗和,檢查指定的腳本是否存在于腳本緩存
* [_SCRIPT LOAD_](script_load.html#script-load) :將一個腳本裝入腳本緩存,但并不立即運行它
* [_SCRIPT KILL_](script_kill.html#script-kill) :殺死當前正在運行的腳本
## 純函數腳本
在編寫腳本方面,一個重要的要求就是,腳本應該被寫成純函數(pure function)。
也就是說,腳本應該具有以下屬性:
* 對于同樣的數據集輸入,給定相同的參數,腳本執行的 Redis 寫命令總是相同的。腳本執行的操作不能依賴于任何隱藏(非顯式)數據,不能依賴于腳本在執行過程中、或腳本在不同執行時期之間可能變更的狀態,并且它也不能依賴于任何來自 I/O 設備的外部輸入。
使用系統時間(system time),調用像 [_RANDOMKEY_](../key/randomkey.html#randomkey) 那樣的隨機命令,或者使用 Lua 的隨機數生成器,類似以上的這些操作,都會造成腳本的求值無法每次都得出同樣的結果。
為了確保腳本符合上面所說的屬性, Redis 做了以下工作:
* Lua 沒有訪問系統時間或者其他內部狀態的命令
* Redis 會返回一個錯誤,阻止這樣的腳本運行: 這些腳本在執行隨機命令之后(比如 [_RANDOMKEY_](../key/randomkey.html#randomkey) 、 [_SRANDMEMBER_](../set/srandmember.html#srandmember) 或 [_TIME_](../server/time.html#time) 等),還會執行可以修改數據集的 Redis 命令。如果腳本只是執行只讀操作,那么就沒有這一限制。注意,隨機命令并不一定就指那些帶 RAND 字眼的命令,任何帶有非確定性的命令都會被認為是隨機命令,比如 [_TIME_](../server/time.html#time) 命令就是這方面的一個很好的例子。
* 每當從 Lua 腳本中調用那些返回無序元素的命令時,執行命令所得的數據在返回給 Lua 之前會先執行一個靜默(slient)的字典序排序([lexicographical sorting](http://en.wikipedia.org/wiki/Lexicographical_order))。舉個例子,因為 Redis 的 Set 保存的是無序的元素,所以在 Redis 命令行客戶端中直接執行 [_SMEMBERS_](../set/smembers.html#smembers) ,返回的元素是無序的,但是,假如在腳本中執行 `redis.call("smembers", KEYS[1])` ,那么返回的總是排過序的元素。
* 對 Lua 的偽隨機數生成函數 `math.random` 和 `math.randomseed` 進行修改,使得每次在運行新腳本的時候,總是擁有同樣的 seed 值。這意味著,每次運行腳本時,只要不使用 `math.randomseed` ,那么 `math.random` 產生的隨機數序列總是相同的。
盡管有那么多的限制,但用戶還是可以用一個簡單的技巧寫出帶隨機行為的腳本(如果他們需要的話)。
假設現在我們要編寫一個 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](#eval) 命令會返回一個錯誤:
```
redis 127.0.0.1:6379> eval 'a=10' 0
(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` 關鍵字定義為局部變量。
## 庫
Redis 內置的 Lua 解釋器加載了以下 Lua 庫:
* `base`
* `table`
* `string`
* `math`
* `debug`
* `cjson`
* `cmsgpack`
其中 `cjson` 庫可以讓 Lua 以非常快的速度處理 JSON 數據,除此之外,其他別的都是 Lua 的標準庫。
每個 Redis 實例都保證會加載上面列舉的庫,從而確保每個 Redis 腳本的運行環境都是相同的。
## 使用腳本散發 Redis 日志
在 Lua 腳本中,可以通過調用 `redis.log` 函數來寫 Redis 日志(log):
`redis.log(loglevel, message)`
其中, `message` 參數是一個字符串,而 `loglevel` 參數可以是以下任意一個值:
* `redis.LOG_DEBUG`
* `redis.LOG_VERBOSE`
* `redis.LOG_NOTICE`
* `redis.LOG_WARNING`
上面的這些等級(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_](../server/config_get.html#config-get) 和 [_CONFIG SET_](../server/config_set.html#config-set) 命令來修改它。
當一個腳本達到最大執行時間的時候,它并不會自動被 Redis 結束,因為 Redis 必須保證腳本執行的原子性,而中途停止腳本的運行意味著可能會留下未處理完的數據在數據集(data set)里面。
因此,當腳本運行的時間超過最大執行時間后,以下動作會被執行:
* Redis 記錄一個腳本正在超時運行
* Redis 開始重新接受其他客戶端的命令請求,但是只有 `SCRIPT KILL` 和 `SHUTDOWN NOSAVE` 兩個命令會被處理,對于其他命令請求, Redis 服務器只是簡單地返回 `BUSY` 錯誤。
* 可以使用 `SCRIPT KILL` 命令將一個僅執行只讀命令的腳本殺死,因為只讀命令并不修改數據,因此殺死這個腳本并不破壞數據的完整性
* 如果腳本已經執行過寫命令,那么唯一允許執行的操作就是 `SHUTDOWN NOSAVE` ,它通過停止服務器來阻止當前數據集寫入磁盤
## 流水線(pipeline)上下文(context)中的 EVALSHA
在流水線請求的上下文中使用 EVALSHA 命令時,要特別小心,因為在流水線中,必須保證命令的執行順序。
一旦在流水線中因為 EVALSHA 命令而發生 NOSCRIPT 錯誤,那么這個流水線就再也沒有辦法重新執行了,否則的話,命令的執行順序就會被打亂。
為了防止出現以上所說的問題,客戶端庫實現應該實施以下的其中一項措施:
* 總是在流水線中使用 [EVAL](#eval) 命令
* 檢查流水線中要用到的所有命令,找到其中的 [EVAL](#eval) 命令,并使用 [_SCRIPT EXISTS_](script_exists.html#script-exists) 命令檢查要用到的腳本是不是全都已經保存在緩存里面了。如果所需的全部腳本都可以在緩存里找到,那么就可以放心地將所有 [EVAL](#eval) 命令改成 EVALSHA 命令,否則的話,就要在流水線的頂端(top)將缺少的腳本用 [_SCRIPT LOAD_](script_load.html#script-load) 命令加上去。
**可用版本:**
>= 2.6.0
**時間復雜度:**
[EVAL](#eval) 和 EVALSHA 可以在 O(1) 復雜度內找到要被執行的腳本,其余的復雜度取決于執行的腳本本身。
- Redis 教程
- Redis 簡介
- Redis 安裝
- Redis 配置
- Redis 數據類型
- Redis 命令
- Redis 數據備份與恢復
- Redis 安全
- Redis 性能測試
- Redis 客戶端連接
- Redis 管道技術
- Redis 分區
- Java 使用 Redis
- Java 使用 Redis
- PHP 使用 Redis
- PHP 使用 Redis
- Redis 命令參考
- Key(鍵)
- DEL
- DUMP
- EXISTS
- EXPIRE
- EXPIREAT
- KEYS
- MIGRATE
- MOVE
- OBJECT
- PERSIST
- PEXPIRE
- PEXPIREAT
- PTTL
- RANDOMKEY
- RENAME
- RENAMENX
- RESTORE
- SORT
- TYPE
- SCAN
- String(字符串)
- APPEND
- BITCOUNT
- BITOP
- DECR
- DECRBY
- GET
- GETBIT
- GETRANGE
- GETSET
- INCR
- INCRBY
- INCRBYFLOAT
- MGET
- MSET
- MSETNX
- PSETEX
- SET
- SETBIT
- SETEX
- SETNX
- SETRANGE
- STRLEN
- Hash(哈希表)
- HDEL
- HEXISTS
- HGET
- HGETALL
- HINCRBY
- HINCRBYFLOAT
- HKEYS
- HLEN
- HMGET
- HMSET
- HSET
- HSETNX
- HVALS
- HSCAN
- List(列表)
- BLPOP
- BRPOP
- BRPOPLPUSH
- LINDEX
- LINSERT
- LLEN
- LPOP
- LPUSH
- LRANGE
- LREM
- LSET
- LTRIM
- RPOP
- RPOPLPUSH
- RPUSH
- RPUSHX
- Set(集合)
- SADD
- SCARD
- SDIFF
- SDIFFSTORE
- SINTER
- SINTER
- SINTERSTORE
- SISMEMBER
- SMEMBERS
- SMOVE
- SPOP
- SRANDMEMBER
- SREM
- SUNION
- SUNIONSTORE
- SSCAN
- SortedSet(有序集合)
- ZADD
- ZCARD
- ZCOUNT
- ZINCRBY
- ZRANGE
- ZRANGEBYSCORE
- ZRANK
- ZREM
- ZREMRANGEBYRANK
- ZREMRANGEBYSCORE
- ZREVRANGE
- ZREVRANGEBYSCORE
- ZREVRANK
- ZSCORE
- ZUNIONSTORE
- ZINTERSTORE
- ZSCAN
- Pub/Sub(發布/訂閱)
- PSUBSCRIBE
- PUBLISH
- PUBSUB
- PUNSUBSCRIBE
- SUBSCRIBE
- UNSUBSCRIBE
- Transaction(事務)
- DISCARD
- EXEC
- MULTI
- UNWATCH
- WATCH
- Script(腳本)
- EVAL
- EVALSHA
- SCRIPT EXISTS
- SCRIPT FLUSH
- SCRIPT KILL
- SCRIPT LOAD
- Connection(連接)
- AUTH
- ECHO
- PING
- QUIT
- SELECT
- Server(服務器)
- BGREWRITEAOF
- BGSAVE
- CLIENT GETNAME
- CLIENT KILL
- CLIENT LIST
- CLIENT SETNAME
- CONFIG GET
- CONFIG RESETSTAT
- CONFIG REWRITE
- CONFIG SET
- DBSIZE
- DEBUG OBJECT
- DEBUG SEGFAULT
- FLUSHALL
- FLUSHDB
- INFO
- LASTSAVE
- MONITOR
- PSYNC
- SAVE
- SHUTDOWN
- SLAVEOF
- SLOWLOG
- SYNC
- TIME
- 免責聲明