redis 的數(shù)據(jù)類型可謂是 Redis 的精華所在,同樣的數(shù)據(jù)類型,例如字符串存儲不同的值對應(yīng)的實際存儲結(jié)構(gòu)也是不同,當你存儲的 int 值是實際的存儲結(jié)構(gòu)也是 int,如果是短字符串(小于 44 字節(jié))實際存儲的結(jié)構(gòu)為 embstr,長字符串對應(yīng)的實際存儲結(jié)構(gòu)是 raw,這樣設(shè)計的目的是為了更好的節(jié)約內(nèi)存。
我們本文的面試題是 Redis 有哪些數(shù)據(jù)類型?
典型回答
Redis 最常用的數(shù)據(jù)類型有 5 種:String(字符串類型)、Hash(字典類型)、List(列表類型)、Set(集合類型)、ZSet(有序集合類型)。
1.字符串類型
字符串類型(Simple Dynamic Strings 簡稱 SDS),譯為:簡單動態(tài)字符串,它是以鍵值對 key-value 的形式進行存儲的,根據(jù) key 來存儲和獲取 value 值,它的使用相對來說比較簡單,但在實際項目中應(yīng)用非常廣泛。
字符串的使用如下:
127.0.0.1:6379> set k1 v1 # 添加數(shù)據(jù)
OK
127.0.0.1:6379> get k1 # 查詢數(shù)據(jù)
"v1"
127.0.0.1:6379> strlen k1 # 查詢字符串的長度
(integer) 5
復(fù)制
我們也可以在存儲字符串時設(shè)置鍵值的過期時間,如下代碼所示:
127.0.0.1:6379> set k1 v1 ex 1000 # 設(shè)置 k1 1000s 后過期(刪除)
OK
復(fù)制
我們還可以使用 SDS 來存儲 int 類型的值,并且可以使用 incr 指令和 decr 指令來操作存儲的值 +1 或者 -1,具體實現(xiàn)代碼如下:
127.0.0.1:6379> get k1 # 查詢 k1=3
"3"
127.0.0.1:6379> incr k1 # 執(zhí)行 +1 操作
(integer) 4
127.0.0.1:6379> get k1 # 查詢 k1=4
"4"
127.0.0.1:6379> decr k1 # 執(zhí)行 -1 操作
(integer) 3
127.0.0.1:6379> get k1 # 查詢 k1=3
"3"
復(fù)制
字符串的常見使用場景:
- 存放用戶(登錄)信息;
- 存放文章詳情和列表信息;
- 存放和累計網(wǎng)頁的統(tǒng)計信息(存儲 int 值)。
……
2.字典類型
字典類型 (Hash) 又被稱為散列類型或者是哈希表類型,它是將一個鍵值 (key) 和一個特殊的“哈希表”關(guān)聯(lián)起來,這個“哈希表”表包含兩列數(shù)據(jù):字段和值。例如我們使用字典類型來存儲一篇文章的詳情信息,存儲結(jié)構(gòu)如下圖所示:

同理我們也可以使用字典類型來存儲用戶信息,并且使用字典類型來存儲此類信息就無需手動序列化和反序列化數(shù)據(jù)了,所以使用起來更加的方便和高效。
字典類型的使用如下:
127.0.0.1:6379> hset myhash key1 value1 # 添加數(shù)據(jù)
(integer) 1
127.0.0.1:6379> hget myhash key1 # 查詢數(shù)據(jù)
"value1"
復(fù)制
字典類型的數(shù)據(jù)結(jié)構(gòu),如下圖所示:

