Redis的資料型別詳解和使用:key、String型別

語言: CN / TW / HK

持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第11天,點選檢視活動詳情

詳細介紹了Redis的key和String資料型別的底層原理,以及最基本的使用方式。

1 Redis的資料型別

Redis 不是一個普通的key-value儲存伺服器,它實際上是一個數據結構伺服器,支援不同型別的值,在Redis中值不僅限於簡單的字串,還可以包含更復雜的資料結構。

以下是Redis支援的所有資料結構:

  1. 二進位制(Binary-safe)安全的String(字串)。
  2. List:根據插入順序排序的String元素的集合,基本上是連結串列。
  3. Set:唯一的、未排序的String元素的集合。
  4. Sorted set:類似於Set,但每個String元素都與一個浮點值相關聯,稱為分數。元素總是按它們的分數排序,因此與 Sets 不同,它可以檢索一系列元素(例如,獲取分數前十個或者後十個)。
  5. Hash:它是由與值關聯的欄位組成的map,欄位和值都是字串,這與 Ruby 或 Python 的Hash非常相似。
  6. Bit array(或者叫bit map):Bitarray本身不是一種資料結構,實際上它就是String,只不過Redis支援使用特殊命令可以像處理(bitarray)位陣列一樣處理字串值,可以設定和清除單個bit位,計算所有設定為 1 的bit位,找到第一個設定或未設定的bit位等等。這使得Redis的String可被用於儲存、獲取、統計等操作,非常節省空間。
  7. HyperLogLog:這是一種概率型資料結構,用於快速估計集合的基數。
  8. Stream:Redis 5.0 版本新增加的資料結構,用於實現可持久化的訊息佇列。

2 Redis key

Redis key是String型別,也是二進位制安全的,我們可以使用任何二進位制序列作為鍵,從像“foo”這樣的字串到 JPEG 檔案的內容。

關於Redis的key有以下規則和建議:

  1. 太長的key不是個好的選擇,不僅因為消耗記憶體,而且在資料中查詢key也需要進行多次複雜的key比較。
  2. 太短的鍵值通常也不是好的選擇,如果你要用”u:1000:pwd”來代替”user:1000:password”,這本身沒有什麼問題,但後者更易閱讀,並且由此增加的空間消耗相對於key object和value object本身來說很小。當然,沒人阻止您一定要用更短的鍵值節省一丁點兒空間。
  3. 最好是堅持一種命名模式。例如:”object-type:id:field”就是個不錯的注意,像這樣:“user:1000:password”。對多單詞的欄位名中可以加上一個點,就像這:“comment:1234:reply.to”樣:。
  4. 允許的最大key的大小為 512 MB

keys命令用於返回服務指定模式的key列表,該命令是阻塞式的,阻塞期間導致Redis服務不可用,可以使用scan命令,該命令無阻塞的提取出指定模式的 key 列表,但有一定重複的概率,可以在客戶端執行去重。

2.1 二進位制安全與SDS

在Redis的學習中,將會看到非常多的binary safe的描述,比如key、String等等,那麼什麼是二進位制安全呢?可以簡單的這麼理解:只關心二進位制化的字串,不關心具體格式,只會嚴格的按照二進位制的資料存取,不會妄圖已某種特殊格式解析資料,即二進位制資料在寫入時是怎麼樣的,讀取時就是怎麼樣。

C語言的字串預設是以'\0'結尾的,也就是說你儲存的字串記憶體在'\0',c語言自會識別前面的資料,後面的就會被忽略掉,所以說是不安全的。比如str=“redis cluster”這個字串,在C中直接讀取的結果就是“redis”,而strlen(str)計算字串長度的結果也是4,這樣就跟原始資料不是相等的,因此就是二進位制不安全的。

Redis雖然是採用C語言來開發的,但是Redis內部儲存的字串資料結構是自己實現的,並不是沿用C語言的字串資料結構。

redis採用SDS(simple dynamic string)來實現簡單動態字串(sds.h/sds.c),同時保證了redis儲存的資料是二進位制安全的,結構如下:

c struct sdshdr { int len; int free; char buf[]; };

實際上的字串底層是一個char陣列(和Java的ArrayList類似),與此同時還儲存了len和free兩個屬性。

len表示該字串的實際內容所佔長度,free表示分配給該字串的全部空間-字串實際內容長度,也就是free表示該字串的空閒空間長度,所以你對該字串取值時是通過len屬性判斷實際內容的長度,然後取的值。

拼接字串時是追加到free空間內中的。所以redis對字串的求長度和更新內容等操作比C語言要快很多,因為求長度只需要返回該字串的len屬性值,C語言想要遍歷整個字串才會知道長度。拼接字串“一般”也不需要在重新分配空間,拼接的字串直接放在free記憶體中就可以了。

