Redis中Set結(jié)構(gòu)使用過程與原理說明
開篇:從購物車到Redis Set
想象一下你在網(wǎng)上購物時(shí),把商品加入購物車的場景。當(dāng)你點(diǎn)擊"加入購物車"按鈕時(shí),系統(tǒng)需要確保同一件商品不會被重復(fù)添加,同時(shí)又能快速判斷某件商品是否已經(jīng)在購物車中。這種場景下,Redis的Set數(shù)據(jù)結(jié)構(gòu)就像是一個(gè)完美的購物車容器。
Redis中的Set是一個(gè)無序的、不重復(fù)的字符串集合,它提供了高效的添加、刪除和查找操作。就像購物車能自動去重一樣,Set結(jié)構(gòu)天然支持去重功能。在實(shí)際應(yīng)用中,Set常被用于存儲用戶標(biāo)簽、好友關(guān)系、投票系統(tǒng)等場景。今天,我們就來深入探討Redis Set的使用方法和內(nèi)部實(shí)現(xiàn)原理。
一、Redis Set的基本操作
理解了Set的應(yīng)用場景后,我們來看看Redis Set提供的基本操作。這些操作就像購物車的各種功能按鈕,讓我們能夠方便地管理集合中的元素。
1.1 常用命令
// 添加元素到集合 SADD myset "item1" "item2" "item3" // 獲取集合中的所有元素 SMEMBERS myset // 判斷元素是否在集合中 SISMEMBER myset "item1" // 獲取集合元素?cái)?shù)量 SCARD myset // 隨機(jī)移除并返回一個(gè)元素 SPOP myset // 隨機(jī)返回一個(gè)元素但不移除 SRANDMEMBER myset
上述代碼展示了Redis Set的基本操作命令。SADD用于添加元素,SMEMBERS查看所有元素,SISMEMBER檢查元素是否存在,SCARD獲取元素?cái)?shù)量,SPOP和SRANDMEMBER用于隨機(jī)操作元素。

以上流程圖說明了Redis Set的基本操作流程。從添加元素開始,到查看、檢查、統(tǒng)計(jì)和隨機(jī)操作,形成了一個(gè)完整的數(shù)據(jù)操作閉環(huán)。
1.2 集合運(yùn)算
Redis Set還支持豐富的集合運(yùn)算,這些運(yùn)算在實(shí)際開發(fā)中非常有用:
// 求兩個(gè)集合的差集 SDIFF set1 set2 // 求兩個(gè)集合的交集 SINTER set1 set2 // 求兩個(gè)集合的并集 SUNION set1 set2 // 將差集/交集/并集結(jié)果存儲到新集合 SDIFFSTORE newset set1 set2 SINTERSTORE newset set1 set2 SUNIONSTORE newset set1 set2
這些集合運(yùn)算 命令可以用于各種數(shù)據(jù)分析場景,比如找出兩個(gè)用戶群的共同好友(SINTER),或者找出A用戶有但B用戶沒有的好友(SDIFF)。

這個(gè)圖展示了Redis Set的三種基本集合運(yùn)算:差集、交集和并集。通過不同的運(yùn)算,我們可以從原始集合中提取出有價(jià)值的信息。
二、Redis Set的內(nèi)部實(shí)現(xiàn)
了解了基本操作后,我們來看看Redis Set的內(nèi)部實(shí)現(xiàn)原理。就像了解購物車的構(gòu)造能幫助我們更好地使用它一樣,理解Set的內(nèi)部實(shí)現(xiàn)能讓我們更高效地使用Redis。
2.1 數(shù)據(jù)結(jié)構(gòu)選擇
Redis Set的底層實(shí)現(xiàn)有兩種數(shù)據(jù)結(jié)構(gòu),根據(jù)元素?cái)?shù)量和元素大小自動選擇:
- intset(整數(shù)集合):當(dāng)集合中所有元素都是整數(shù)且元素?cái)?shù)量較少時(shí)使用
- hashtable(哈希表):當(dāng)集合包含非整數(shù)元素或元素?cái)?shù)量較多時(shí)使用
這種智能選擇的設(shè)計(jì)就像我們根據(jù)購物物品的多少選擇不同大小的購物車一樣,既節(jié)省空間又保證效率。