通常情況下字典類型會使用數(shù)組的方式來存儲相關(guān)的數(shù)據(jù),但發(fā)生哈希沖突時才會使用鏈表的結(jié)構(gòu)來存儲數(shù)據(jù)。
3.列表類型
列表類型 (List) 是一個使用鏈表結(jié)構(gòu)存儲的有序結(jié)構(gòu),它的元素插入會按照先后順序存儲到鏈表結(jié)構(gòu)中,因此它的元素操作 (插入和刪除) 時間復(fù)雜度為 O(1),所以相對來說速度還是比較快的,但它的查詢時間復(fù)雜度為 O(n),因此查詢可能會比較慢。
列表類型的使用如下:
127.0.0.1:6379> lpush list 1 2 3 # 添加數(shù)據(jù)
(integer) 3
127.0.0.1:6379> lpop list # 獲取并刪除列表的第一個元素
1
復(fù)制
列表的典型使用場景有以下兩個:
- 消息隊列:列表類型可以使用 rpush 實現(xiàn)先進先出的功能,同時又可以使用 lpop 輕松的彈出(查詢并刪除)第一個元素,所以列表類型可以用來實現(xiàn)消息隊列;
- 文章列表:對于博客站點來說,當用戶和文章都越來越多時,為了加快程序的響應(yīng)速度,我們可以把用戶自己的文章存入到 List 中,因為 List 是有序的結(jié)構(gòu),所以這樣又可以完美的實現(xiàn)分頁功能,從而加速了程序的響應(yīng)速度。
4.集合類型
集合類型 (Set) 是一個無序并唯一的鍵值集合。
集合類型的使用如下:
127.0.0.1:6379> sadd myset v1 v2 v3 # 添加數(shù)據(jù)
(integer) 3
127.0.0.1:6379> smembers myset # 查詢集合中的所有數(shù)據(jù)
1) "v1"
2) "v3"
3) "v2"
復(fù)制
集合類型的經(jīng)典使用場景如下:
- 微博關(guān)注我的人和我關(guān)注的人都適合用集合存儲,可以保證人員不會重復(fù);
- 中獎人信息也適合用集合類型存儲,這樣可以保證一個人不會重復(fù)中獎。
集合類型(Set)和列表類型(List)的區(qū)別如下:
- 列表可以存儲重復(fù)元素,集合只能存儲非重復(fù)元素;
- 列表是按照元素的先后順序存儲元素的,而集合則是無序方式存儲元素的。
5.有序集合類型
有序集合類型 (Sorted Set) 相比于集合類型多了一個排序?qū)傩?score(分值),對于有序集合 ZSet 來說,每個存儲元素相當于有兩個值組成的,一個是有序結(jié)合的元素值,一個是排序值。有序集合的存儲元素值也是不能重復(fù)的,但分值是可以重復(fù)的。
當我們把學(xué)生的成績存儲在有序集合中時,它的存儲結(jié)構(gòu)如下圖所示:

