Redis數(shù)據(jù)類型超詳細講解分析
1 Redis
Redis
官網(wǎng)英文版:https://redis.io/
Redis
官網(wǎng)中文版:http://redis.cn/
1.1 概述
Redis
是以key-value
存儲的數(shù)據(jù)結(jié)構(gòu)服務(wù)器,所有的key(鍵)
是字符串,而value
可以包含:
- 字符串類型(
string
):最基本的數(shù)據(jù)類型,二進制安全的字符串,最大512M
- 列表類型(
list
):按照添加順序保持順序的字符串列表 - 集合類型(
set
):無序的字符串集合,不存在重復(fù)的元素 - 有序集合類型(
sorted set
或Zset
):已排序的字符串集合 - 散列類型(
hash
):key-value對的一種集合 - 位操作(
bitmap
):更細化的一種操作,以bit
為單位。 - 基數(shù)統(tǒng)計(
hyperloglog
):基于概率的數(shù)據(jù)結(jié)構(gòu),2.8.9新增 - 地理位置(
Geo
):地理位置信息儲存起來, 并對這些信息進行操作 3.2新增 - 流(
Stream
) 5.0新增
1.2 查看內(nèi)部編碼
Redis
查看內(nèi)部編碼使用OBJECT ENCODING
命令
該命令用來返回數(shù)據(jù)結(jié)構(gòu)的內(nèi)部編碼
對象所使用的底層數(shù)據(jù)結(jié)構(gòu) | 編碼常量 | object encoding 命令輸出 |
---|---|---|
整數(shù) | REDIS_ENCODING_INT | “int” |
embstr編碼簡單動態(tài)字符串(SDS) | REDIS_ENCODING_EMBSTR | “embstr” |
簡單動態(tài)字符串 | REDIS_ENCODING_RAW | “raw” |
字典 | REDIS_ENCODING_HT | “hashtable” |
雙端鏈表 | REDIS_ENCODING_LINKEDLIST | “linkedlist” |
壓縮列表 | REDIS_ENCODING_ZIPLiST | “ziplist” |
整數(shù)集合 | REDIS_ENCODING_INTSET | “intset” |
跳躍表和字典 | REDIS_ENCODING_SKIPLIST | “skiplist” |
1.3 String字符串
1.3.1 簡介
String
是redis中最基本的數(shù)據(jù)類型,一個key對應(yīng)一個value。
redis的key和string類型value限制均為512MB雖然Key的大小上限為512M
,但是一般建議key的大小不要超過1KB
,這樣既可以節(jié)約存儲空間,又有利于Redis
進行檢索
1.3.2 應(yīng)用常景
String
類型是二進制安全的,意思是 redis
的 string
可以包含任何數(shù)據(jù)。如數(shù)字
,字符串
,jpg圖片
或者序列化
的對象。字符串類型實際上可以是字符串(簡單的字符串、復(fù)雜的字符串(xml、json)、數(shù)字(整數(shù)、浮點數(shù))、二進制(圖片、音頻、視頻))
緩存: 經(jīng)典使用場景,把常用信息,字符串,圖片或者視頻等信息放到redis中,redis作為緩存層,mysql做持久化層,降低mysql的讀寫壓力。
1.3.3 String內(nèi)部編碼
String
內(nèi)部編碼:
int
:8個字節(jié)的長整型(long,2^63-1)embstr
:小于等于44
個字節(jié)的字符串,embstr
格式的SDS(簡單動態(tài)字符串:Simple Dynamic String)
raw
:SDS大于 44
個字節(jié)的字符串
Redis
為什么要自己寫一個SDS
的數(shù)據(jù)類型,主要是為了解決C語言 char[]
的四個問題:
字符數(shù)組
必須先給目標變量分配足夠的空間,否則可能會溢出- 查詢字符數(shù)組長度,時間復(fù)雜度
O(n)
- 長度變化,需要重新分配內(nèi)存
- 通過從字符串開始到結(jié)尾碰到的第一個
\0
來標記字符串的結(jié)束,因此不能保存圖片、音頻、視頻、壓縮文件等二進制(bytes
)保存的內(nèi)容,二進制不安全
Redis SDS
的優(yōu)勢:
- 不用擔(dān)心內(nèi)存溢出問題,如果需要會對
SDS
進行擴容 - 因為定義了
len
屬性,查詢數(shù)組長度時間復(fù)雜度O(1)
固定長度 - 空間預(yù)分配,惰性空間釋放
- 根據(jù)長度
len
來判斷是否結(jié)束,而不是\0
為什么要有embstr
編碼呢?比raw
的優(yōu)勢在哪里?
embstr
編碼將創(chuàng)建字符串對象所需的空間分配的次數(shù)從raw
編碼的兩次降低為一次。
因為emstr
編碼字符串的素有對象保持在一塊連續(xù)的內(nèi)存里面,所以那個編碼的字符串對象比起raw
編碼的字符串對象能更好的利用緩存。并且釋放embstr
編碼的字符串對象只需要調(diào)用一次內(nèi)存釋放函數(shù),而釋放raw
編碼對象的字符串對象需要調(diào)用兩次內(nèi)存釋放函數(shù)
1.4 Hash散列
1.4.1 簡介
常用命令:hget,hsetnx,hset,hvals,hgetall,hmset,hmget 等Redis
中每個 hash
可以存儲 2^32 - 1
鍵值對(40多億)
1.4.2 應(yīng)用常景
我們簡單舉個實例來描述下 Hash 的應(yīng)用場景:
比如我們要存儲一個用戶信息對象數(shù)據(jù),包含以下信息:用戶 ID 為查找的 key,存儲的 value 用戶對象包含姓名,年齡,生日等信息,如果用普通的 key/value 結(jié)構(gòu)來存儲,主要有以下2種存儲方式:
- 第一種方式將用戶 ID 作為查找 key,把其他信息封裝成一個對象以序列化的方式存儲,這種方式的缺點是,增加了序列化/反序列化的開銷,并且在需要修改其中一項信息時,需要把整個對象取回,并且修改操作需要對并發(fā)進行保護,引入CAS等復(fù)雜問題。
- 第二種方法是這個用戶信息對象有多少成員就存成多少個 key-value 對兒,用用戶 ID +對應(yīng)屬性的名稱作為唯一標識來取得對應(yīng)屬性的值,雖然省去了序列化開銷和并發(fā)問題,但是用戶 ID 為重復(fù)存儲,如果存在大量這樣的數(shù)據(jù),內(nèi)存浪費還是非常可觀的。
那么 Redis
提供的 Hash
很好的解決了這個問題,Redis
的 Hash
實際是內(nèi)部存儲的 Value
為一個 HashMap
,并提供了直接存取這個 Map
成員的接口
也就是說,Key 仍然是用戶 ID,value 是一個 Map,這個 Map 的 key 是成員的屬性名,value 是屬性值,這樣對數(shù)據(jù)的修改和存取都可以直接通過其內(nèi)部 Map 的 Key(Redis 里稱內(nèi)部 Map 的 key 為 field),也就是通過 key(用戶 ID) + field(屬性標簽)就可以操作對應(yīng)屬性數(shù)據(jù)了,既不需要重復(fù)存儲數(shù)據(jù),也不會帶來序列化和并發(fā)修改控制的問題。
很好的解決了問題。這里同時需要注意,Redis
提供了接口(hgetall
)可以直接取到全部的屬性數(shù)據(jù),但是如果內(nèi)部 Map 的成員很多,那么涉及到遍歷整個內(nèi)部 Map
的操作,由于 Redis
單線程模型的緣故,這個遍歷操作可能會比較耗時,而另其它客戶端的請求完全不響應(yīng),這點需要格外注意。
1.4.3 Hash內(nèi)部編碼
內(nèi)部編碼:
ziplist
(壓縮列表):當(dāng)哈希類型中元素個數(shù)小于hash-max-ziplist-entries
配置(默認512
個),同時所有值都小于hash-max-ziplist-value
配置(默認64 字節(jié)
)時,Redis
會使用ziplist
作為哈希的內(nèi)部實現(xiàn)。hashtable
(哈希表):當(dāng)上述條件不滿足時,Redis
則會采用hashtable
作為哈希的內(nèi)部實現(xiàn)。
在 Redis 7.0
中,壓縮列表
數(shù)據(jù)結(jié)構(gòu)已經(jīng)廢棄了,交由 listpack
數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)了
1.4.4 rehash和漸進式rehash操作
擴容和縮容都會通過rehash
來實現(xiàn),所謂漸進式rehash
是指我們的大字典的擴容是比較消耗時間的,需要重新申請新的數(shù)組,然后將舊字典所有鏈表的元素重新掛接到新的數(shù)組下面,是一個O(n)
的操作。但是因為我們的redis
是單線程的,無法承受這樣的耗時過程,所以采用了漸進式rehash
小步搬遷,雖然慢一點,但是可以搬遷完畢
redis
會在內(nèi)部擴容時新建一個長度為原始長度2倍的空哈希表,然后原哈希表上的元素重新rehash
到新的哈希表中去,然后我們再使用新的哈希表即可。
那么,這樣還是有個問題要解決呀
要知道
redis
中存儲的數(shù)據(jù)可能是成百萬上千萬的,我們重新rehash
一次未免太耗時了吧,因為redis中操作大部分是單線程的。
這個過程可能會阻斷其他操作很長時間,這是不能忍受的,那要怎么處理呢
1.4.4.1 過程
首先redis
是采用了漸進式rehash
的操作,就是會有一個變量,指向第一個哈希桶,然后redis
每執(zhí)行一個添加key
,刪除key
的類似命令,就順便copy
一個哈希桶中的數(shù)據(jù)到新的哈希表中去,這樣細水長流的操作,是不會影響什么性能,就會所有的數(shù)據(jù)都被重新hash
到新的哈希表中。
那么在這個過程中,當(dāng)然再有寫的操作,會直接把數(shù)據(jù)放到新的哈希表中,保證舊的肯定有copy完的時候,如果這段時間對數(shù)據(jù)庫的操作比較少,也沒有關(guān)系,redis
內(nèi)部也有定時任務(wù),每隔一段時間也會copy
一次
redis
通過鏈式哈希解決沖突,也就是同一個桶里面的元素使用鏈表
保存。但是當(dāng)鏈表過長就會導(dǎo)致查找性能變差可能。所以redis
為了追求塊,使用了兩個全局哈希表
。用于rehash
操作,增加現(xiàn)有的哈希桶數(shù)量,減少哈希沖突
開始默認使用hash表1
保存鍵值對數(shù)據(jù),hash表2
此刻沒有分配空間。當(dāng)數(shù)據(jù)越來越多的觸發(fā)rehash
操作,則執(zhí)行以下操作:
- 給
hash表2
分配更大的空間 - 將
hash表1
的數(shù)據(jù)重新映射拷貝到hash表2
中
將hash表1的數(shù)據(jù)重新映射到hash表2的過程并不是一次性的,這樣會造成redis阻塞,無法提供服務(wù) - 釋放
hash表1
的空間
詳細步驟:
- 為ht[1]分配空間,讓字典同時持有ht[0]和ht[1]兩個hash表
- 在字典中維持一個索引計數(shù)器變量rehashidx,并將它的值設(shè)置為0,表示rehash工作正式開始
- 在rehash進行期間,每次對字典執(zhí)行添加,刪除,查找或者更新操作時,程序除了執(zhí)行特定的操作以外,還會順帶將ht[0]哈希表在rehashidx索引上的所有鍵值對rehash到ht[1],當(dāng)rehash工作完成之后,程序?qū)ehashidx屬性的值增1
- 隨著字典操作的不斷執(zhí)行,最終在某個時間點上,ht[0]的所有鍵值對都會被rehash至ht[1],這時程序?qū)ehashidx屬性的值設(shè)為-1,表示rehash操作已完成
- 將ht[0]釋放,然后將ht[1]設(shè)置成ht[0],最后為ht[1]分配一個空白哈希表
1.4.4.2 rehash觸發(fā)條件
rehash
觸發(fā)條件:
- 擴容
我們的擴容一般會在Hash
表中的元素個數(shù)等于第一維數(shù)組的長度的時候,就會開始擴容。擴容的大小是原數(shù)組的兩倍。不過在redis
在做bgsave
(RDB持久化操作的過程)時,為了減少內(nèi)存頁的過多分離(Copy On Write),redis
不會去擴容。
但是如果hash表的元素個數(shù)已經(jīng)到達了第一維數(shù)組長度的5倍的時候,就會強制擴容,不管你是否在持久化。 - 縮容
當(dāng)我們的hash
表元素逐漸刪除的越來越少的時候。redis
就會對hash
表進行縮容來減少第一維數(shù)組長度的空間占用??s容的條件是元素個數(shù)低于數(shù)組長度的10%
,并且縮容不考慮是否在做redis持久化
。
不用考慮bgsave
主要原因是因為我們的縮容的內(nèi)存都是已經(jīng)使用過的,縮容的時候可以直接置空,而且由于申請的內(nèi)存比較小,同時會釋放掉一些已經(jīng)使用的內(nèi)存,不會增大系統(tǒng)的壓力。
1.4.5 跟JDK的HashMap的區(qū)別
數(shù)據(jù)結(jié)構(gòu)上,采用了兩個數(shù)組保存數(shù)據(jù),發(fā)生hash沖突時,只采用了鏈地址法
解決hash
沖突,并沒有跟jdk1.8
一樣當(dāng)鏈表超過8時優(yōu)化成紅黑樹,因此插入元素時跟jdk1.7
的hashmap
一樣采用的是頭插法
。
在發(fā)生擴容時,跟jdk的hashmap一次性、集中式進行擴容不一樣,采取的是漸進式的rehash,每次操作只會操作當(dāng)前的元素,在當(dāng)前數(shù)組中移除或者存放到新的數(shù)組中,直到老數(shù)組的元素徹底變成空表。
當(dāng)負載因子小于0.1
時,會自動進行縮容
。jdk的hashmap出于性能考慮,不提供縮容的操作。redis
使用MurmurHash
來計算哈希表的鍵的hash值,而jdk
的hashmap
使用key.hashcode()的高十六位跟低十六位做與運算獲得鍵的hash值。
1.5 List列表
1.5.1 簡介
Redis
中的List
其實就是鏈表
(Redis
用雙端鏈表
實現(xiàn)List
)
使用List
結(jié)構(gòu),我們可以輕松地實現(xiàn)最新消息排隊功能(比如新浪微博的TimeLine)。List的另一個應(yīng)用就是消息隊列,可以利用List的 PUSH 操作,將任務(wù)存放在List中,然后工作線程再用 POP 操作將任務(wù)取出進行執(zhí)行。
列表(List
)用來存儲多個有序的字符串,每個字符串稱為元素;一個列表可以存儲2^32-1
個元素。Redis
中的列表支持兩端插入和彈出,并可以獲得指定位置(或范圍)的元素,可以充當(dāng)數(shù)組、隊列、棧等
1.5.2 命令和應(yīng)用
常用命令:lpush,rpush,lpop,rpop,lrange
等。
應(yīng)用場景
比如 twitter 的關(guān)注列表,粉絲列表等都可以用 Redis 的 list 結(jié)構(gòu)來實現(xiàn),可以利用lrange命令,做基于Redis的分頁功能,性能極佳,用戶體驗好
消息隊列:Redis 的 list 是有序的列表結(jié)構(gòu),可以實現(xiàn)阻塞隊列,使用左進右出的方式。Lpush 用來生產(chǎn) 從左側(cè)插入數(shù)據(jù),Brpop 用來消費,用來從右側(cè) 阻塞的消費數(shù)據(jù)。
數(shù)據(jù)的分頁展示: lrange 命令需要兩個索引來獲取數(shù)據(jù),這個就可以用來實現(xiàn)分頁,可以在代碼中計算兩個索引值,然后來 redis 中取數(shù)據(jù)。
可以用來實現(xiàn)粉絲列表以及最新消息排行等功能
使用列表的技巧:
lpush+lpop=Stack
(棧)lpush+rpop=Queue
(隊列)lpush+ltrim=Capped Collection
(有限集合)lpush+brpop=Message Queue
(消息隊列)
1.5.3 List內(nèi)部編碼
內(nèi)部編碼:
ziplist
(壓縮列表):當(dāng)列表中元素個數(shù)小于512
(默認)個,并且列表中每個元素的值都小于64
(默認)個字節(jié)時,Redis
會選擇用ziplist
來作為列表的內(nèi)部實現(xiàn)以減少內(nèi)存的使用。當(dāng)然上述默認值也可以通過相關(guān)參數(shù)修改:list-max-ziplist-entried
(元素個數(shù))、list-max-ziplist-value
(元素值)。linkedlist
(鏈表):當(dāng)列表類型無法滿足ziplist
條件時,Redis
會選擇用linkedlist
作為列表的內(nèi)部實現(xiàn)。
因為雙向鏈表
占用的內(nèi)存比壓縮列表
要多, 所以當(dāng)創(chuàng)建新的列表鍵時, 列表會優(yōu)先考慮使用壓縮列表, 并且在有需要的時候, 才從壓縮列表實現(xiàn)轉(zhuǎn)換到雙向鏈表實現(xiàn)quicklist
(快速列表)就是linkedlist
和ziplist
的結(jié)合。quicklist
中的每個節(jié)點ziplist
都能夠存儲多個數(shù)據(jù)元素。Redis3.2
開始,列表采quicklist
進編碼
1.6 Set集合
1.6.1 簡介
Redis
的 Set
是 String
類型的無序集合。集合成員是唯一的,這就意味著集合中不能出現(xiàn)重復(fù)的數(shù)據(jù)。Redis
中集合是通過哈希表實現(xiàn)的,所以添加,刪除,查找的復(fù)雜度都是 O(1)
集合中最大的成員數(shù)為 2^32 - 1
(每個集合可存儲40多億個成員)
1.6.2 命令和應(yīng)用
常用命令:sadd,spop,smembers,sunion,scard,sscan,sismember等。
應(yīng)用場景:
Redis set
對外提供的功能與 list
類似是一個列表的功能,特殊之處在于 set
是可以自動去重的,當(dāng)你需要存儲一個列表數(shù)據(jù),又不希望出現(xiàn)重復(fù)數(shù)據(jù)時,set
是一個很好的選擇,并且 set
提供了判斷某個成員是否在一個 set
集合內(nèi)的重要接口,這個也是 list 所不能提供的。
標簽(tag):集合類型比較典型的使用場景,如一個用戶對娛樂、體育比較感興趣,另一個可能對新聞感興趣,這些興趣就是標簽,有了這些數(shù)據(jù)就可以得到同一標簽的人,以及用戶的共同愛好的標簽,這些數(shù)據(jù)對于用戶體驗以及曾強用戶粘度比較重要。
點贊,或點踩,收藏等,可以放到set中實現(xiàn)
1.6.3 Set內(nèi)部編碼
Set
內(nèi)部編碼:
intset
(整數(shù)集合):當(dāng)集合中的元素都是整數(shù),并且集合中的元素個數(shù)小于set-max-intset-entries
參數(shù)時,默認512
,Redis
會選用intset
作為底層內(nèi)部實現(xiàn)。hashtable
(哈希表):當(dāng)上述條件不滿足時,Redis
會采用hashtable
作為底層實現(xiàn)。
1.7 ZSet有序集合
1.7.1 簡介
Redis
有序集合和集合一樣也是 string
類型元素的集合,且不允許重復(fù)的成員。不同的是每個元素都會關(guān)聯(lián)一個 double
類型的分數(shù)
。redis
正是通過分數(shù)來為集合中的成員進行從小到大的排序。
有序集合的成員是唯一的,但分數(shù)(score
)卻可以重復(fù)。集合是通過哈希表實現(xiàn)的,所以添加,刪除,查找的復(fù)雜度都是 O(1)。
集合中最大的成員數(shù)為 2^32 - 1
(每個集合可存儲40多億個成員)
1.7.2 命令和應(yīng)用
常用命令:zadd,zrange,zrem,zcard,zscore,zcount,zlexcount等
應(yīng)用常景:
- 排行榜:有序集合經(jīng)典使用場景
例如小說視頻等網(wǎng)站需要對用戶上傳的小說視頻做排行榜,榜單可以按照用戶關(guān)注數(shù),更新時間,字數(shù)等打分,做排行,
如新聞網(wǎng)站對熱點新聞排序,比如根據(jù)點擊量、點贊量等。 - 帶權(quán)重的消息隊列:重要的消息
score
大一些,普通消息score
小一些,可以實現(xiàn)優(yōu)先級高的任務(wù)先執(zhí)行
1.7.3 ZSet內(nèi)部編碼
內(nèi)部編碼:
ziplist
(壓縮列表):當(dāng)有序集合的元素個數(shù)小于128
個(默認設(shè)置),同時每個元素的值都小于64
字節(jié)(默認設(shè)置),Redis
會采用ziplist
作為有序集合的內(nèi)部實現(xiàn)。也可以通過以下參數(shù)設(shè)置:zset-max-ziplist-entries
和zset-max-ziplist-value
skiplist
(跳躍表):當(dāng)上述條件不滿足時,Redis
會采用skiplist
作為內(nèi)部編碼。
在 Redis 7.0
中,壓縮列表數(shù)據(jù)結(jié)構(gòu)已經(jīng)廢棄了,交由 listpack
數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)了
1.7.4 跳表數(shù)據(jù)結(jié)構(gòu)
鏈表
在查找元素的時候,因為需要逐一查找,所以查詢效率非常低,時間復(fù)雜度是O(N)
,于是就出現(xiàn)了跳表。跳表是在鏈表基礎(chǔ)上改進過來的,實現(xiàn)了一種 多層
的有序鏈表,這樣的好處是能快讀定位數(shù)據(jù)。
那跳表長什么樣呢?這里舉個例子,下圖展示了一個層級為 3 的跳表。
圖中頭節(jié)點有 L0~L2
三個頭指針,分別指向了不同層級的節(jié)點,然后每個層級的節(jié)點都通過指針連接起來:
- L0 層級共有 5 個節(jié)點,分別是節(jié)點1、2、3、4、5;
- L1 層級共有 3 個節(jié)點,分別是節(jié)點 2、3、5;
- L2 層級只有 1 個節(jié)點,也就是節(jié)點 3 。
如果我們要在鏈表中查找節(jié)點 4 這個元素,只能從頭開始遍歷鏈表,需要查找 4 次,而使用了跳表后,只需要查找 2 次就能定位到節(jié)點 4,因為可以在頭節(jié)點直接從 L2 層級跳到節(jié)點 3,然后再往前遍歷找到節(jié)點 4。
可以看到,這個查找過程就是在多個層級上跳來跳去,最后定位到元素。當(dāng)數(shù)據(jù)量很大時,跳表的查找復(fù)雜度就是 O(logN)
。
跳表是怎么設(shè)置層高的?
跳表在創(chuàng)建節(jié)點時候,會生成范圍為
[0-1]
的一個隨機數(shù),如果這個隨機數(shù)小于0.25
(相當(dāng)于概率 25%),那么層數(shù)就增加 1 層,然后繼續(xù)生成下一個隨機數(shù),直到隨機數(shù)的結(jié)果大于 0.25 結(jié)束,最終確定該節(jié)點的層數(shù)。
1.8 Bitmap位圖和布隆過濾器
1.8.1 Bitmap位圖
1.8.1.1 簡介
Bitmap
(也稱為位數(shù)組或者位向量等)是一種實現(xiàn)對位的操作的’數(shù)據(jù)結(jié)構(gòu)’,在數(shù)據(jù)結(jié)構(gòu)加引號主要因為:Bitmap
本身不是一種數(shù)據(jù)結(jié)構(gòu),底層實際上是字符串,可以借助字符串進行位操作。
Bitmap
單獨提供了一套命令,所以與使用字符串的方法不太相同??梢园?Bitmaps 想象成一個以位為單位的數(shù)組,數(shù)組的每個單元只能存儲 0
和 1
,數(shù)組的下標在 Bitmap
中叫做偏移量 offset
。bitmap
的出現(xiàn)是為了大數(shù)據(jù)量而來的,但是前提是統(tǒng)計的這個大數(shù)據(jù)量每個的狀態(tài)只能有兩種
,因為每一個bit
位只能表示兩種狀態(tài)。
優(yōu)點:
- 極高空間效率:bitmap 是真的節(jié)省數(shù)據(jù)存儲空間。粗略的算一下,一億位的 Bitmap 大概才占 12MB 的內(nèi)存,相比其他數(shù)據(jù)結(jié)構(gòu),能極大地節(jié)省存儲空間;
- 快速查詢:位操作通常比其他數(shù)據(jù)結(jié)構(gòu)查詢速度更快。無論是設(shè)置位值還是獲取位值,時間復(fù)雜度都為 O (1),能夠快速響應(yīng)查詢請求;
- 易于操作:支持單個位操作、位統(tǒng)計、位邏輯運算等,運算效率高,不需要進行比較和移位;
缺點:
- 由于數(shù)據(jù)結(jié)構(gòu)特點,導(dǎo)致它僅適用于表示兩種狀態(tài),即 0 和 1。對于需要表示更多狀態(tài)的情況,Bitmap 就不適用了;
- 只有當(dāng)數(shù)據(jù)比較密集時才有優(yōu)勢,如果只設(shè)置(20,30,888888888)三個偏移量的位值,則需要創(chuàng)建一個 99999999 長度的
BitMap
,但是實際上只存了3個數(shù)據(jù),這時候就有很大的空間浪費,碰到這種問題的話,可以通過引入另一個Roaring BitMap
來解決;
1.8.1.2 應(yīng)用常景
假如我們現(xiàn)在有幾億個數(shù)據(jù),數(shù)據(jù)狀態(tài)都是1或者0兩個狀態(tài),比如用戶簽到次數(shù)、或者登錄次數(shù)等。
- 場景一:用戶簽到
很多網(wǎng)站都提供了簽到功能(這里不考慮數(shù)據(jù)落地事宜),并且需要展示最近一個月的簽到情況Redis
為我們提供了bitmap
(位圖)這一數(shù)據(jù)結(jié)構(gòu),每個用戶每天的登錄記錄只占據(jù)一位,365
天就是365
位,僅僅需要46字節(jié)就可存儲,極大地節(jié)約了存儲空間。 - 場景二:統(tǒng)計活躍用戶
使用時間作為cacheKey,然后用戶ID為offset,如果當(dāng)日活躍過就設(shè)置為1
那么我該如果計算某幾天/月/年的活躍用戶呢(暫且約定,統(tǒng)計時間內(nèi)只有有一天在線就稱為活躍),有請下一個redis的命令 - 場景三:用戶在線狀態(tài)
前段時間開發(fā)一個項目,對方給我提供了一個查詢當(dāng)前用戶是否在線的接口。不了解對方是怎么做的,自己考慮了一下,使用bitmap是一個節(jié)約空間效率又高的一種方法,只需要一個key,然后用戶ID為offset,如果在線就設(shè)置為1,不在線就設(shè)置為0,和上面的場景一樣,5000W用戶只需要6MB的空間
1.8.1.3 底層原理
我們知道 Bitmap
本身不是一種數(shù)據(jù)結(jié)構(gòu),底層實際上使用字符串
來存儲。只不過操作的粒度變成了位,即bit。
由于 Redis
中字符串的最大長度是 512 MB
字節(jié),所以 BitMap
的偏移量 offset
值也是有上限的,其最大值是:8 * 1024 * 1024 * 512 = 2^32
。由于 C 語言中字符串的末尾都要存儲一位分隔符,所以實際上 BitMap
的偏移量 offset 值上限是:2^32-1
Bitmap
本身是用 String
類型作為底層數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的一種統(tǒng)計二值狀態(tài)的數(shù)據(jù)類型。String
類型是會保存為二進制的字節(jié)數(shù)組,所以,Redis
就把字節(jié)數(shù)組的每個 bit
位利用起來,用來表示一個元素的二值狀態(tài)??梢园?nbsp;Bitmap
看作是一個 bit
數(shù)組。
1.8.1.4 命令
- SETBIT
SETBIE
用來設(shè)置或清除存儲在鍵處的字符串值的偏移位,其返回值是原來位上存儲的值。key 在初始狀態(tài)下所有的位都為 0
基本格式:SETBIT key offset value
- GETBIT
GETBIT
用來獲取存儲在鍵處的字符串值中偏移位置的位值。
基本格式:GETBIT key offset
- BITCOUNT
BITCOUNT
用來統(tǒng)計指定區(qū)間內(nèi),值為1的個數(shù)。選擇特定的byte
范圍計數(shù),具體如下
基本格式:BITCOUNT key [start end]
(注意start和end指的是字節(jié),不是位)start
:設(shè)置位索引起始位置(包含該位置計數(shù)),第一個位置以 0 開始,start
參數(shù)需和end
參數(shù)同時設(shè)置才合法end
:設(shè)置位索引結(jié)束位置(包含該位置計數(shù)),end
參數(shù)需和start
參數(shù)同時設(shè)置才合法
- BITOP
對一個或多個保存二進制位的字符串key
進行位元操作,并將結(jié)果保存到destkey
上
語法格式:BITOP operation destkey key [key ...]
語法:operation
可以是AND(與) 、 OR (或)、 NOT(非) 、 XOR(異或)
除了NOT
操作之外,其他操作都可以接受一個或多個 key 作為輸入 - BITPOS
用來計算指定key
對應(yīng)字符串中,第一位為1
或者0
的offset
位置。除此之外,BITPOS
也有兩個選項start
和end
,跟BITCOUNT
一樣。
語法格式:BITPOS key bit [ start [ end [ BYTE | BIT]]]
BYTE、BIT 這兩個選項從 7.0.0 版本開始才能使用。
1.8.2 布隆過濾器
1.8.2.1 簡介
上邊提到 bitmap
記錄字符元素的狀態(tài)時,需要先借助哈希運算得出偏移量。但引入哈希運算后可能會出現(xiàn)哈希碰撞的情況,導(dǎo)致狀態(tài)誤判。
布隆過濾器對這個問題做了進一步的優(yōu)化,做到了可控誤判率,當(dāng)我們將一個郵箱地址添加到集合中,多個不同的哈希函數(shù)會將這個郵箱地址映射到 bitmap 中的不同偏移量位置上,且將這些位值置為 1。
要判斷郵箱地址是否在集合中,通過相同的哈希函數(shù)映射到 bitmap 上的多個位置,如果這些位上的值都為 1,則郵箱可能存在集合中;如果有任何一個位置的值為 0,則元素一定不在集合中。這是布隆過濾器的特點。
雖然但是布隆過濾器還是會發(fā)生誤判的情況,但好在我們可以通過調(diào)整布隆過濾器的大小和哈希函數(shù)的數(shù)量來控制誤判率。
注意
:需要Redis
服務(wù)器版本是4.0或以上,因為Redis 4.0
引入了插件機制,支持布隆過濾器等模塊的加載
配置的話是,打開Redis
的配置文件redis.conf
,這個文件通常位于Redis的安裝目錄下。在配置文件中,添加以下行來加載布隆過濾器模塊:loadmodule /path/to/redisbloom.so
1.8.2.2 操作命令
布隆過濾器的命令也不多,主要用到的如下幾個:
BF.RESERVE
:創(chuàng)建一個新的布隆過濾器,并指定容量 capacity
和誤判率 error_rate
。
BF.RESERVE <key> <error_rate> <capacity> BF.RESERVE myfilter 0.000001 999999
BF.INFO
:獲取布隆過濾器的信息,包括容量、誤判率等。BF.INFO <key>
BF.ADD
和 BF.MADD
:分別是向布隆過濾器中添加元素和批量添加
# 向布隆過濾器中添加元素 BF.ADD myfilter hello BF.MADD <key> <item> [item ...]
BF.EXISTS
和 BF.MEXISTS
:分別是檢查布隆過濾器中某個元素和批量檢查元素是否存在
# 元素是否存在于布隆過濾器中 BF.EXISTS myfilter hello # 元素是否存在于布隆過濾器中 BF.MEXISTS <key> <item> [item ...]
1.8.2.3 優(yōu)缺點
- 優(yōu)點:
- 布隆過濾器的空間占用也是極小,它本身不存儲完整的數(shù)據(jù),和
bitmap
一樣底層也是通過bit
位來表示數(shù)據(jù)是否存在。 - 性能比較穩(wěn)定,無論集合中元素的數(shù)量有多少,插入和查詢操作的時間復(fù)雜度都非常低,通常為 O (k),其中 k 是哈希函數(shù)的個數(shù)。也就是說在處理大規(guī)模數(shù)據(jù)時,布隆過濾器的性能不會隨著數(shù)據(jù)量的增加而急劇下降。
- 布隆過濾器的空間占用也是極小,它本身不存儲完整的數(shù)據(jù),和
- 缺點:
- 存在一定的誤識別率:布隆過濾器存在誤判的情況,即當(dāng)一個元素實際上不在集合中時,有可能被判斷為在集合中。這是因為多個元素可能通過哈希函數(shù)映射到相同的位置,導(dǎo)致誤判。但是,當(dāng)布隆過濾器判斷一個元素不在集合中時,則是 100% 正確的。
- 刪除元素比較困難:一般情況下,不能直接從布隆過濾器中刪除元素。這是因為一個位置可能被多個元素映射到,如果直接將該位置的值置為 0,可能會影響其他元素的判斷。
1.8.2.4 應(yīng)用場景
布隆過濾器存在一定的誤判,所以使用它的場景就一定要允許不準確的情況發(fā)生:
- 解決
Redis
緩存穿透問題:秒殺商品詳情通常會被緩存到 Redis 中。如果有大量惡意請求查詢不存在的商品,通過布隆過濾器可以快速判斷這些商品不存在,從而避免了對數(shù)據(jù)庫的查詢,減輕了數(shù)據(jù)庫的壓力。 - 郵箱黑名單過濾:在郵件系統(tǒng)中,可以使用布隆過濾器來過濾垃圾郵件和惡意郵件。將已知的垃圾郵件發(fā)送者的地址或特征存儲在布隆過濾器中,新郵件來時判斷發(fā)送者是否在黑名單中。
- 對爬蟲網(wǎng)址進行過濾:在爬蟲程序中,為了避免重復(fù)抓取相同的網(wǎng)址,可以使用布隆過濾器來記錄已經(jīng)抓取過的網(wǎng)址。新網(wǎng)址出現(xiàn)時,先判斷是否已抓取過。
1.9 HyperLogLog基數(shù)統(tǒng)計
1.9.1 簡介
Redis 2.8.9
版本更新了 Hyperloglog
數(shù)據(jù)結(jié)構(gòu),Redis HyperLogLog
是用來做基數(shù)統(tǒng)計的算法,所謂基數(shù),也就是不重復(fù)的元素
- 優(yōu)點:
在輸入元素的數(shù)量或者體積非常大時,計算基數(shù)所需的空間總是固定的、并且是很小的。在Redis
里面,每個HyperLogLog
鍵只需要花費12 KB
內(nèi)存,就可以計算接近2^64
個不同元素的基數(shù)。 - 缺點:
因為HyperLogLog
只會根據(jù)輸入元素來計算基數(shù),而不會儲存輸入元素本身,所以HyperLogLog
不能像集合那樣,返回輸入的各個元素。
估算的值,可能存在誤差,帶有 0.81% 標準錯誤的近似值
1.9.2 命令和場景
這個數(shù)據(jù)結(jié)構(gòu)的命令有三個:
PFADD
:添加指定元素到HyperLogLog
PFCOUNT
:返回給定HyperLogLog
的基數(shù)估算值PFMERGE
:將多個HyperLogLog
合并為一個HyperLogLog
應(yīng)用場景:
- 網(wǎng)頁統(tǒng)計UV (瀏覽用戶數(shù)量,同一天同一個ip多次訪問算一次訪問,目的是計數(shù),而不是保存用戶)
傳統(tǒng)的方式是使用set保存用戶的id,可以統(tǒng)計set中元素數(shù)量作為標準判斷。
但如果這種方式保存大量用戶id,會占用大量內(nèi)存,我們的目的是為了計數(shù),而不是去保存id。 - 注冊 IP 數(shù)、每日訪問 IP 數(shù)、頁面實時UV)、在線用戶數(shù)等
1.9.3 內(nèi)部編碼和原理
HyperLogLog
算法時一種非常巧妙的近似統(tǒng)計大量去重元素數(shù)量的算法,它內(nèi)部維護了16384
個桶來記錄各自桶的元素數(shù)量,當(dāng)一個元素過來,它會散列到其中一個桶。當(dāng)元素到來時,通過 hash
算法將這個元素分派到其中的一個小集合存儲,同樣的元素總是會散列到同樣的小集合。這樣總的計數(shù)就是所有小集合大小的總和。使用這種方式精確計數(shù)除了可以增加元素外,還可以減少元素
一個HyperLogLog
實際占用的空間大約是 12k
字節(jié)。但是在計數(shù)比較小的時候,大多數(shù)桶的計數(shù)值都是零。如果 12k
字節(jié)里面太多的字節(jié)都是零,那么這個空間是可以適當(dāng)節(jié)約一下的。Redis
在計數(shù)值比較小的情況下采用了稀疏存儲
,稀疏存儲
的空間占用遠遠小于 12k
字節(jié)。相對于稀疏存儲
的就是密集存儲
,密集存儲
會恒定占用 12k 字節(jié)。
內(nèi)部編碼HyperLogLog
整體的內(nèi)部結(jié)構(gòu)就是 HLL
對象頭 加上 16384
個桶的計數(shù)值位圖。它在 Redis
的內(nèi)部結(jié)構(gòu)表現(xiàn)就是一個字符串位圖。你可以把 HyperLogLog
對象當(dāng)成普通的字符串來進行處理。
1.10 GEO地理位置
1.10.1 簡介
Redis
的 Geo
在 Redis 3.2
版本就推出了,這個功能可以推算地理位置的信息: 兩地之間的距離, 方圓幾里的人,GEO使用的是國際通用坐標系WGS-84
1.10.2 命令和場景
命令:
help @geo 查看geo分組下所有的命令 help geoadd 用于查看單個具體命令
主要操作方法有:
geoadd
:添加地理位置的坐標
geoadd 語法格式:GEOADD key longitude latitude member [longitude latitude member ...]
geopos
:用于從給定的key
里返回所有指定名稱(member
)的位置(經(jīng)度和緯度),不存在的返回nil
geopos
語法格式:GEOPOS key member [member ...]
geodist
:用于返回兩個給定位置之間的距離
geodist 語法格式:GEODIST key member1 member2 [m|km|ft|mi]
member1 member2 為兩個地理位置
最后一個距離單位參數(shù)說明:m :米,默認單位;km :千米;mi :英里;ft :英尺georadius
:以給定的經(jīng)緯度為中心, 返回鍵包含的位置元素當(dāng)中, 與中心的距離不超過給定最大距離的所有位置元素
語法格式:GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
參數(shù)說明:m
:米,默認單位。km
:千米。mi
:英里。ft
:英尺。WITHDIST
: 在返回位置元素的同時, 將位置元素與中心之間的距離也一并返回。WITHCOORD
: 將位置元素的經(jīng)度和緯度也一并返回。WITHHASH
: 以 52 位有符號整數(shù)的形式, 返回位置元素經(jīng)過原始 geohash 編碼的有序集合分值。 這個選項主要用于底層應(yīng)用或者調(diào)試, 實際中的作用并不大。COUNT
: 限定返回的記錄數(shù)ASC
: 查找結(jié)果根據(jù)距離從近到遠排序。DESC
: 查找結(jié)果根據(jù)從遠到近排序。
georadiusbymember
: 和GEORADIUS
命令一樣, 都可以找出位于指定范圍內(nèi)的元素, 但是georadiusbymember
的中心點是由給定的位置元素
決定的, 而不是使用經(jīng)度和緯度
來決定中心點
語法格式:GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
參數(shù)說明同GEORADIUS
geohash
:返回一個或多個位置對象的geohash
值。Redis GEO
使用geohash
來保存地理位置的坐標。geohash
用于獲取一個或多個位置元素的geohash
值。geohash
語法格式如下:GEOHASH key member [member ...]
應(yīng)用場景:
用于存儲地理信息以及對地理信息作操作的場景
- 查看附近的人
- 微信位置共享
- 地圖上直線距離的展示
- 比如檢索附近的主播
1.10.3 內(nèi)部編碼
需要說明的是,Geo
本身不是一種數(shù)據(jù)結(jié)構(gòu),它本質(zhì)上還是借助于Sorted Set(ZSET)
,并且使用GeoHash
技術(shù)進行填充。Redis
中將經(jīng)緯度使用52位
的整數(shù)進行編碼,放進zset
中,score
就是GeoHash
的52位整數(shù)值。在使用Redis
進行Geo
查詢時,其內(nèi)部對應(yīng)的操作其實就是zset(skiplist)
的操作。
通過zset
的score
進行排序就可以得到坐標附近的其它元素,通過將score
還原成坐標值就可以得到元素的原始坐標。
總之,Redis
中處理這些地理位置坐標點的思想是:二維平面坐標點 --> 一維整數(shù)編碼值 --> zset(score為編碼值) --> zrangebyrank(獲取score相近的元素)、zrangebyscore --> 通過score
(整數(shù)編碼值)反解坐標點 --> 附近點的地理位置坐標
1.11 Stream流
1.11.1 簡介
Redis Stream
是 Redis 5.0
版本新增加的數(shù)據(jù)結(jié)構(gòu)。Redis Stream
主要用于消息隊列(MQ,Message Queue),Redis
本身是有一個 Redis
發(fā)布訂閱 (pub/sub
) 來實現(xiàn)消息隊列的功能,但它有個缺點就是消息無法持久化,如果出現(xiàn)網(wǎng)絡(luò)斷開、Redis 宕機等,消息就會被丟棄。
點擊此處了解為什么Redis不適合用作MQ簡單來說發(fā)布訂閱 (pub/sub) 可以分發(fā)消息,但無法記錄歷史消息。
Redis Stream
提供了消息的持久化
和主備復(fù)制
功能,可以讓任何客戶端訪問任何時刻的數(shù)據(jù),并且能記住每一個客戶端的訪問位置,還能保證消息不丟失。
用一句話概括Stream
就是Redis
實現(xiàn)的內(nèi)存版kafka
,支持多播的可持久化的消息隊列,用于實現(xiàn)發(fā)布訂閱功能,借鑒了 kafka
的設(shè)計。Redis Stream
的結(jié)構(gòu)有一個消息鏈表,將所有加入的消息都串起來,每個消息都有一個唯一的ID和對應(yīng)的內(nèi)容。消息是持久化的,Redis
重啟后,內(nèi)容還在。
1.11.2 命令
Redis Stream
的結(jié)構(gòu)如下所示,它有一個消息鏈表,將所有加入的消息都串起來,每個消息都有一個唯一的 ID 和對應(yīng)的內(nèi)容
每個 Stream
都有唯一的名稱,它就是 Redis
的 key
,在我們首次使用 xadd
指令追加消息時自動創(chuàng)建。
上圖解析:
Consumer Group
:消費組,使用XGROUP CREATE
命令創(chuàng)建,一個消費組有多個消費者(Consumer
)。last_delivered_id
:游標,每個消費組會有個游標last_delivered_id
,任意一個消費者讀取了消息都會使游標last_delivered_id
往前移動。pending_ids
:消費者(Consumer
)的狀態(tài)變量,作用是維護消費者的未確認的 id。pending_ids
記錄了當(dāng)前已經(jīng)被客戶端讀取的消息,但是還沒有ack
(Acknowledge character
:確認字符)。
消息隊列相關(guān)命令:
XADD
:添加消息到末尾
使用XADD
向隊列添加消息,如果指定的隊列不存在,則創(chuàng)建一個隊列
語法格式:XADD key ID field value [field value ...]
key
:隊列名稱,如果不存在就創(chuàng)建ID
:消息 id,我們使用*
表示由 redis 生成,可以自定義,但是要自己保證遞增性。field value
: 記錄
XTRIM
:對流進行修剪,限制長度
語法格式:XTRIM key MAXLEN [~] count
key
:隊列名稱MAXLEN
:長度count
:數(shù)量
XDEL
:刪除消息
語法格式:XDEL key ID [ID ...]
key
:隊列名稱ID
:消息 ID
XLEN
:獲取流包含的元素數(shù)量,即消息長度
語法格式:XLEN key
,key
:隊列名稱XRANGE
:獲取消息列表,會自動過濾已經(jīng)刪除的消息
語法格式:XRANGE key start end [COUNT count]
key
:隊列名start
:開始值,-
表示最小值end
:結(jié)束值,+
表示最大值count
:數(shù)量
XREVRANGE
:反向獲取消息列表,ID 從大到小
語法格式:XREVRANGE key end start [COUNT count]
key
:隊列名end
:結(jié)束值, + 表示最大值start
:開始值, - 表示最小值count
:數(shù)量
XREAD
:以阻塞或非阻塞方式獲取消息列表
語法格式:XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
count
:數(shù)量milliseconds
:可選,阻塞毫秒數(shù),沒有設(shè)置就是非阻塞模式key
:隊列名id
:消息 ID
消費者組相關(guān)命令:
XGROUP CREATE
:創(chuàng)建消費者組
語法格式:XGROUP [CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]
key
:隊列名稱,如果不存在就創(chuàng)建groupname
:組名。$
: 表示從尾部開始消費,只接受新消息,當(dāng)前 Stream 消息會全部忽略。- 從頭開始消費:
XGROUP CREATE mystream consumer-group-name 0-0
- 從尾部開始消費:
XGROUP CREATE mystream consumer-group-name $
XREADGROUP GROUP
:讀取消費者組中的消息
語法格式:XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]
group
:消費組名consumer
:消費者名count
: 讀取數(shù)量milliseconds
: 阻塞毫秒數(shù)key
: 隊列名ID
: 消息 ID
XACK
:將消息標記為"已處理"XGROUP SETID
:為消費者組設(shè)置新的最后遞送消息IDXGROUP DELCONSUMER
:刪除消費者XGROUP DESTROY
:刪除消費者組XPENDING
:顯示待處理消息的相關(guān)信息XCLAIM
:轉(zhuǎn)移消息的歸屬權(quán)XINFO
:查看流和消費者組的相關(guān)信息;XINFO GROUPS
:打印消費者組的信息;XINFO STREAM
:打印流信息
1.11.3 內(nèi)部編碼
stream
底層的數(shù)據(jù)結(jié)構(gòu)是radix tree
:Radix Tree
(基數(shù)樹) 事實上就是幾乎相同是傳統(tǒng)的二叉樹。僅僅是在尋找方式上,以一個unsigned int類型數(shù)為例,利用這個數(shù)的每個比特位作為樹節(jié)點的推斷。能夠這樣說,比方一個數(shù)10001010101010110101010,那么依照Radix
樹的插入就是在根節(jié)點,假設(shè)遇到0,就指向左節(jié)點,假設(shè)遇到1就指向右節(jié)點,在插入過程中構(gòu)造樹節(jié)點,在刪除過程中刪除樹節(jié)點。
如下是一個保存了7個單詞的Radix Tree:
127.0.0.1:6379> xadd mystream * key1 128 "1576480551233-0" 127.0.0.1:6379> object encoding mystream "unknown"
mystream 總共由 3 部分構(gòu)成:
- 第一部分是
robj
, 每個redis
對象實例都會有一個最基本的結(jié)構(gòu)來存儲它實際的類型, 編碼和對應(yīng)的結(jié)構(gòu)的位置 - 第二部分是一個
rax
, 用作存儲 stream ID - 第三部分是
listpack
,rax
下的每一個 key 節(jié)點都會把對應(yīng)的 keys 和 values 的值存在這個 listpack 結(jié)構(gòu)中
總結(jié)
到此這篇關(guān)于Redis數(shù)據(jù)類型的文章就介紹到這了,更多相關(guān)Redis數(shù)據(jù)類型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
面試常問:如何保證Redis緩存和數(shù)據(jù)庫的數(shù)據(jù)一致性
在實際開發(fā)過程中,緩存的使用頻率是非常高的,只要使用緩存和數(shù)據(jù)庫存儲,就難免會出現(xiàn)雙寫時數(shù)據(jù)一致性的問題,那我們又該如何解決呢2021-09-09Redis字典實現(xiàn)、Hash鍵沖突及漸進式rehash詳解
這篇文章主要介紹了Redis字典實現(xiàn)、Hash鍵沖突以及漸進式rehash的相關(guān)知識,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09詳解Redis數(shù)據(jù)類型實現(xiàn)原理
這篇文章主要介紹了Redis數(shù)據(jù)類型實現(xiàn)原理,在工作中或?qū)W習(xí)中有需要的小伙伴可以參考一下這篇文章2021-08-08