這個(gè)狀態(tài)圖展示了Redis選擇Set底層數(shù)據(jù)結(jié)構(gòu)的過程。首先檢查元素類型和數(shù)量,然后決定使用intset還是hashtable。
2.2 intset實(shí)現(xiàn)原理詳解
intset是Redis為整數(shù)集合優(yōu)化設(shè)計(jì)的一種緊湊數(shù)據(jù)結(jié)構(gòu),它的核心特點(diǎn)包括:
2.2.1 內(nèi)存布局
intset的內(nèi)存布局非常緊湊,由三部分組成:
struct intset {
uint32_t encoding; // 編碼方式:INTSET_ENC_INT16/32/64
uint32_t length; // 元素?cái)?shù)量
int8_t contents[]; // 實(shí)際存儲數(shù)組
};
這個(gè)結(jié)構(gòu)體展示了intset的內(nèi)存布局。encoding表示元素使用的位數(shù)(16/32/64),length是元素?cái)?shù)量,contents是柔性數(shù)組,實(shí)際存儲元素?cái)?shù)據(jù)。
2.2.2 編碼升級機(jī)制
intset有一個(gè)獨(dú)特的特性:當(dāng)插入的元素超過當(dāng)前編碼范圍時(shí),會自動升級編碼:
- 初始創(chuàng)建時(shí)默認(rèn)使用16位(INTSET_ENC_INT16)編碼
- 當(dāng)插入32位整數(shù)時(shí),升級為32位編碼(INTSET_ENC_INT32)
- 當(dāng)插入64位整數(shù)時(shí),升級為64位編碼(INTSET_ENC_INT64)
升級過程需要重新分配內(nèi)存并轉(zhuǎn)換所有現(xiàn)有元素,這是一個(gè)O(N)操作。

這個(gè)序列圖展示了intset編碼升級的過程。當(dāng)插入超過當(dāng)前編碼范圍的數(shù)值時(shí),Redis會自動升級編碼并轉(zhuǎn)換所有現(xiàn)有元素。
2.2.3 查找與插入
intset使用二分查找來定位元素,保證O(logN)的查找復(fù)雜度:
// 偽代碼展示intset查找過程
int search(intset *is, int64_t value) {
int low = 0, high = is->length-1;
while(low <= high) {
int mid = (low + high)/2;
int64_t midval = _intsetGet(is, mid);
if (value < midval) high = mid - 1;
else if (value > midval) low = mid + 1;
else return mid; // 找到
}
return -1; // 未找到
}
這段偽代碼展示了intset的二分查找實(shí)現(xiàn)。由于元素是有序存儲的,可以使用二分查找快速定位元素位置。
2.3 hashtable實(shí)現(xiàn)原理詳解
當(dāng)Set使用hashtable實(shí)現(xiàn)時(shí),實(shí)際上與Redis的Hash類型使用相同的字典結(jié)構(gòu),只是value被設(shè)置為NULL。讓我們深入分析其實(shí)現(xiàn)細(xì)節(jié):
2.3.1 字典結(jié)構(gòu)
Redis字典的核心結(jié)構(gòu)如下:
typedef struct dict {
dictType *type; // 類型特定函數(shù)
void *privdata; // 私有數(shù)據(jù)
dictht ht[2]; // 哈希表(兩個(gè)用于rehash)
long rehashidx; // rehash進(jìn)度,-1表示未進(jìn)行
unsigned long iterators; // 正在運(yùn)行的迭代器數(shù)量
} dict;
typedef struct dictht {
dictEntry **table; // 哈希表數(shù)組
unsigned long size; // 表大小
unsigned long sizemask; // 大小掩碼,用于計(jì)算索引值
unsigned long used; // 已使用節(jié)點(diǎn)數(shù)量
} dictht;
typedef struct dictEntry {
void *key; // 鍵
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v; // 值(Set中為NULL)
struct dictEntry *next; // 指向下個(gè)哈希表節(jié)點(diǎn),形成鏈表
} dictEntry;
這些結(jié)構(gòu)體定義了Redis字典的核心實(shí)現(xiàn)。dict是頂層結(jié)構(gòu),包含兩個(gè)dictht用于漸進(jìn)式rehash,dictEntry是實(shí)際的鍵值對節(jié)點(diǎn)。
2.3.2 哈希算法與沖突解決
Redis使用MurmurHash2算法計(jì)算鍵的哈希值,然后通過取模確定索引位置:
// 計(jì)算鍵的哈希值 hash = dict->type->hashFunction(key); // 計(jì)算索引位置 index = hash & dict->ht[0].sizemask;
當(dāng)發(fā)生哈希沖突時(shí),Redis使用鏈地址法解決沖突,即在同一個(gè)索引位置形成鏈表。

這個(gè)圖展示了Redis哈希表的鏈?zhǔn)經(jīng)_突解決方法。相同索引位置的元素通過鏈表連接起來。
2.3.3 漸進(jìn)式rehash
當(dāng)哈希表需要擴(kuò)容時(shí),Redis使用漸進(jìn)式rehash策略:
- 為ht[1]分配更大的空間(通常是原大小的2倍)
- 設(shè)置rehashidx=0,開始rehash
- 每次對字典執(zhí)行操作時(shí),順帶將ht[0]中的一個(gè)索引上的所有鍵值對rehash到ht[1]
- 當(dāng)所有鍵值對都遷移完成后,釋放ht[0],將ht[1]設(shè)置為ht[0]
這種策略避免了集中式rehash帶來的性能問題。

