Redis 中用于操作鍵的命令基本上可以分為兩種類型。
其中一種命令可以對任何類型的鍵執行, 比如說?DEL?命令、?EXPIRE?命令、?RENAME?命令、?TYPE?命令、?OBJECT?命令, 等等。
舉個例子, 以下代碼就展示了使用?DEL?命令來刪除三種不同類型的鍵:
~~~
# 字符串鍵
redis> SET msg "hello"
OK
# 列表鍵
redis> RPUSH numbers 1 2 3
(integer) 3
# 集合鍵
redis> SADD fruits apple banana cherry
(integer) 3
redis> DEL msg
(integer) 1
redis> DEL numbers
(integer) 1
redis> DEL fruits
(integer) 1
~~~
而另一種命令只能對特定類型的鍵執行, 比如說:
* SET?、?GET?、?APPEND?、?STRLEN?等命令只能對字符串鍵執行;
* HDEL?、?HSET?、?HGET?、?HLEN?等命令只能對哈希鍵執行;
* RPUSH?、?LPOP?、?LINSERT?、?LLEN?等命令只能對列表鍵執行;
* SADD?、?SPOP?、?SINTER?、?SCARD?等命令只能對集合鍵執行;
* ZADD?、?ZCARD?、?ZRANK?、?ZSCORE?等命令只能對有序集合鍵執行;
諸如此類。
舉個例子, 我們可以用?SET?命令創建一個字符串鍵, 然后用?GET?命令和?APPEND?命令操作這個鍵, 但如果我們試圖對這個字符串鍵執行只有列表鍵才能執行的?LLEN?命令, 那么 Redis 將向我們返回一個類型錯誤:
~~~
redis> SET msg "hello world"
OK
redis> GET msg
"hello world"
redis> APPEND msg " again!"
(integer) 18
redis> GET msg
"hello world again!"
redis> LLEN msg
(error) WRONGTYPE Operation against a key holding the wrong kind of value
~~~
## 類型檢查的實現
從上面發生類型錯誤的代碼示例可以看出, 為了確保只有指定類型的鍵可以執行某些特定的命令, 在執行一個類型特定的命令之前, Redis 會先檢查輸入鍵的類型是否正確, 然后再決定是否執行給定的命令。
類型特定命令所進行的類型檢查是通過?`redisObject`?結構的?`type`?屬性來實現的:
* 在執行一個類型特定命令之前, 服務器會先檢查輸入數據庫鍵的值對象是否為執行命令所需的類型, 如果是的話, 服務器就對鍵執行指定的命令;
* 否則, 服務器將拒絕執行命令, 并向客戶端返回一個類型錯誤。
舉個例子, 對于?LLEN?命令來說:
* 在執行?LLEN?命令之前, 服務器會先檢查輸入數據庫鍵的值對象是否為列表類型, 也即是, 檢查值對象?`redisObject`?結構?`type`?屬性的值是否為?`REDIS_LIST`?, 如果是的話, 服務器就對鍵執行?LLEN?命令;
* 否則的話, 服務器就拒絕執行命令并向客戶端返回一個類型錯誤;
圖 8-18 展示了這一類型檢查過程。