因為有了對字串長度定義len,所以在處理字串時候不會以零值位元組(\0)為字串結尾標誌,能夠獲取到完整的輸入的資料,即保證二進位制。

3 Redis String

String是最基本的 Redis 值型別,也是Memcached中的唯一值型別。String是最基本的 Redis 值型別,也是Memcached中的唯一值型別。由於 Redis key是字串,所以當我們也使用字串型別作為值時,我們將一個字串對映到另一個字串。String值的最大長度為 512 MB。

Redis的String是二進位制安全的,這意味著 Redis String可以包含任何型別的資料,例如 JPEG 影象或序列化的 Ruby 物件,為了提高網站執行速度,可以使用String型別快取一些靜態檔案,如圖片檔案、CSS檔案等。

我們可以使用 redis-cli 命令來對 Redis 進行命令列的操作。最基本的操作就是SET和GET命令:

shell 127.0.0.1:6379> set xx yyy OK 127.0.0.1:6379> get xx "yyy"

使用SET命令時,如果此前已存在相同key的快取,則會執行value的替換:

shell 127.0.0.1:6379> set xx yyy OK 127.0.0.1:6379> get xx "yyy"

可以使用SETNX來避免上面這種情況,SETNX 是“SET if Not eXists(如果不存在,則 SET)”的簡寫。只在key 不存在的情況下,將鍵 key 的值設定為 value 。若key 已經存在,則 SETNX 命令不做任何動作,也不會覆蓋原值。

shell 127.0.0.1:6379> GET a (nil) 127.0.0.1:6379> SETNX a aa (integer) 1 127.0.0.1:6379> SETNX a bb (integer) 0 127.0.0.1:6379> GET a "aa"

另一個有趣的命令就是操作是GETSET命令,它為指定key設定新值並且返回原值。這有什麼用處呢?例如:你的系統每當有新使用者訪問時就用INCR命令操作一個Redis key。如果你希望每小時對這個資訊收集一次。你就可以在每個小時的開始GETSET這個key並給其賦值0並讀取原值。

shell 127.0.0.1:6379> get num "100" 127.0.0.1:6379> GETSET num 0 "100" 127.0.0.1:6379> get num "0"

3.1 數值原子操作

即使Redis 的基本值是字串型別,也可以對其進行一些特殊操作,比如進行原子遞增(前提是整數型別的字串)。

shell 127.0.0.1:6379> set num 100 OK 127.0.0.1:6379> incr num (integer) 101 127.0.0.1:6379> incr num (integer) 102 127.0.0.1:6379> get num "102"

INCR命令將字串值解析為10進位制的64位有符號整型資料,將其加一,最後將獲得的值設定為新值。還有其他類似的命令,如 INCRBY——增加指定的值、DECR——自減1 和 DECRBY——減去指定的值。在內部,它實際上是相同的命令,以稍微不同的方式執行。

shell 127.0.0.1:6379> INCRBY num 20 (integer) 122 127.0.0.1:6379> decr num (integer) 121 127.0.0.1:6379> decr num (integer) 120 127.0.0.1:6379> DECRBY num 20 (integer) 100

如果key 不是整數數值型別的字串,使用增減操作將返回一個異常:

shell 127.0.0.1:6379> set flo 11.1 OK 127.0.0.1:6379> INCR flo (error) ERR value is not an integer or out of range

如果指定的key不存在,那麼在執行增減操作之前,會先將它的值設定為0:

shell 127.0.0.1:6379> DECR numm (integer) -1

即使多個客戶端對同一個key發出增減操作的命令,也決不會導致競爭的情況。例如如下情況永遠不可能發生:“客戶端1和客戶端2同時讀出10,他們倆都對其加到11,然後將新值設定為11”。最終的值一定是12。

由於INCR、INCRBY、DECR、DECRBY等數值操作都是原子性的,因此可以用來做很多有用的事情,最常用的使用場景是計數器。

使用思路是:每次有相關操作的時候,就向Redis伺服器傳送一個incr命令。例如這樣一個場景:我們有一個web應用,我們想記錄每個使用者每天訪問這個網站的次數。web應用只需要通過拼接使用者id和代表當天時間的字串作為key,該每次使用者訪問這個頁面的時候對這個key執行一下incr命令。

這個場景可以有很多種擴充套件方法:

  1. 通過結合使用INCR和EXPIRE命令,可以實現一個只記錄用戶在指定間隔時間內的訪問次數的計數器。
  2. 客戶端可以通過GETSET命令獲取當前計數器的值並且重置為0。
  3. 通過類似於DECR或者INCRBY等原子遞增/遞減的命令,可以根據使用者的操作來增加或者減少某些值,比如線上遊戲,需要對使用者的遊戲分數進行實時控制,分數可能增加也可能減少。