有序集合類型的使用如下:
127.0.0.1:6379> zadd zset1 3 golang 4 sql 1 redis # 添加數(shù)據(jù)
(integer) 3
127.0.0.1:6379> zrange zset 0 -1 # 查詢所有數(shù)據(jù)
1) "redis"
2) "MySQL"
3) "JAVA"
復(fù)制
有序集合的經(jīng)典使用場景如下:
- 學(xué)生成績排名;
- 粉絲列表,根據(jù)關(guān)注的先后時間排序。
考點分析
關(guān)于 Redis 數(shù)據(jù)類型的這個問題,對于大多數(shù)人既熟悉又陌生,熟悉的是每天都在使用 Redis 存取數(shù)據(jù),陌生的是對于 Redis 的數(shù)據(jù)類型知之甚少,因為對于普通的開發(fā)工作使用字符串類型就可以搞定了。但是善用 Redis 的數(shù)據(jù)類型可以到達意想不到的效果,不但可以提高程序的運行速度又可以減少業(yè)務(wù)代碼,可謂一舉兩得。
例如我們經(jīng)常會把用戶的登錄信息存儲在 Redis 中,但通常的做法是先將用戶登錄實體類轉(zhuǎn)為 JSON 字符串存儲在 Redis 中,然后讀取時先查詢數(shù)據(jù)再反序列化為 User 對象,這個過程看似沒什么問題,但我們可以有更優(yōu)的解決方案來處理此問題,比如我們可以使用 Hash 存儲用戶的信息,這樣就無需序列化的過程了,并且讀取之后無需反序列化,直接使用 Map 來接收就可以了,這樣既提高了程序的運行速度有省去了序列化和反序列化的業(yè)務(wù)代碼。
與此知識點相關(guān)的面試題還有以下幾個:
- 有序列表的實際存儲結(jié)構(gòu)是什么?
- 除了五種基本的數(shù)據(jù)類型之外,還有什么數(shù)據(jù)類型?
知識擴展
有序列表的內(nèi)部實現(xiàn)
有序集合是由 ziplist (壓縮列表) 或 skiplist (跳躍表) 組成的。
ziplist 介紹
當數(shù)據(jù)比較少時,有序集合使用的是 ziplist 存儲的,如下代碼所示:
127.0.0.1:6379> zadd myzset 1 db 2 redis 3 mysql
(integer) 3
127.0.0.1:6379> object encoding myzset
"ziplist"
復(fù)制
從結(jié)果可以看出,有序集合把 myset 鍵值對存儲在 ziplist 結(jié)構(gòu)中了。 有序集合使用 ziplist 格式存儲必須滿足以下兩個條件:
- 有序集合保存的元素個數(shù)要小于 128 個;
- 有序集合保存的所有元素成員的長度都必須小于 64 字節(jié)。
如果不能滿足以上兩個條件中的任意一個,有序集合將會使用 skiplist 結(jié)構(gòu)進行存儲。 接下來我們來測試以下,當有序集合中某個元素長度大于 64 字節(jié)時會發(fā)生什么情況? 代碼如下:
127.0.0.1:6379> zadd zmaxleng 1.0 redis
(integer) 1
127.0.0.1:6379> object encoding zmaxleng
"ziplist"
127.0.0.1:6379> zadd zmaxleng 2.0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
(integer) 1
127.0.0.1:6379> object encoding zmaxleng
"skiplist"
復(fù)制
通過以上代碼可以看出,當有序集合保存的所有元素成員的長度大于 64 字節(jié)時,有序集合就會從 ziplist 轉(zhuǎn)換成為 skiplist。
小貼士:可以通過配置文件中的 zset-max-ziplist-entries(默認 128)和 zset-max-ziplist-value(默認 64)來設(shè)置有序集合使用 ziplist 存儲的臨界值。
skiplist 介紹
skiplist 數(shù)據(jù)編碼底層是使用 zset 結(jié)構(gòu)實現(xiàn)的,而 zset 結(jié)構(gòu)中包含了一個字典和一個跳躍表,源碼如下:
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;
復(fù)制
跳躍表的結(jié)構(gòu)如下圖所示:

根據(jù)以上圖片展示,當我們在跳躍表中查詢值 32 時,執(zhí)行流程如下:
- 從最上層開始找,1 比 32 小,在當前層移動到下一個節(jié)點進行比較;
- 7 比 32 小,當前層移動下一個節(jié)點比較,由于下一個節(jié)點指向 Null,所以以 7 為目標,移動到下一層繼續(xù)向后比較;
- 18 小于 32,繼續(xù)向后移動查找,對比 77 大于 32,以 18 為目標,移動到下一層繼續(xù)向后比較;
- 對比 32 等于 32,值被順利找到。
從上面的流程可以看出,跳躍表會想從最上層開始找起,依次向后查找,如果本層的節(jié)點大于要找的值,或者本層的節(jié)點為 Null 時,以上一個節(jié)點為目標,往下移一層繼續(xù)向后查找并循環(huán)此流程,直到找到該節(jié)點并返回,如果對比到最后一個元素仍未找到,則返回 Null。
高級數(shù)據(jù)類型
除了有 5 大基本數(shù)據(jù)類型外,還有 GEO(地理位置類型)、HyperLogLog(統(tǒng)計類型)、Stream(流類型)。
GEO(地理位置類型)是 Redis 3.2 版本中新增的數(shù)據(jù)類型,用于存儲和查詢地理位置的,使用它我們可以實現(xiàn)查詢附近的人或查詢附近的商家等功能(這部分的內(nèi)容會在后面的章節(jié)單獨講解)。
Stream(流類型)是 Redis 5.0 版本中新增的數(shù)據(jù)類型,因為使用 Stream 可以實現(xiàn)消息消費確認的功能,使用“xack key group-key ID”命令,所以此類型的出現(xiàn)給 Redis 更好的實現(xiàn)消息隊列提供了很大的幫助。
HyperLogLog(統(tǒng)計類型)是本文介紹的重點,HyperLogLog (下文簡稱為 HLL) 是 Redis 2.8.9 版本添加的數(shù)據(jù)結(jié)構(gòu),它用于高性能的基數(shù) (去重) 統(tǒng)計功能,它的缺點就是存在極低的誤差率。
HLL 具有以下幾個特點:
- 能夠使用極少的內(nèi)存來統(tǒng)計巨量的數(shù)據(jù),它只需要 12K 空間就能統(tǒng)計 2^64 的數(shù)據(jù);
- 統(tǒng)計存在一定的誤差,誤差率整體較低,標準誤差為 0.81%;
- 誤差可以被設(shè)置輔助計算因子進行降低。
HLL 的命令只有 3 個,但都非常的實用,下面分別來看。
1.添加元素
127.0.0.1:6379> pfadd key "redis"
(integer) 1
127.0.0.1:6379> pfadd key "java" "sql"
(integer) 1
復(fù)制
相關(guān)語法: pfadd key element [element ...] 此命令支持添加一個或多個元素至 HLL 結(jié)構(gòu)中。
2.統(tǒng)計不重復(fù)的元素
127.0.0.1:6379> pfadd key "redis"
(integer) 1
127.0.0.1:6379> pfadd key "sql"
(integer) 1
127.0.0.1:6379> pfadd key "redis"
(integer) 0
127.0.0.1:6379> pfcount key
(integer) 2
復(fù)制
從 pfcount 的結(jié)果可以看出,在 HLL 結(jié)構(gòu)中鍵值為 key 的元素, 有 2 個不重復(fù)的值:redis 和 sql,可以看出結(jié)果還是挺準的。 相關(guān)語法: pfcount key [key ...]
此命令支持統(tǒng)計一個或多個 HLL 結(jié)構(gòu)。
3.合并一個或多個 HLL 至新結(jié)構(gòu)
新增 k 和 k2 合并至新結(jié)構(gòu) k3 中,代碼如下:
127.0.0.1:6379> pfadd k "java" "sql"
(integer) 1
127.0.0.1:6379> pfadd k2 "redis" "sql"
(integer) 1
127.0.0.1:6379> pfmerge k3 k k2
OK
127.0.0.1:6379> pfcount k3
(integer) 3
復(fù)制
相關(guān)語法:pfmerge destkey sourcekey [sourcekey ...] ** pfmerge 使用場景:當我們需要合并兩個或多個同類頁面的訪問數(shù)據(jù)時,我們可以使用 pfmerge 來操作。
總結(jié)
本文我們介紹了 Redis 的 5 大基礎(chǔ)數(shù)據(jù)類型的概念以及簡單的使用:String(字符串類型)、Hash(字典類型)、List(列表類型)、Set(集合類型)、ZSet(有序集合類型),還深入的介紹了 ZSet 的底層數(shù)據(jù)存儲結(jié)構(gòu):ziplist (壓縮列表) 或 skiplist (跳躍表)。除此之外我們還介紹了 Redis 中的提前 3 個高級的數(shù)據(jù)類型:GEO(地理位置類型)用于實現(xiàn)查詢附近的人、HyperLogLog(統(tǒng)計類型)用于高效的實現(xiàn)數(shù)據(jù)的去重統(tǒng)計(存在一定的誤差)、Stream(流類型)主要應(yīng)用于消息隊列的實現(xiàn)。