其他類型特定命令的類型檢查過程也和這里展示的?LLEN?命令的類型檢查過程類似。
## 多態命令的實現
Redis 除了會根據值對象的類型來判斷鍵是否能夠執行指定命令之外, 還會根據值對象的編碼方式, 選擇正確的命令實現代碼來執行命令。
舉個例子, 在前面介紹列表對象的編碼時我們說過, 列表對象有?`ziplist`?和?`linkedlist`?兩種編碼可用, 其中前者使用壓縮列表 API 來實現列表命令, 而后者則使用雙端鏈表 API 來實現列表命令。
現在, 考慮這樣一個情況, 如果我們對一個鍵執行?LLEN?命令, 那么服務器除了要確保執行命令的是列表鍵之外, 還需要根據鍵的值對象所使用的編碼來選擇正確的?LLEN?命令實現:
* 如果列表對象的編碼為?`ziplist`?, 那么說明列表對象的實現為壓縮列表, 程序將使用?`ziplistLen`?函數來返回列表的長度;
* 如果列表對象的編碼為?`linkedlist`?, 那么說明列表對象的實現為雙端鏈表, 程序將使用?`listLength`?函數來返回雙端鏈表的長度;
借用面向對象方面的術語來說, 我們可以認為?LLEN?命令是多態([polymorphism](http://en.wikipedia.org/wiki/Polymorphism_(computer_science)))的: 只要執行?LLEN?命令的是列表鍵, 那么無論值對象使用的是?`ziplist`?編碼還是?`linkedlist`?編碼, 命令都可以正常執行。
圖 8-19 展示了?LLEN?命令從類型檢查到根據編碼選擇實現函數的整個執行過程, 其他類型特定命令的執行過程也是類似的。

實際上, 我們可以將?DEL?、?EXPIRE?、?TYPE?等命令也稱為多態命令, 因為無論輸入的鍵是什么類型, 這些命令都可以正確地執行。
DEL?、?EXPIRE?等命令和?LLEN?等命令的區別在于, 前者是基于類型的多態 —— 一個命令可以同時用于處理多種不同類型的鍵, 而后者是基于編碼的多態 —— 一個命令可以同時用于處理多種不同編碼。
- 介紹
- 前言
- 致謝
- 簡介
- 第一部分:數據結構與對象
- 簡單動態字符串
- SDS 的定義
- SDS 與 C 字符串的區別
- SDS API
- 重點回顧
- 參考資料
- 鏈表
- 鏈表和鏈表節點的實現
- 鏈表和鏈表節點的 API
- 重點回顧
- 字典
- 字典的實現
- 哈希算法
- 解決鍵沖突
- rehash
- 漸進式 rehash
- 字典 API
- 重點回顧
- 跳躍表
- 跳躍表的實現
- 跳躍表 API
- 重點回顧
- 整數集合
- 整數集合的實現
- 升級
- 升級的好處
- 降級
- 整數集合 API
- 重點回顧
- 壓縮列表
- 壓縮列表的構成
- 壓縮列表節點的構成
- 連鎖更新
- 壓縮列表 API
- 重點回顧
- 對象
- 對象的類型與編碼
- 字符串對象
- 列表對象
- 哈希對象
- 集合對象
- 有序集合對象
- 類型檢查與命令多態
- 內存回收
- 對象共享
- 對象的空轉時長
- 重點回顧
- 第二部分:單機數據庫的實現
- 數據庫
- 數據庫鍵空間
- 重點回顧
- RDB 持久化
- RDB 文件結構
- 重點回顧
- AOF 持久化
- AOF 持久化的實現
- 重點回顧
- 事件
- 文件事件
- 重點回顧
- 參考資料
- 客戶端
- 客戶端屬性
- 重點回顧
- 服務器
- 命令請求的執行過程
- 重點回顧
- 第三部分:多機數據庫的實現
- 復制
- 舊版復制功能的實現
- 重點回顧
- Sentinel
- 啟動并初始化 Sentinel
- 重點回顧
- 參考資料
- 集群
- 節點
- 重點回顧
- 第四部分:獨立功能的實現
- 發布與訂閱
- 頻道的訂閱與退訂
- 重點回顧
- 參考資料
- 事務
- 事務的實現
- 重點回顧
- Lua 腳本
- 創建并修改 Lua 環境
- 重點回顧
- 排序
- SORT <key> 命令的實現
- 重點回顧
- 二進制位數組
- GETBIT 命令的實現
- 重點回顧
- 慢查詢日志
- 慢查詢記錄的保存
- 慢查詢日志的閱覽和刪除
- 添加新日志
- 重點回顧
- 監視器
- 成為監視器
- 向監視器發送命令信息
- 重點回顧
- 源碼、相關資源和勘誤