另外對於電商領域,也有一種應用就是秒殺業務。

3.2 批量操作

Redis支援在單個命令中設定或檢索多個key的值的能力,這對於對於減少延遲也很有用。

Redis提供了 MSET 和 MGET 命令:

shell 127.0.0.1:6379> MSET a aa b bb c cc d dd OK 127.0.0.1:6379> MGET a b d 1) "aa" 2) "bb" 3) "dd"

當使用 MGET 時,Redis 返回一個值陣列。

3.3 key通用操作

有些命令不是針對特定的值型別,可以與任何型別的key一起使用。

例如,使用EXISTS命令判斷key對應的值是否存在,將會返回1或0。也可以傳入多個key,將會返回存在的key的總和:

shell 127.0.0.1:6379> keys * 1) "b" 2) "c" 3) "d" 4) "a" 5) "num" 127.0.0.1:6379> EXISTS a (integer) 1 127.0.0.1:6379> EXISTS a b (integer) 2 127.0.0.1:6379> EXISTS a b e (integer) 2 127.0.0.1:6379> EXISTS e (integer) 0

使用DEL命令可以刪除key對應的值,將會返回1或0,標識值是被成功刪除(值存在)或者沒被刪除(key對應的值本就不存在)。也可以傳入多個key,將會返回成功刪除的key的總和:

shell 127.0.0.1:6379> DEL a c (integer) 2 127.0.0.1:6379> DEL a c (integer) 0 127.0.0.1:6379> DEL b (integer) 1 127.0.0.1:6379> DEL a (integer) 0

TYPE命令可以返回key對應的值的儲存型別:

shell 127.0.0.1:6379> KEYS * 1) "d" 2) "num" 127.0.0.1:6379> TYPE num string 127.0.0.1:6379> TYPE d string 127.0.0.1:6379> DEL num d (integer) 2 127.0.0.1:6379> TYPE num none 127.0.0.1:6379> TYPE d none

可以對任何值型別的key設定超時時間,當這個時間到達後key-value會被刪除。精度可以使用毫秒或秒,但過期時間的解析度始終為 1 毫秒。

通常使用EXPIRE來設定超時時間,預設單位是秒,也可以再次呼叫這個命令來改變超時時間:

shell 127.0.0.1:6379> set a aa OK 127.0.0.1:6379> set b bb OK 127.0.0.1:6379> EXPIRE a 8 (integer) 1 127.0.0.1:6379> GET a (nil) 127.0.0.1:6379> GET b "bb"

有關過期的資訊被複制並儲存在磁碟上,實際上Redis會儲存key的過期時間點,當Redis 伺服器停止時,時間實際上已經過去了,重啟時該過期key將被刪除。

使在key過期之前用PERSIST命令可以去除key的超時時間:

shell 127.0.0.1:6379> SET a aa OK 127.0.0.1:6379> EXPIRE a 8 (integer) 1 127.0.0.1:6379> PERSIST a (integer) 1 127.0.0.1:6379> GET a "aa"

我們也可以在設定key-value的時候設定超時時間,使用TTL檢視key的剩餘時間:

shell 127.0.0.1:6379> SET a aa ex 8 OK 127.0.0.1:6379> TTL a (integer) 4 127.0.0.1:6379> TTL a (integer) 2 127.0.0.1:6379> GET a (nil)

以下是超時相關命令的彙總:

  1. EXPIRE 將key的生存時間設定為秒。
  2. PEXPIRE:將key的生存時間設定為毫秒。
  3. EXPIREAT:將key的過期時間設定為timestamp所代表的的秒數的時間戳。
  4. PEXPIREAT:將key的過期時間設定為timestamp所代表的的毫秒數的時間戳。
  5. PTTL:以毫秒為單位檢查key的剩餘時間。

3.5 彩蛋

Redis的LOLWUT [VERSION version]指令將會返回包含生成的計算機藝術影象的字串,以及帶有 Redis 版本的文字。本文演示版本為Redis 6.2。

LOLWUT VERSION 5

在這裡插入圖片描述

LOLWUT: 在這裡插入圖片描述

官方文件:https://www.redis.com.cn/commands/lolwut.html

相關文章:

  1. https://redis.io/topics/data-types
  2. https://redis.io/topics/data-types-intro

如有需要交流,或者文章有誤,請直接留言。另外希望點贊、收藏、關注,我將不間斷更新各種Java學習部落格!