這個(gè)用戶旅程圖展示了漸進(jìn)式rehash的完整過程。從初始化到逐步遷移,最后完成整個(gè)rehash操作。
三、Set的應(yīng)用場景與最佳實(shí)踐
掌握了Set的基本操作和實(shí)現(xiàn)原理后,我們來看看它在實(shí)際開發(fā)中的應(yīng)用場景和使用技巧。
3.1 典型應(yīng)用場景
Redis Set在實(shí)際項(xiàng)目中有許多經(jīng)典應(yīng)用:
- 用戶標(biāo)簽系統(tǒng):每個(gè)用戶的標(biāo)簽存儲為一個(gè)Set
- 社交關(guān)系:用戶的好友、關(guān)注列表可以用Set存儲
- 抽獎系統(tǒng):使用SPOP實(shí)現(xiàn)隨機(jī)抽獎
- 共同好友/興趣:使用SINTER計(jì)算用戶間的共同點(diǎn)
- 黑白名單:使用Set實(shí)現(xiàn)高效的存在性檢查

這個(gè)思維導(dǎo)圖總結(jié)了Redis Set的主要應(yīng)用場景。從用戶標(biāo)簽到社交關(guān)系,從抽獎系統(tǒng)到黑白名單,Set都能發(fā)揮重要作用。
3.2 性能優(yōu)化建議
為了充分發(fā)揮Redis Set的性能,我有以下建議:
- 對于小型集合(元素少且都是整數(shù)),盡量保持使用intset
- 大型集合操作(SINTER/SUNION等)可能會阻塞Redis,考慮在從節(jié)點(diǎn)執(zhí)行
- 頻繁的SPOP操作可以考慮結(jié)合管道(pipeline)批量執(zhí)行
- 超大集合(百萬級以上)的SMEMBERS操作要謹(jǐn)慎,可能消耗大量內(nèi)存

這個(gè)用戶旅程圖展示了Redis Set性能優(yōu)化的關(guān)鍵點(diǎn)。從小集合處理到大集合操作,再到日常使用習(xí)慣,每個(gè)環(huán)節(jié)都有相應(yīng)的優(yōu)化策略。
四、總結(jié)
通過今天的討論,我們對Redis Set有了全面的認(rèn)識。讓我們總結(jié)一下本文的主要內(nèi)容:
- 基本操作:SADD/SMEMBERS/SISMEMBER等命令的使用
- 集合運(yùn)算:SDIFF/SINTER/SUNION等集合操作
- 內(nèi)部實(shí)現(xiàn):intset的內(nèi)存布局和編碼升級機(jī)制,hashtable的字典結(jié)構(gòu)和漸進(jìn)式rehash
- 應(yīng)用場景:標(biāo)簽系統(tǒng)、社交關(guān)系、抽獎等典型應(yīng)用
- 性能優(yōu)化:大小集合的不同處理策略和日常優(yōu)化建議
Redis Set是一個(gè)功能強(qiáng)大且高效的數(shù)據(jù)結(jié)構(gòu),正確使用它可以極大地簡化我們的開發(fā)工作。
希望這篇文章能幫助大家更好地理解和應(yīng)用Redis Set。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Redis Desktop Manager(Redis可視化工具)安裝及使用圖文教程
這篇文章主要介紹了Redis Desktop Manager(Redis可視化工具)安裝及使用圖文教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
Redis?SortedSet數(shù)據(jù)類型及其常用命令總結(jié)
Redis的SortedSet是一個(gè)可排序的set集合,與Java中的TreeSet有些類似,但底層數(shù)據(jù)結(jié)構(gòu)卻差別很大,這篇文章主要介紹了Redis?SortedSet數(shù)據(jù)類型及其常用命令詳解,需要的朋友可以參考下2024-06-06
Redis定時(shí)任務(wù)原理的實(shí)現(xiàn)
本文主要是基于?redis?6.2?源碼進(jìn)行分析定時(shí)事件的數(shù)據(jù)結(jié)構(gòu)和常見操作,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Redis系列之底層數(shù)據(jù)結(jié)構(gòu)SDS詳解
SDS(簡單動態(tài)字符串)是Redis使用的核心數(shù)據(jù)結(jié)構(gòu),用于替代C語言的字符串,以解決長度獲取慢、內(nèi)存溢出等問題,SDS通過預(yù)分配與惰性釋放策略優(yōu)化內(nèi)存使用,增強(qiáng)安全性,且能存儲文本與二進(jìn)制數(shù)據(jù),可查看源碼src/sds.h和src/sds.c了解更多2024-11-11

