深入剖析Redis系列 - Redis資料結構與全域性命令概述
前言
Redis 提供了 5 種資料結構。理解每種資料結構的特點,對於 Redis 的 開發運維 非常重要,同時掌握 Redis 的 單執行緒命令處理 機制,會使 資料結構 和 命令 的選擇事半功倍。

接下來的幾篇文章,將從如下幾個方面介紹 Redis 的幾種資料結構,命令使用及其應用場景。
預備知識 :幾個簡單的 全域性命令 , 資料結構 和 內部編碼 , 單執行緒命令 處理機制分析。
資料結構特性 :5 種 資料結構 的特點、 命令使用 、 應用場景 。
資料管理 : 鍵管理 、 遍歷鍵 、 資料庫管理 。
正文
1. 預備知識
在介紹 5 種 資料結構 之前,需要先了解 Redis 的一些 全域性命令 、 資料結構 和 內部編碼 、 單執行緒命令處理機制 。
Redis 的命令有 上百個 ,理解 Redis 的一些機制,會發現這些命令有很強的 通用性 。
Redis 不是萬金油,有些 資料結構 和 命令 必須在 特定場景 下使用,一旦 使用不當 可能對 Redis 本身 或者 應用本身 造成致命傷害。
2. 全域性命令
Redis 有 5 種 資料結構 ,它們是 鍵值對 中的 值 ,對於 鍵 來說有一些通用的命令。
2.1. 檢視所有鍵
keys *
下面插入了 3 對字串型別的鍵值對:
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> set java jedis OK 127.0.0.1:6379> set python redis-py OK 複製程式碼
命令會將所有的鍵輸出:
127.0.0.1:6379> keys * 1) "python" 2) "java" 3) "hello" 複製程式碼
2.2. 鍵總數
dbsize
下面插入一個 列表型別 的 鍵值對 (值是 多個元素 組成):
127.0.0.1:6379> rpush mylist a b c d e f g (integer) 7 複製程式碼
dbsize 命令會返回當前資料庫中 鍵的總數 。
127.0.0.1:6379> dbsize (integer) 4 複製程式碼
dbsize 命令在 計算鍵總數 時 不會遍歷 所有鍵,而是直接獲取 Redis 內建的鍵總數變數 ,所以 dbsize 命令的 時間複雜度 是 O(1)。而 keys 命令會 遍歷 所有鍵,所以它的 時間複雜度 是 O(n),當 Redis 儲存了 大量鍵 時,線上環境 禁止 使用。
2.3. 檢查鍵是否存在
exists key
如果鍵存在則返回 1,不存在則返回 0:
127.0.0.1:6379> exists java (integer) 1 127.0.0.1:6379> exists not_exist_key (integer) 0 複製程式碼
2.4. 刪除鍵
del key
del 是一個 通用命令 ,無論值是什麼 資料結構 型別,del 命令都可以將其 刪除 。
127.0.0.1:6379> del java (integer) 1 127.0.0.1:6379> exists java (integer) 0 127.0.0.1:6379> del not_exist_key (integer) 0 127.0.0.1:6379> exists not_exist_key (integer) 0 複製程式碼
返回結果為 成功刪除 的 鍵的個數 ,假設刪除一個 不存在 的鍵,就會返回 0。
2.5. 鍵過期
expire key seconds
Redis 支援對 鍵 新增 過期時間 ,當超過過期時間後,會 自動刪除鍵 ,例如為鍵 hello 設定 10 秒過期時間:
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> expire hello 10 (integer) 0 複製程式碼
ttl 命令會返回鍵的 剩餘過期時間 ,它有 3 種返回值:
大於等於 0 的整數:表示鍵 剩餘 的 過期時間 。
返回 -1: 鍵 沒設定 過期時間 。
返回 -2: 鍵 不存在。
可以通過 ttl 命令觀察 鍵 hello 的 剩餘過期時間 :
# 還剩7秒 127.0.0.1:6379> ttl hello(integer) (integer) 7 ... # 還剩1秒 127.0.0.1:6379> ttl hello(integer) (integer) 1 # 返回結果為-2,說明鍵hello已經被刪除 127.0.0.1:6379> ttl hello(integer) (integer) -2 127.0.0.1:6379> get hello (nil) 複製程式碼
2.6. 鍵的資料結構型別
type key
例如鍵 hello 是的值 字串型別 ,返回結果為 string。鍵 mylist 的值是 列表型別 ,返回結果為 list。如果鍵不存在,則返回 none。
127.0.0.1:6379> set a b OK 127.0.0.1:6379> type a string 127.0.0.1:6379> rpush mylist a b c d e f g (integer) 7 127.0.0.1:6379> type mylist list 複製程式碼
3. 資料結構和內部編碼
type 命令實際返回的就是當前 鍵 的 資料結構型別 ,它們分別是:string( 字串 )、hash( 雜湊 )、list( 列表 )、set( 集合 )、zset( 有序集合 ),但這些只是 Redis 對外的 資料結構 。如圖所示:

對於每種 資料結構 ,實際上都有自己底層的 內部編碼 實現,而且是 多種實現 。這樣 Redis 會在合適的 場景 選擇合適的 內部編碼 ,如圖所示:

可以看到,每種 資料結構 都有 兩種以上 的 內部編碼實現 。例如 list 資料結構 包含了 linkedlist 和 ziplist 兩種 內部編碼 。同時有些 內部編碼 ,例如 ziplist,可以作為 多種外部資料結構 的內部實現,可以通過 object encoding 命令查詢 內部編碼 :
127.0.0.1:6379> object encoding hello "embstr" 127.0.0.1:6379> object encoding mylist "quicklist" 複製程式碼
可以看到鍵 hello 對應值的 內部編碼 是 embstr,鍵 mylist 對應值的 內部編碼 是 ziplist。
Redis 這樣設計有兩個好處:
其一 :可以改進 內部編碼 ,而對外的 資料結構 和 命令 沒有影響。例如 Redis3.2 提供的 quicklist,結合了 ziplist 和 linkedlist 兩者的優勢,為 列表型別 提供了一種 更加高效 的 內部編碼實現 。
其二 :不同 內部編碼 可以在 不同場景 下發揮各自的 優勢 。例如 ziplist 比較 節省記憶體 ,但是在列表 元素比較多 的情況下, 效能 會有所 下降 ,這時候 Redis 會根據 配置 ,將列表型別的 內部實現 轉換為 linkedlist。
4. 單執行緒架構
Redis 使用了 單執行緒架構 和 I/O 多路複用模型 來實現 高效能 的 記憶體資料庫服務 。那為什麼 單執行緒 還能這麼快,下面分析原因:
4.1. 純記憶體訪問
Redis 將所有資料放在 記憶體 中,記憶體的 響應時長 大約為 100 納秒 ,這是 Redis 達到 每秒萬級別 訪問的重要基礎。
4.2. 非阻塞I/O
Redis 使用 epoll 作為 I/O 多路複用技術 的實現,再加上 Redis 自身的 事件處理模型 將 epoll 中的 連線 、 讀寫 、 關閉 都轉換為 事件 ,從而不用不在 網路 I/O 上浪費過多的時間,如圖所示:

4.3. 單執行緒避免執行緒切換和競態產生的消耗
採用 單執行緒 就能達到如此 高的效能 ,那麼不失為一種不錯的選擇,因為 單執行緒 能帶來幾個好處:
單執行緒 可以簡化 資料結構和演算法 的實現,開發人員不需要了解複雜的 併發資料結構 。
單執行緒 避免了 執行緒切換 和 競態 產生的消耗,對於服務端開發來說, 鎖和執行緒切換 通常是效能殺手。
單執行緒 的問題:對於 每個命令 的 執行時間 是有要求的。如果某個命令 執行過長 ,會造成其他命令的 阻塞 ,對於 Redis 這種 高效能 的服務來說是致命的,所以 Redis 是面向 快速執行 場景的資料庫。
小結
本文堆 Redis 的幾種 資料結構 進行了概述,介紹了幾個簡單的 全域性命令 , 資料結構 和 內部編碼 以及 單執行緒命令 處理機制分析。