Redis的資料型別詳解和使用:key、String型別
持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第11天,點選檢視活動詳情
詳細介紹了Redis的key和String資料型別的底層原理,以及最基本的使用方式。
1 Redis的資料型別
Redis 不是一個普通的key-value儲存伺服器,它實際上是一個數據結構伺服器,支援不同型別的值,在Redis中值不僅限於簡單的字串,還可以包含更復雜的資料結構。
以下是Redis支援的所有資料結構:
- 二進位制(Binary-safe)安全的String(字串)。
- List:根據插入順序排序的String元素的集合,基本上是連結串列。
- Set:唯一的、未排序的String元素的集合。
- Sorted set:類似於Set,但每個String元素都與一個浮點值相關聯,稱為分數。元素總是按它們的分數排序,因此與 Sets 不同,它可以檢索一系列元素(例如,獲取分數前十個或者後十個)。
- Hash:它是由與值關聯的欄位組成的map,欄位和值都是字串,這與 Ruby 或 Python 的Hash非常相似。
- Bit array(或者叫bit map):Bitarray本身不是一種資料結構,實際上它就是String,只不過Redis支援使用特殊命令可以像處理(bitarray)位陣列一樣處理字串值,可以設定和清除單個bit位,計算所有設定為 1 的bit位,找到第一個設定或未設定的bit位等等。這使得Redis的String可被用於儲存、獲取、統計等操作,非常節省空間。
- HyperLogLog:這是一種概率型資料結構,用於快速估計集合的基數。
- Stream:Redis 5.0 版本新增加的資料結構,用於實現可持久化的訊息佇列。
2 Redis key
Redis key是String型別,也是二進位制安全的,我們可以使用任何二進位制序列作為鍵,從像“foo”這樣的字串到 JPEG 檔案的內容。
關於Redis的key有以下規則和建議:
- 太長的key不是個好的選擇,不僅因為消耗記憶體,而且在資料中查詢key也需要進行多次複雜的key比較。
- 太短的鍵值通常也不是好的選擇,如果你要用”u:1000:pwd”來代替”user:1000:password”,這本身沒有什麼問題,但後者更易閱讀,並且由此增加的空間消耗相對於key object和value object本身來說很小。當然,沒人阻止您一定要用更短的鍵值節省一丁點兒空間。
- 最好是堅持一種命名模式。例如:”
object-type:id:field
”就是個不錯的注意,像這樣:“user:1000:password
”。對多單詞的欄位名中可以加上一個點,就像這:“comment:1234:reply.to
”樣:。 - 允許的最大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命令。
這個場景可以有很多種擴充套件方法:
- 通過結合使用INCR和EXPIRE命令,可以實現一個只記錄用戶在指定間隔時間內的訪問次數的計數器。
- 客戶端可以通過GETSET命令獲取當前計數器的值並且重置為0。
- 通過類似於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)
以下是超時相關命令的彙總:
EXPIRE
將key的生存時間設定為秒。PEXPIRE
:將key的生存時間設定為毫秒。EXPIREAT
:將key的過期時間設定為timestamp所代表的的秒數的時間戳。PEXPIREAT
:將key的過期時間設定為timestamp所代表的的毫秒數的時間戳。PTTL
:以毫秒為單位檢查key的剩餘時間。
3.5 彩蛋
Redis的LOLWUT [VERSION version]
指令將會返回包含生成的計算機藝術影象的字串,以及帶有 Redis 版本的文字。本文演示版本為Redis 6.2。
LOLWUT VERSION 5
:
LOLWUT:
官方文件:https://www.redis.com.cn/commands/lolwut.html
相關文章:
- https://redis.io/topics/data-types
- https://redis.io/topics/data-types-intro
如有需要交流,或者文章有誤,請直接留言。另外希望點贊、收藏、關注,我將不間斷更新各種Java學習部落格!
- Java Executor原始碼解析(7)—Executors執行緒池工廠以及四大內建執行緒池
- Java中的物件記憶體佈局以及如何計算物件大小
- Redis的資料型別詳解和使用:key、String型別
- Spring Boot內嵌Tomcat原理
- Spring Boot啟動原始碼分析【一萬字】
- Redis GEO 地理位置的使用與原理解析以及Java實現GEOHash演算法
- Redis的快取穿透、快取雪崩、快取擊穿問題的概念與解決辦法
- Java Executor原始碼解析(5)—ThreadPoolExecutor執行緒池其他方法的原始碼
- Java Executor原始碼解析(3)—ThreadPoolExecutor執行緒池execute核心方法原始碼【一萬字】
- Java Semaphore 訊號量的原始碼深度解析與應用
- Java LongAccumulator原子累加器原始碼深度解析
- Java LongAdder原子累加器原始碼深度解析
- Java SynchronousQueue阻塞傳輸器原始碼深度解析【三萬字】
- 再見 MMKV,自己擼一個FastKV,快的一批
- Android效能優化:全量編譯提速黑科技!
- Android編譯提速黑科技—Wade Plugin
- 資料結構—樹、森林和二叉樹的轉換詳解
- 資料結構—二叉樹的4種遍歷方式詳解以及Java程式碼的完整演示
- 如何絲滑般地載入超大gif圖?
- Java的JVM效能監控與故障處理工具詳細介紹以及使用案例