前言
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?的幾種?數據結構?進行了概述,介紹了幾個簡單的?全局命令,數據結構?和?內部編碼?以及?單線程命令?處理機制分析。