Redis系列(一):深入了解Redis數(shù)據(jù)類型和底層數(shù)據(jù)結(jié)構(gòu)
Redis有以下幾種常用的數(shù)據(jù)類型:

redis數(shù)據(jù)是如何組織的
為了實(shí)現(xiàn)從鍵到值的快速訪問(wèn),Redis 使用了一個(gè)哈希表來(lái)保存所有鍵值對(duì)。
Redis全局哈希表(Global Hash Table)是指在Redis數(shù)據(jù)庫(kù)內(nèi)部用于存儲(chǔ)所有鍵值對(duì)的主要數(shù)據(jù)結(jié)構(gòu)。它的實(shí)現(xiàn)原理涉及到哈希表、字典、漸進(jìn)式rehash等技術(shù),以下是Redis全局哈希表的實(shí)現(xiàn)原理和查詢流程:
實(shí)現(xiàn)原理:
哈希表(Hash Table):
Redis的全局哈希表是由多個(gè)哈希表構(gòu)成的,每個(gè)哈希表稱為一個(gè)數(shù)據(jù)庫(kù)(DB)。數(shù)據(jù)庫(kù)的數(shù)量可以通過(guò)配置進(jìn)行設(shè)置,默認(rèn)是16個(gè)。每個(gè)數(shù)據(jù)庫(kù)都是一個(gè)獨(dú)立的哈希表,負(fù)責(zé)存儲(chǔ)鍵值對(duì)。
字典(Dictionary):
每個(gè)數(shù)據(jù)庫(kù)都使用字典(Dictionary)來(lái)實(shí)現(xiàn)鍵值對(duì)的存儲(chǔ)。字典是一種高效的鍵值對(duì)存儲(chǔ)結(jié)構(gòu),它使用哈希表來(lái)支持快速的查找、插入和刪除操作。
漸進(jìn)式rehash:
當(dāng)數(shù)據(jù)庫(kù)的鍵值對(duì)數(shù)量較多時(shí),為了保持查詢性能,Redis會(huì)在不中斷服務(wù)的情況下,逐步將舊的數(shù)據(jù)庫(kù)哈希表中的數(shù)據(jù)遷移到新的數(shù)據(jù)庫(kù)哈希表中,這個(gè)過(guò)程叫做漸進(jìn)式rehash。這樣,Redis能夠平滑地將數(shù)據(jù)從舊的哈希表遷移到新的哈希表,避免大規(guī)模的數(shù)據(jù)遷移對(duì)性能造成影響。

查詢流程:
客戶端發(fā)送查詢命令,指定要查詢的鍵。
Redis會(huì)根據(jù)鍵通過(guò)哈希函數(shù)計(jì)算哈希槽(hash slot)的索引,確定鍵在哪個(gè)數(shù)據(jù)庫(kù)中。
Redis根據(jù)數(shù)據(jù)庫(kù)的哈希表,找到對(duì)應(yīng)的字典。
在字典中,Redis使用鍵進(jìn)行查找,通過(guò)哈希表查找對(duì)應(yīng)的值。如果找到了值,則將其返回給客戶端。
如果鍵在當(dāng)前數(shù)據(jù)庫(kù)沒(méi)有找到對(duì)應(yīng)的值,Redis可以根據(jù)需要進(jìn)行跳轉(zhuǎn)到其他數(shù)據(jù)庫(kù)(例如在Redis集群中)。
整個(gè)查詢流程涉及到多次哈希計(jì)算和哈希表查找,這使得Redis能夠在平均時(shí)間復(fù)雜度為O(1)的情況下,高效地進(jìn)行鍵值對(duì)的查詢操作。由于Redis的全局哈希表是一個(gè)核心組件,其優(yōu)化和設(shè)計(jì)對(duì)于保障Redis的性能和可用性非常重要。
如果你只是了解了哈希表的 O(1) 復(fù)雜度和快速查找特性,那么,當(dāng)你往 Redis 中寫(xiě)入大量數(shù)據(jù)后,就可能發(fā)現(xiàn)操作有時(shí)候會(huì)突然變慢了。這其實(shí)是因?yàn)槟愫雎粤艘粋€(gè)潛在的風(fēng)險(xiǎn)點(diǎn),那就是哈希表的沖突問(wèn)題和 rehash 可能帶來(lái)的操作阻塞。
為什么哈希表操作變慢了?
Redis 解決哈希沖突的方式,就是鏈?zhǔn)焦?。鏈?zhǔn)焦R埠苋菀桌斫?,就是指同一個(gè)哈希桶中的多個(gè)元素用一個(gè)鏈表來(lái)保存,它們之間依次用指針連接。

哈希沖突是指在使用哈希函數(shù)將鍵映射到哈希表中的索引時(shí),兩個(gè)或多個(gè)鍵被映射到相同的索引位置。在Redis中,哈希表是通過(guò)哈希函數(shù)將鍵映射到一個(gè)固定數(shù)量的桶(bucket)中的。
Redis使用MurmurHash2算法作為默認(rèn)的哈希函數(shù),它是一種快速且低碰撞率的哈希函數(shù)。然而,即使使用了高質(zhì)量的哈希函數(shù),仍然存在哈希沖突的可能性。
當(dāng)發(fā)生哈希沖突時(shí),Redis使用鏈地址法(chaining)來(lái)解決。具體來(lái)說(shuō),每個(gè)桶中存儲(chǔ)的是一個(gè)鏈表,鏈表中的每個(gè)節(jié)點(diǎn)都包含了鍵值對(duì)。當(dāng)多個(gè)鍵被映射到同一個(gè)桶時(shí),它們會(huì)被添加到鏈表中,形成一個(gè)鍵值對(duì)的集合。
當(dāng)執(zhí)行哈希表的讀取操作時(shí),Redis會(huì)遍歷鏈表,直到找到匹配的鍵值對(duì)或者鏈表結(jié)束。這個(gè)過(guò)程的時(shí)間復(fù)雜度取決于鏈表的長(zhǎng)度,因此,如果哈希沖突較多,鏈表會(huì)變得很長(zhǎng),導(dǎo)致讀取操作的性能下降。
為了減少哈希沖突的發(fā)生,可以采取以下措施:
使用更好的哈希函數(shù):選擇一個(gè)更具隨機(jī)性和低碰撞率的哈希函數(shù),可以減少哈希沖突的概率。
擴(kuò)大哈希表的大?。涸黾庸1淼耐皵?shù)量,可以分散鍵的分布,減少哈希沖突的可能性。
使用一致性哈希算法:一致性哈希算法可以將鍵均勻地映射到多個(gè)節(jié)點(diǎn)上,減少單個(gè)節(jié)點(diǎn)上的哈希沖突。
哈希沖突是不可避免的,但可以通過(guò)選擇合適的哈希函數(shù)和調(diào)整哈希表的大小來(lái)減少其發(fā)生的概率,并且Redis的鏈地址法能夠有效地解決哈希沖突帶來(lái)的問(wèn)題。
但是,這里依然存在一個(gè)問(wèn)題,哈希沖突鏈上的元素只能通過(guò)指針逐一查找再操作。如果哈希表里寫(xiě)入的數(shù)據(jù)越來(lái)越多,哈希沖突可能也會(huì)越來(lái)越多,這就會(huì)導(dǎo)致某些哈希沖突鏈過(guò)長(zhǎng),進(jìn)而導(dǎo)致這個(gè)鏈上的元素查找耗時(shí)長(zhǎng),效率降低。對(duì)于追求“快”的 Redis 來(lái)說(shuō),這是不太能接受的。
所以,Redis 會(huì)對(duì)哈希表做 rehash 操作。rehash 也就是增加現(xiàn)有的哈希桶數(shù)量,讓逐漸增多的 entry 元素能在更多的桶之間分散保存,減少單個(gè)桶中的元素?cái)?shù)量,從而減少單個(gè)桶中的沖突。
那具體怎么做rehash呢?
Redis的rehash是指在哈希表擴(kuò)容或縮小時(shí),重新計(jì)算并重新分配所有鍵值對(duì)的過(guò)程。rehash的目的是為了保持哈希表的負(fù)載因子在一個(gè)合理的范圍內(nèi),以提高哈希表的性能。
在Redis中,rehash是一個(gè)漸進(jìn)式的過(guò)程,它不會(huì)一次性地將所有鍵值對(duì)重新分配到新的哈希表中,而是分多次進(jìn)行,每次處理一小部分鍵值對(duì)。這種漸進(jìn)式的rehash過(guò)程可以保證在rehash期間,Redis仍然可以正常處理讀取和寫(xiě)入操作,不會(huì)阻塞客戶端請(qǐng)求。
具體的rehash過(guò)程如下:
Redis會(huì)創(chuàng)建一個(gè)新的空哈希表,大小是當(dāng)前哈希表的兩倍(或更小,如果是縮小操作)。
Redis會(huì)將當(dāng)前哈希表的rehashidx屬性設(shè)置為0,表示rehash的起始位置。
在每次執(zhí)行讀取或?qū)懭氩僮鲿r(shí),Redis會(huì)同時(shí)對(duì)當(dāng)前哈希表和新哈希表進(jìn)行操作。
對(duì)于讀取操作,Redis首先在當(dāng)前哈希表中查找鍵值對(duì),如果找不到,則繼續(xù)在新哈希表中查找。
對(duì)于寫(xiě)入操作,Redis會(huì)將新的鍵值對(duì)添加到新哈希表中,同時(shí)保留當(dāng)前哈希表中的鍵值對(duì)。
在每次執(zhí)行完一定數(shù)量的操作后,Redis會(huì)逐步將當(dāng)前哈希表中的鍵值對(duì)遷移到新哈希表中,直到遷移完成。
最后,Redis會(huì)將新哈希表設(shè)置為當(dāng)前哈希表,并釋放舊的哈希表的內(nèi)存空間。
通過(guò)漸進(jìn)式的rehash過(guò)程,Redis可以平滑地將鍵值對(duì)從舊哈希表遷移到新哈希表,避免了一次性的大規(guī)模遷移帶來(lái)的性能問(wèn)題。同時(shí),由于讀取操作可以同時(shí)在兩個(gè)哈希表中進(jìn)行,所以即使在rehash過(guò)程中,Redis仍然可以提供正常的讀取服務(wù)。
需要注意的是,rehash過(guò)程是一個(gè)相對(duì)耗時(shí)的操作,特別是在哈希表中存儲(chǔ)了大量鍵值對(duì)的情況下。因此,在進(jìn)行rehash時(shí),應(yīng)該避免對(duì)Redis進(jìn)行大量的寫(xiě)入操作,以免影響性能。

底層實(shí)現(xiàn)復(fù)雜度總結(jié)

一、字符串(String)
適用場(chǎng)景
字符串(String)類型在Redis中是最常用的數(shù)據(jù)類型之一,適用于以下場(chǎng)景:
緩存:字符串類型可以用于緩存數(shù)據(jù),例如緩存數(shù)據(jù)庫(kù)查詢結(jié)果、計(jì)算結(jié)果等。由于Redis的高性能和快速讀寫(xiě)能力,使用字符串類型作為緩存可以大大提高系統(tǒng)的響應(yīng)速度。
計(jì)數(shù)器:字符串類型可以用于實(shí)現(xiàn)計(jì)數(shù)器功能,例如統(tǒng)計(jì)網(wǎng)站的訪問(wèn)次數(shù)、用戶的點(diǎn)贊數(shù)等。通過(guò)使用字符串類型的自增命令,可以方便地對(duì)計(jì)數(shù)器進(jìn)行增加或減少操作。
分布式鎖:字符串類型可以用于實(shí)現(xiàn)分布式鎖,保證在分布式環(huán)境下的數(shù)據(jù)一致性和并發(fā)控制。通過(guò)設(shè)置一個(gè)唯一的字符串作為鎖的值,并利用Redis的原子性操作,可以實(shí)現(xiàn)簡(jiǎn)單而高效的分布式鎖機(jī)制。
會(huì)話管理:字符串類型可以用于存儲(chǔ)用戶的會(huì)話信息,例如用戶登錄狀態(tài)、購(gòu)物車內(nèi)容等。通過(guò)將會(huì)話信息存儲(chǔ)在字符串類型中,可以方便地進(jìn)行讀寫(xiě)操作,并且可以設(shè)置過(guò)期時(shí)間來(lái)自動(dòng)清理過(guò)期的會(huì)話數(shù)據(jù)。
消息隊(duì)列:字符串類型可以用于實(shí)現(xiàn)簡(jiǎn)單的消息隊(duì)列,例如將消息內(nèi)容作為字符串存儲(chǔ)在Redis中,然后使用列表類型的命令進(jìn)行消息的發(fā)布和訂閱。
分布式緩存:字符串類型可以用于實(shí)現(xiàn)分布式緩存,例如將經(jīng)過(guò)序列化的對(duì)象存儲(chǔ)在字符串類型中,然后通過(guò)緩存命中來(lái)提高系統(tǒng)的性能和擴(kuò)展性。
底層實(shí)現(xiàn)是什么
當(dāng)我們?cè)赗edis中存儲(chǔ)字符串時(shí),Redis使用了一種稱為簡(jiǎn)單動(dòng)態(tài)字符串(Simple Dynamic String,SDS)的數(shù)據(jù)結(jié)構(gòu)來(lái)表示字符串。
SDS是Redis自己實(shí)現(xiàn)的一種字符串表示方式,相比于傳統(tǒng)的C語(yǔ)言字符串,SDS具有許多優(yōu)勢(shì)和特點(diǎn)。
動(dòng)態(tài)調(diào)整大?。篠DS可以根據(jù)字符串的長(zhǎng)度動(dòng)態(tài)調(diào)整內(nèi)存大小。這意味著當(dāng)我們向SDS中添加更多的字符時(shí),SDS會(huì)自動(dòng)分配更多的內(nèi)存空間來(lái)容納新的字符,而不需要手動(dòng)管理內(nèi)存分配和釋放。這樣可以避免頻繁的內(nèi)存重新分配操作,提高了性能。
O(1)時(shí)間復(fù)雜度的長(zhǎng)度獲?。篠DS在內(nèi)部維護(hù)了字符串的長(zhǎng)度信息。因此,無(wú)論字符串的長(zhǎng)度是多少,我們都可以在常數(shù)時(shí)間內(nèi)獲取字符串的長(zhǎng)度,而不需要遍歷整個(gè)字符串。這使得獲取字符串長(zhǎng)度的操作非常高效。
二進(jìn)制安全:SDS可以存儲(chǔ)任意二進(jìn)制數(shù)據(jù),而不僅僅局限于文本字符串。這意味著我們可以在SDS中存儲(chǔ)包含空字符('\0')在內(nèi)的任意二進(jìn)制數(shù)據(jù),而不會(huì)導(dǎo)致字符串的截?cái)嗷蝈e(cuò)誤解析。
緩沖區(qū)溢出保護(hù):SDS在內(nèi)部維護(hù)了字符串的長(zhǎng)度信息,這使得Redis能夠有效地防止緩沖區(qū)溢出的問(wèn)題。當(dāng)我們向SDS中添加新的字符時(shí),Redis會(huì)檢查是否有足夠的空間來(lái)容納新的字符,如果沒(méi)有足夠的空間,Redis會(huì)自動(dòng)分配更多的內(nèi)存空間,以避免溢出。
兼容C字符串:SDS可以通過(guò)轉(zhuǎn)換函數(shù)與C字符串進(jìn)行互相轉(zhuǎn)換。這意味著我們可以在Redis中使用SDS來(lái)存儲(chǔ)字符串,然后將其轉(zhuǎn)換為C字符串,以便與現(xiàn)有的C代碼進(jìn)行交互。反之,我們也可以將C字符串轉(zhuǎn)換為SDS,以便在Redis中使用更多的字符串操作功能。
SDS的結(jié)構(gòu)如下:
struct sdshdr {
? ?int len; ? ? ? ?// 字符串的長(zhǎng)度
? ?int free; ? ? ? // 未使用的字節(jié)長(zhǎng)度
? ?char buf[]; ? ? // 字符串的實(shí)際內(nèi)容
};
其中,len
表示字符串的長(zhǎng)度,free
表示未使用的字節(jié)長(zhǎng)度,buf
是一個(gè)柔性數(shù)組,用于存儲(chǔ)字符串的實(shí)際內(nèi)容。
通過(guò)使用簡(jiǎn)單動(dòng)態(tài)字符串作為底層數(shù)據(jù)結(jié)構(gòu),Redis能夠高效地處理字符串操作,并提供了豐富的字符串操作命令和功能。這使得Redis成為一個(gè)強(qiáng)大的鍵值存儲(chǔ)系統(tǒng),可以用于各種不同的應(yīng)用場(chǎng)景。作為新手,了解SDS的特點(diǎn)和結(jié)構(gòu)將有助于你更好地理解和使用Redis中的字符串?dāng)?shù)據(jù)類型。
如何使用
要在Redis中使用字符串類型,你可以使用以下命令:
設(shè)置字符串值:使用
SET
命令可以設(shè)置一個(gè)字符串鍵的值。例如,SET key value
將鍵key
的值設(shè)置為value
。獲取字符串值:使用
GET
命令可以獲取一個(gè)字符串鍵的值。例如,GET key
將返回鍵key
的值。自增/自減操作:使用
INCR
命令可以將一個(gè)字符串鍵的值自增1,使用DECR
命令可以將一個(gè)字符串鍵的值自減1。例如,INCR key
將鍵key
的值增加1。設(shè)置過(guò)期時(shí)間:使用
EXPIRE
命令可以為一個(gè)字符串鍵設(shè)置過(guò)期時(shí)間,單位為秒。例如,EXPIRE key seconds
將鍵key
的過(guò)期時(shí)間設(shè)置為seconds
秒。批量操作:使用
MSET
命令可以同時(shí)設(shè)置多個(gè)字符串鍵的值,使用MGET
命令可以同時(shí)獲取多個(gè)字符串鍵的值。字符串拼接:使用
APPEND
命令可以將指定字符串追加到一個(gè)字符串鍵的值的末尾。其他操作:Redis還提供了許多其他的字符串操作命令,如獲取子字符串、獲取字符串長(zhǎng)度、設(shè)置指定位置的字符等。
以下是一些示例命令的用法:
SET name "John" ? ? ? ? ?// 設(shè)置鍵為name的值為"John"
GET name ? ? ? ? ? ? ? ? // 獲取鍵為name的值
INCR counter ? ? ? ? ? ? // 將鍵為counter的值自增1
EXPIRE key 60 ? ? ? ? ? ?// 設(shè)置鍵為key的過(guò)期時(shí)間為60秒
MSET key1 value1 key2 value2 ? // 同時(shí)設(shè)置多個(gè)鍵值對(duì)
MGET key1 key2 ? ? ? ? ? // 同時(shí)獲取多個(gè)鍵的值
APPEND greeting ", welcome!" ? // 將", welcome!"追加到鍵greeting的值的末尾
通過(guò)使用這些命令,你可以在Redis中靈活地操作字符串類型,實(shí)現(xiàn)各種功能和應(yīng)用場(chǎng)景。記得在使用字符串類型時(shí),根據(jù)具體需求選擇合適的命令和參數(shù),并注意處理異常情況和錯(cuò)誤返回值。
需要注意的地方
在使用Redis的字符串類型時(shí),有一些需要注意的地方:
字符串長(zhǎng)度限制:Redis的字符串類型最大可以存儲(chǔ)512MB的數(shù)據(jù)。如果需要存儲(chǔ)更大的數(shù)據(jù),可以考慮使用Redis的其他數(shù)據(jù)類型或?qū)?shù)據(jù)分片存儲(chǔ)。
數(shù)據(jù)類型轉(zhuǎn)換:當(dāng)使用字符串類型時(shí),需要注意數(shù)據(jù)類型的轉(zhuǎn)換。Redis的字符串類型是二進(jìn)制安全的,可以存儲(chǔ)任意二進(jìn)制數(shù)據(jù),但在使用時(shí)需要根據(jù)具體情況進(jìn)行數(shù)據(jù)的序列化和反序列化。
過(guò)期時(shí)間設(shè)置:通過(guò)使用
EXPIRE
命令可以為字符串鍵設(shè)置過(guò)期時(shí)間,但需要注意過(guò)期時(shí)間的合理設(shè)置。過(guò)期時(shí)間過(guò)短可能導(dǎo)致頻繁的數(shù)據(jù)失效和重新加載,過(guò)期時(shí)間過(guò)長(zhǎng)可能導(dǎo)致數(shù)據(jù)過(guò)期不及時(shí)。內(nèi)存使用:由于Redis是內(nèi)存數(shù)據(jù)庫(kù),使用字符串類型時(shí)需要注意內(nèi)存的使用情況。特別是在存儲(chǔ)大量字符串?dāng)?shù)據(jù)時(shí),需要合理控制內(nèi)存的分配和釋放,避免出現(xiàn)內(nèi)存溢出的問(wèn)題。
并發(fā)操作:在多線程或多進(jìn)程環(huán)境下使用字符串類型時(shí),需要注意并發(fā)操作的問(wèn)題。Redis提供了原子性操作命令,如自增、自減等,可以保證操作的原子性,但需要注意并發(fā)操作可能導(dǎo)致的數(shù)據(jù)競(jìng)爭(zhēng)和一致性問(wèn)題。
鍵的命名規(guī)范:為了避免鍵的沖突和混淆,建議在命名字符串鍵時(shí)使用有意義的、具有一定規(guī)范的命名方式,以便更好地管理和維護(hù)數(shù)據(jù)。
數(shù)據(jù)備份和持久化:Redis提供了數(shù)據(jù)持久化的機(jī)制,可以將數(shù)據(jù)保存到磁盤(pán)上,以防止數(shù)據(jù)丟失。在使用字符串類型時(shí),可以考慮定期進(jìn)行數(shù)據(jù)備份和持久化操作,以保證數(shù)據(jù)的安全性和可恢復(fù)性。
總之,在使用Redis的字符串類型時(shí),需要根據(jù)具體的應(yīng)用場(chǎng)景和需求,合理選擇命令和參數(shù),并注意處理異常情況和錯(cuò)誤返回值。同時(shí),合理規(guī)劃和管理數(shù)據(jù),注意內(nèi)存使用和并發(fā)操作,可以更好地利用Redis的字符串類型,提高系統(tǒng)的性能和可靠性。
二、列表(List)
適用場(chǎng)景
列表(List)類型在Redis中是一種非常常用的數(shù)據(jù)類型,適用于以下場(chǎng)景:
消息隊(duì)列:列表類型可以用于實(shí)現(xiàn)簡(jiǎn)單的消息隊(duì)列。生產(chǎn)者可以使用
LPUSH
命令將消息添加到列表的頭部,消費(fèi)者可以使用RPOP
命令從列表的尾部獲取消息。這種方式可以實(shí)現(xiàn)先進(jìn)先出(FIFO)的消息處理。實(shí)時(shí)排行榜:列表類型可以用于實(shí)現(xiàn)實(shí)時(shí)排行榜。例如,可以使用
LPUSH
命令將用戶的得分添加到列表中,然后使用LPOP
命令獲取排行榜的前幾名。任務(wù)隊(duì)列:列表類型可以用于實(shí)現(xiàn)任務(wù)隊(duì)列。生產(chǎn)者可以使用
LPUSH
命令將任務(wù)添加到列表的尾部,消費(fèi)者可以使用RPOP
命令從列表的頭部獲取任務(wù)。這種方式可以實(shí)現(xiàn)任務(wù)的分發(fā)和處理。消息發(fā)布與訂閱:列表類型可以用于實(shí)現(xiàn)簡(jiǎn)單的消息發(fā)布與訂閱。生產(chǎn)者可以使用
LPUSH
命令將消息添加到列表的頭部,訂閱者可以使用BLPOP
命令阻塞地從列表中獲取消息。歷史記錄:列表類型可以用于存儲(chǔ)歷史記錄。例如,可以使用
LPUSH
命令將用戶的瀏覽記錄添加到列表中,然后使用LRANGE
命令獲取最近的瀏覽記錄。
底層實(shí)現(xiàn)是什么
當(dāng)涉及到Redis中列表類型的底層實(shí)現(xiàn)時(shí),有兩種可能的數(shù)據(jù)結(jié)構(gòu):壓縮列表(Ziplist)和雙向鏈表(Doubly Linked List)。
壓縮列表(Ziplist):
壓縮列表是一種緊湊的數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)較小的列表。它將多個(gè)列表元素緊密地存儲(chǔ)在一起,以減少內(nèi)存的使用。壓縮列表的結(jié)構(gòu)如下:
<zlbytes><zltail><zllen><entry><entry>...<entry><zlend>
- `<zlbytes>`:表示壓縮列表的總字節(jié)數(shù)。
- `<zltail>`:指向壓縮列表的最后一個(gè)節(jié)點(diǎn)。
- `<zllen>`:表示壓縮列表中的元素?cái)?shù)量。
- `<entry>`:表示每個(gè)列表元素的存儲(chǔ)形式,包括元素長(zhǎng)度和元素內(nèi)容。
- `<zlend>`:表示壓縮列表的結(jié)束標(biāo)志。
壓縮列表的優(yōu)勢(shì)在于它可以在一定程度上減少內(nèi)存的使用,并且對(duì)于較小的列表,它的性能比雙向鏈表更好。但是,當(dāng)列表的長(zhǎng)度或元素的大小超過(guò)一定限制時(shí),Redis會(huì)自動(dòng)將壓縮列表轉(zhuǎn)換為雙向鏈表。
雙向鏈表(Doubly Linked List):
雙向鏈表是一種常見(jiàn)的數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)列表元素。每個(gè)節(jié)點(diǎn)都包含一個(gè)指向前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn)的指針。雙向鏈表的結(jié)構(gòu)如下:
<prev><entry><next>
- `<prev>`:指向前一個(gè)節(jié)點(diǎn)的指針。
- `<entry>`:表示節(jié)點(diǎn)中存儲(chǔ)的列表元素。
- `<next>`:指向后一個(gè)節(jié)點(diǎn)的指針。
雙向鏈表的優(yōu)勢(shì)在于它可以高效地進(jìn)行插入、刪除和遍歷操作。通過(guò)指針,可以快速地在鏈表中移動(dòng),并且在任意位置插入或刪除節(jié)點(diǎn)的開(kāi)銷較小。
Redis在選擇使用壓縮列表還是雙向鏈表作為列表的底層實(shí)現(xiàn)時(shí),會(huì)根據(jù)以下兩個(gè)因素進(jìn)行判斷:
列表的長(zhǎng)度:當(dāng)列表的長(zhǎng)度超過(guò)一定限制(默認(rèn)為512個(gè)元素)時(shí),Redis會(huì)將壓縮列表轉(zhuǎn)換為雙向鏈表,以便更好地處理大型列表。
列表元素的大?。寒?dāng)列表中的元素大小超過(guò)一定限制(默認(rèn)為64字節(jié))時(shí),Redis會(huì)將壓縮列表轉(zhuǎn)換為雙向鏈表,以便更好地處理大型元素。
轉(zhuǎn)換時(shí)機(jī)是在執(zhí)行插入或刪除操作時(shí)進(jìn)行檢查的。如果列表滿足轉(zhuǎn)換條件,Redis會(huì)自動(dòng)將壓縮列表轉(zhuǎn)換為雙向鏈表,并將數(shù)據(jù)從壓縮列表復(fù)制到新的雙向鏈表中。這個(gè)轉(zhuǎn)換過(guò)程可能會(huì)導(dǎo)致一些額外的內(nèi)存開(kāi)銷,但它使得Redis能夠更好地處理大型列表和大型元素。
通過(guò)使用壓縮列表和雙向鏈表作為底層實(shí)現(xiàn),Redis的列表類型可以在不同的場(chǎng)景下提供高效的性能和靈活性。
如何使用
在Redis中,可以使用列表(List)類型進(jìn)行以下操作:
添加元素:
使用
LPUSH key value
命令將一個(gè)或多個(gè)元素添加到列表的頭部。使用
RPUSH key value
命令將一個(gè)或多個(gè)元素添加到列表的尾部。彈出元素:
使用
LPOP key
命令從列表的頭部彈出并返回一個(gè)元素。使用
RPOP key
命令從列表的尾部彈出并返回一個(gè)元素。獲取元素:
使用
LINDEX key index
命令獲取列表中指定位置的元素。索引從0開(kāi)始,負(fù)數(shù)表示從尾部開(kāi)始計(jì)數(shù)。使用
LRANGE key start stop
命令獲取列表中指定范圍的元素。范圍包括起始位置和結(jié)束位置,負(fù)數(shù)表示從尾部開(kāi)始計(jì)數(shù)。獲取列表長(zhǎng)度:
使用
LLEN key
命令獲取列表的長(zhǎng)度。在指定元素前或后插入元素:
使用
LINSERT key BEFORE|AFTER pivot value
命令在列表中指定元素的前或后插入一個(gè)元素。移除指定數(shù)量的元素:
使用
LREM key count value
命令從列表中移除指定數(shù)量的匹配元素。獲取并設(shè)置指定位置的元素:
使用
LSET key index value
命令將列表中指定位置的元素設(shè)置為新的值,并返回舊的值。獲取并移動(dòng)元素:
使用
RPOPLPUSH source destination
命令從一個(gè)列表的尾部彈出一個(gè)元素,并將它添加到另一個(gè)列表的頭部。阻塞彈出元素:
使用
BLPOP key1 key2 ... timeout
命令阻塞地從多個(gè)列表中彈出元素,直到有元素可彈出或超時(shí)。
這些是列表類型的一些常用操作,可以根據(jù)具體的需求選擇適合的命令來(lái)操作列表。列表類型在Redis中非常靈活和多用途,適用于各種場(chǎng)景,包括消息隊(duì)列、排行榜、任務(wù)隊(duì)列、消息發(fā)布與訂閱、歷史記錄等。
需要注意的地方
在使用Redis中的列表數(shù)據(jù)類型時(shí),有一些注意事項(xiàng)和最佳實(shí)踐,特別是對(duì)于新手來(lái)說(shuō)。以下是一些使用Redis列表時(shí)需要注意的地方:
插入順序和重復(fù):
列表是有序的數(shù)據(jù)結(jié)構(gòu),插入的元素按照插入的順序排列。允許插入重復(fù)的元素,因此列表可以作為一個(gè)簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)隊(duì)列或棧。
左右插入操作:
Redis提供了
LPUSH
和RPUSH
命令來(lái)在列表的左側(cè)和右側(cè)插入元素。左側(cè)插入類似于棧的操作,右側(cè)插入類似于隊(duì)列的操作。范圍操作:
使用
LRANGE
命令可以獲取列表中的一定范圍的元素。這對(duì)于分頁(yè)顯示、獲取最近的數(shù)據(jù)等場(chǎng)景非常有用。修剪列表:
使用
LTRIM
命令可以修剪列表,只保留指定范圍的元素,其余的元素會(huì)被刪除。列表長(zhǎng)度:
使用
LLEN
命令可以獲取列表的長(zhǎng)度。彈出元素:
使用
LPOP
和RPOP
命令可以從列表的左側(cè)和右側(cè)彈出一個(gè)元素。這可以用于實(shí)現(xiàn)隊(duì)列和棧的行為。阻塞操作:
Redis還提供了阻塞版本的彈出操作,例如
BLPOP
和BRPOP
,這些命令可以在列表為空時(shí)阻塞等待新元素的到來(lái)。遍歷操作:
Redis并沒(méi)有直接提供像迭代器一樣的遍歷機(jī)制,因此如果需要遍歷列表,需要自己實(shí)現(xiàn)。
內(nèi)存注意:
列表雖然很方便,但隨著元素的增加,內(nèi)存占用也會(huì)增加。在插入大量元素時(shí)要注意內(nèi)存消耗。
不適合大型列表:
Redis的列表是基于鏈表實(shí)現(xiàn)的,對(duì)于大型列表的隨機(jī)訪問(wèn)效率較低,如果需要頻繁的隨機(jī)訪問(wèn),請(qǐng)考慮其他數(shù)據(jù)結(jié)構(gòu)。
避免濫用:
列表適用于有序插入和刪除的場(chǎng)景,但不適合用作集合數(shù)據(jù)的存儲(chǔ)。如果需要集合操作,可以考慮使用集合(Set)數(shù)據(jù)類型。
總之,使用Redis列表時(shí)需要根據(jù)具體的業(yè)務(wù)需求和場(chǎng)景來(lái)選擇。了解Redis列表的特點(diǎn)和限制,可以幫助你更好地規(guī)劃和使用這一數(shù)據(jù)類型。
三、集合(Set)
適用場(chǎng)景
Redis的Set數(shù)據(jù)類型是一個(gè)無(wú)序的字符串集合,它可以存儲(chǔ)多個(gè)不重復(fù)的元素。Set在Redis中有許多實(shí)際的使用場(chǎng)景,以下是一些常見(jiàn)的使用場(chǎng)景:
唯一性數(shù)據(jù)存儲(chǔ):
最基本的使用場(chǎng)景就是用來(lái)存儲(chǔ)不重復(fù)的數(shù)據(jù)。你可以使用Set來(lái)存儲(chǔ)用戶ID、IP地址、郵箱地址等,確保數(shù)據(jù)的唯一性。
標(biāo)簽和標(biāo)記系統(tǒng):
Set可以用于創(chuàng)建標(biāo)簽或標(biāo)記系統(tǒng)。例如,你可以為文章、商品或其他實(shí)體創(chuàng)建一個(gè)包含相關(guān)標(biāo)簽的Set,以便后續(xù)快速檢索。
關(guān)注和粉絲系統(tǒng):
在社交媒體或用戶關(guān)系管理中,Set可以用來(lái)實(shí)現(xiàn)關(guān)注和粉絲系統(tǒng)。每個(gè)用戶可以有一個(gè)Set,其中包含他們關(guān)注的其他用戶或粉絲。
在線用戶:
Set可以用于跟蹤在線用戶。將用戶ID添加到一個(gè)Set中,表示用戶當(dāng)前在線。通過(guò)檢查Set中的成員,可以快速查找在線用戶。
投票系統(tǒng):
Set可以用于實(shí)現(xiàn)投票系統(tǒng)。每個(gè)投票項(xiàng)目可以表示為一個(gè)Set,用戶投票時(shí)將其ID添加到相應(yīng)的Set中,確保每個(gè)用戶只能投一次。
集合運(yùn)算:
Redis提供了多種Set運(yùn)算,如交集、并集和差集。這些運(yùn)算可以用于計(jì)算多個(gè)集合之間的共同元素、合并元素等。
排行榜和排名:
Set可以用于創(chuàng)建排行榜系統(tǒng)。例如,每個(gè)元素代表一個(gè)玩家,分?jǐn)?shù)作為元素的權(quán)重??梢酝ㄟ^(guò)有序集合操作獲取排名和排行。
地理位置標(biāo)記:
Set可以用于存儲(chǔ)地理位置數(shù)據(jù),例如存儲(chǔ)用戶的經(jīng)緯度坐標(biāo),然后利用Set運(yùn)算來(lái)查找附近的位置。
過(guò)濾重復(fù)事件:
如果你需要記錄一系列事件,并且要確保事件不重復(fù)記錄,可以使用Set來(lái)存儲(chǔ)已經(jīng)發(fā)生的事件,防止重復(fù)記錄。
總的來(lái)說(shuō),Redis的Set數(shù)據(jù)類型非常適合需要存儲(chǔ)不重復(fù)數(shù)據(jù)、進(jìn)行集合運(yùn)算以及需要高效查找元素的場(chǎng)景。無(wú)論是在社交網(wǎng)絡(luò)、實(shí)時(shí)分析、排行榜、地理位置服務(wù)等領(lǐng)域,Set都有著廣泛的應(yīng)用。
底層實(shí)現(xiàn)是什么
在Redis中,集合(Set)類型的底層實(shí)現(xiàn)有兩種:哈希表(Hash Table)和跳躍表(Skip List)。
哈希表(Hash Table):哈希表是一種使用哈希函數(shù)將元素映射到桶(bucket)的數(shù)據(jù)結(jié)構(gòu)。在Redis中,集合的每個(gè)元素都被存儲(chǔ)在哈希表的一個(gè)桶中。哈希表提供了快速的插入、刪除和查找操作,平均情況下的時(shí)間復(fù)雜度為O(1)。哈希表適用于存儲(chǔ)大量元素的集合,并且對(duì)于查找操作的性能要求較高。
跳躍表(Skip List):跳躍表是一種有序的數(shù)據(jù)結(jié)構(gòu),它通過(guò)多層鏈表的方式來(lái)提供快速的查找操作。每個(gè)節(jié)點(diǎn)都包含一個(gè)指向下一層和右側(cè)節(jié)點(diǎn)的指針。在Redis中,集合的元素按照從小到大的順序存儲(chǔ)在跳躍表中。跳躍表提供了快速的插入、刪除和范圍查找操作,平均情況下的時(shí)間復(fù)雜度為O(log n)。跳躍表適用于有序集合的場(chǎng)景,或者對(duì)于范圍查找操作的性能要求較高。
在Redis中,當(dāng)集合的元素?cái)?shù)量較少時(shí),底層實(shí)現(xiàn)會(huì)使用哈希表。當(dāng)集合的元素?cái)?shù)量增加到一定閾值時(shí),Redis會(huì)自動(dòng)將哈希表轉(zhuǎn)換為跳躍表,以提供更好的性能和空間效率。

Redis中的有序集合(Sorted Set)使用的是跳躍表(Skip List)數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)的。跳躍表是一種用于有序元素存儲(chǔ)和檢索的數(shù)據(jù)結(jié)構(gòu),它的設(shè)計(jì)使得有序集合的插入、刪除和查找操作都能在平均情況下達(dá)到 O(log n) 的時(shí)間復(fù)雜度。
跳躍表(Skip List)實(shí)現(xiàn)原理:
多級(jí)索引:
跳躍表的核心思想是使用多級(jí)索引來(lái)加速查找操作。除了底層的鏈表結(jié)構(gòu),跳躍表還有多個(gè)級(jí)別的索引,每一級(jí)索引都是一個(gè)較小的有序鏈表,其中的節(jié)點(diǎn)包含指向下一級(jí)索引節(jié)點(diǎn)的指針。
底層鏈表:
跳躍表的底層是一個(gè)有序鏈表,節(jié)點(diǎn)按照鍵的大小順序排列。每個(gè)節(jié)點(diǎn)包含一個(gè)鍵和對(duì)應(yīng)的值。
多級(jí)索引節(jié)點(diǎn):
跳躍表的多級(jí)索引節(jié)點(diǎn)也是有序鏈表,但是它的節(jié)點(diǎn)數(shù)目比底層鏈表少。每個(gè)多級(jí)索引節(jié)點(diǎn)都存儲(chǔ)了指向底層鏈表中對(duì)應(yīng)范圍節(jié)點(diǎn)的指針。不同級(jí)別的索引通過(guò)鏈?zhǔn)竭B接在一起。
節(jié)點(diǎn)的分布:
節(jié)點(diǎn)在不同級(jí)別的索引中以一定概率分布,使得跳躍表在查詢時(shí)能夠快速跳過(guò)一些不必要的節(jié)點(diǎn),從而達(dá)到快速查找的效果。
跳躍表查詢流程:
客戶端發(fā)送查詢命令,指定要查詢的成員。
Redis會(huì)從頂級(jí)索引(最高級(jí)別)開(kāi)始,逐級(jí)向右移動(dòng),查找每一級(jí)索引中的節(jié)點(diǎn)。
在每一級(jí)索引中,Redis會(huì)沿著鏈表移動(dòng),比較節(jié)點(diǎn)的鍵與要查找的成員的大小。
當(dāng)找到第一個(gè)大于等于要查找成員的節(jié)點(diǎn)時(shí),如果節(jié)點(diǎn)的鍵等于要查找的成員,查找成功;如果節(jié)點(diǎn)的鍵大于要查找的成員,就會(huì)進(jìn)入下一級(jí)索引繼續(xù)查找。
如果最底層鏈表中沒(méi)有找到匹配的節(jié)點(diǎn),那么查詢失敗,返回結(jié)果為空。
跳躍表的設(shè)計(jì)使得它在有序集合中實(shí)現(xiàn)高效的查找、插入和刪除操作,特別是對(duì)于范圍查詢等操作。通過(guò)多級(jí)索引和有序鏈表的結(jié)合,Redis的有序集合能夠在平均情況下達(dá)到 O(log n) 的時(shí)間復(fù)雜度,從而保證了高性能的數(shù)據(jù)操作。
如何使用
Redis的Set是一種無(wú)序、不重復(fù)元素的數(shù)據(jù)結(jié)構(gòu),類似于數(shù)學(xué)上的集合。它支持添加、刪除和查詢?cè)?,并且能夠?qū)Χ鄠€(gè)集合進(jìn)行交集、并集、差集等操作。下面是關(guān)于Redis Set的基本使用方法:
1. 添加元素:
使用 SADD
命令可以向一個(gè)Set中添加一個(gè)或多個(gè)元素。
SADD myset value1 value2 value3
2. 刪除元素:
使用 SREM
命令可以從一個(gè)Set中刪除一個(gè)或多個(gè)元素。
SREM myset value1 value2
3. 判斷元素是否存在:
使用 SISMEMBER
命令可以判斷一個(gè)元素是否存在于Set中。
SISMEMBER myset value
4. 獲取集合中的元素?cái)?shù)量:
使用 SCARD
命令可以獲取一個(gè)Set中元素的數(shù)量。
SCARD myset
5. 獲取集合中的所有元素:
使用 SMEMBERS
命令可以獲取一個(gè)Set中的所有元素。
SMEMBERS myset
6. 集合操作:
并集:使用
SUNION
命令可以對(duì)多個(gè)Set進(jìn)行并集操作。交集:使用
SINTER
命令可以對(duì)多個(gè)Set進(jìn)行交集操作。差集:使用
SDIFF
命令可以對(duì)多個(gè)Set進(jìn)行差集操作。
SUNION destination_set set1 set2
SINTER destination_set set1 set2
SDIFF destination_set set1 set2
需要注意的地方
在使用Redis的Set數(shù)據(jù)類型時(shí),有一些注意事項(xiàng)和最佳實(shí)踐可以幫助你更好地利用它。以下是使用Redis Set時(shí)需要注意的幾個(gè)方面:
1. 唯一性:
Set是無(wú)序、不重復(fù)元素的集合。確保你向Set中添加的元素是唯一的,因?yàn)镾et不會(huì)存儲(chǔ)重復(fù)的值。
2. 數(shù)據(jù)量:
雖然Redis可以處理大量的數(shù)據(jù),但仍需謹(jǐn)慎處理數(shù)據(jù)量較大的Set。當(dāng)Set中的元素?cái)?shù)量變得很大時(shí),查詢、插入和刪除等操作的性能可能會(huì)受到影響。
3. 考慮使用過(guò)期時(shí)間:
可以為Set設(shè)置過(guò)期時(shí)間,讓不再需要的數(shù)據(jù)自動(dòng)過(guò)期,以釋放內(nèi)存資源。
4. 避免大量的成員操作:
在某些情況下,如果需要對(duì)Set中的大量成員進(jìn)行操作(如刪除),可能會(huì)影響性能。如果需要頻繁進(jìn)行大規(guī)模操作,可以考慮使用多個(gè)小規(guī)模的Set,而不是一個(gè)包含大量成員的Set。
5. 集合操作注意事項(xiàng):
集合操作(如并集、交集、差集)可能會(huì)對(duì)性能產(chǎn)生一定影響,特別是在Set的成員數(shù)量較大時(shí)。在執(zhí)行集合操作時(shí),應(yīng)該考慮其對(duì)性能的影響,并根據(jù)實(shí)際情況進(jìn)行優(yōu)化。
6. 避免全量遍歷:
避免使用SMEMBERS
等命令獲取所有成員,因?yàn)樵诖髷?shù)據(jù)集下會(huì)產(chǎn)生性能問(wèn)題。如果需要遍歷成員,可以考慮使用SSCAN
命令進(jìn)行分頁(yè)式的遍歷。
7. 使用有序集合代替:
如果你需要有序的集合,可以考慮使用有序集合(Sorted Set)數(shù)據(jù)類型,它可以同時(shí)提供有序性和唯一性,適用于排行榜、計(jì)分系統(tǒng)等場(chǎng)景。
8. 持久化和備份:
在重要的生產(chǎn)環(huán)境中,始終要考慮持久化和備份策略,以確保數(shù)據(jù)不會(huì)因?yàn)橐馔馇闆r而丟失。
總之,在使用Redis的Set數(shù)據(jù)類型時(shí),需要根據(jù)應(yīng)用需求和數(shù)據(jù)量合理規(guī)劃和優(yōu)化。了解你的數(shù)據(jù)模型、數(shù)據(jù)量以及操作需求,可以幫助你更好地利用Redis的Set功能,并確保系統(tǒng)的性能和穩(wěn)定性。
四、有序集合(Sorted Set):與集合類似,但每個(gè)元素都關(guān)聯(lián)一個(gè)分?jǐn)?shù),可以根據(jù)分?jǐn)?shù)進(jìn)行排序。
適用場(chǎng)景
有序集合(Sorted Set)是Redis中的一種特殊數(shù)據(jù)類型,它在有序性和唯一性的基礎(chǔ)上,為存儲(chǔ)一組成員(元素)分配了一個(gè)分?jǐn)?shù)(score)。這種數(shù)據(jù)結(jié)構(gòu)使得有序集合在許多應(yīng)用場(chǎng)景中非常有用。以下是一些適用場(chǎng)景:
1. 排行榜和計(jì)分系統(tǒng): 有序集合非常適合實(shí)現(xiàn)排行榜和計(jì)分系統(tǒng)。成員的分?jǐn)?shù)可以表示玩家的得分、評(píng)分、積分等。你可以通過(guò)分?jǐn)?shù)對(duì)成員進(jìn)行排序,快速地獲取前幾名的排名。
2. 時(shí)間序列數(shù)據(jù): 如果你需要存儲(chǔ)帶有時(shí)間戳的數(shù)據(jù),有序集合可以根據(jù)時(shí)間戳(作為分?jǐn)?shù))進(jìn)行排序,然后按時(shí)間范圍快速查詢數(shù)據(jù)。
3. 最新消息: 有序集合可以用來(lái)存儲(chǔ)最新的消息,每個(gè)消息的分?jǐn)?shù)可以是消息的時(shí)間戳,這樣可以方便地獲取最新的消息。
4. 帶權(quán)重的標(biāo)簽/標(biāo)簽云: 在社交網(wǎng)絡(luò)或標(biāo)簽系統(tǒng)中,你可以使用有序集合來(lái)存儲(chǔ)標(biāo)簽,成員是標(biāo)簽,分?jǐn)?shù)可以表示標(biāo)簽的熱度、權(quán)重等。這可以用來(lái)實(shí)現(xiàn)標(biāo)簽云、熱門(mén)標(biāo)簽等功能。
5. 范圍查詢: 有序集合允許根據(jù)分?jǐn)?shù)范圍進(jìn)行查詢,從而可以快速地獲取在某個(gè)分?jǐn)?shù)范圍內(nèi)的成員。
6. 唯一性: 有序集合保持了成員的唯一性,這意味著你可以方便地存儲(chǔ)和查詢不重復(fù)的元素。
7. 高級(jí)集合運(yùn)算: Redis提供了對(duì)有序集合的集合運(yùn)算(交集、并集、差集)操作,這可以用來(lái)實(shí)現(xiàn)多個(gè)數(shù)據(jù)集的交叉分析、數(shù)據(jù)篩選等。
8. 范圍分頁(yè): 使用ZRANGE
等命令,可以對(duì)有序集合進(jìn)行分頁(yè)查詢,獲取指定范圍內(nèi)的成員。
總之,有序集合適用于需要保持元素有序性、需要快速進(jìn)行范圍查詢、具有權(quán)重或分?jǐn)?shù)的情況。它在多個(gè)場(chǎng)景中都提供了高效的數(shù)據(jù)存儲(chǔ)和操作,使得Redis成為了解決這些問(wèn)題的有力工具。
底層實(shí)現(xiàn)是什么
Redis的有序集合(Sorted Set)底層的實(shí)現(xiàn)采用了跳躍表(Skip List)和哈希表(Hash Table)的結(jié)合。這種設(shè)計(jì)使得有序集合既能在保持有序性的同時(shí),也能夠高效地執(zhí)行添加、刪除、查詢等操作。
跳躍表(Skip List): 跳躍表是用來(lái)維護(hù)有序集合中的成員的。在有序集合中,每個(gè)成員都有一個(gè)分?jǐn)?shù)(score),而跳躍表則根據(jù)這個(gè)分?jǐn)?shù)來(lái)排序成員。跳躍表通過(guò)多級(jí)索引,可以在平均情況下實(shí)現(xiàn) O(log n) 的插入、刪除和查詢操作。
哈希表(Hash Table): 有序集合在存儲(chǔ)成員和分?jǐn)?shù)之間的映射關(guān)系時(shí),使用了哈希表。每個(gè)成員都會(huì)在哈希表中對(duì)應(yīng)一個(gè)鍵值對(duì),其中鍵是成員,值是分?jǐn)?shù)。通過(guò)哈希表,Redis可以在 O(1) 時(shí)間內(nèi)查找某個(gè)成員的分?jǐn)?shù)。
結(jié)合使用的方式: 有序集合的每個(gè)元素在底層的哈希表中存儲(chǔ)著成員和分?jǐn)?shù)的映射關(guān)系,同時(shí)在跳躍表中存儲(chǔ)了成員的排序信息。通過(guò)這種方式,Redis可以在跳躍表中按照成員的分?jǐn)?shù)順序快速地進(jìn)行范圍查詢,而在哈希表中通過(guò)成員快速查找分?jǐn)?shù)。
這種底層實(shí)現(xiàn)結(jié)合了跳躍表和哈希表的優(yōu)點(diǎn),使得Redis有序集合能夠同時(shí)滿足有序性和高效性的需求。這種設(shè)計(jì)讓有序集合在插入、刪除、查詢和范圍操作等場(chǎng)景下都能表現(xiàn)出色。
如何使用
使用Redis的有序集合(Sorted Set)需要掌握一些基本命令和操作。以下是一些常見(jiàn)的有序集合操作示例:
1. 添加成員:
使用 ZADD
命令可以向有序集合中添加成員,同時(shí)指定成員的分?jǐn)?shù)。
ZADD myset 10 member1
ZADD myset 20 member2
2. 獲取成員分?jǐn)?shù):
使用 ZSCORE
命令可以獲取指定成員的分?jǐn)?shù)。
ZSCORE myset member1
3. 獲取成員排名:
使用 ZRANK
命令可以獲取指定成員在有序集合中的排名(從0開(kāi)始)。
ZRANK myset member2
4. 獲取分?jǐn)?shù)范圍內(nèi)的成員:
使用 ZRANGEBYSCORE
命令可以獲取指定分?jǐn)?shù)范圍內(nèi)的成員列表。
ZRANGEBYSCORE myset 15 25
5. 獲取排名范圍內(nèi)的成員:
使用 ZRANGE
命令可以獲取指定排名范圍內(nèi)的成員列表。
ZRANGE myset 0 2
6. 刪除成員:
使用 ZREM
命令可以從有序集合中刪除一個(gè)或多個(gè)成員。
ZREM myset member1
7. 獲取成員數(shù)量:
使用 ZCARD
命令可以獲取有序集合中成員的數(shù)量。
ZCARD myset
8. 集合操作:
并集:使用
ZUNIONSTORE
命令可以對(duì)多個(gè)有序集合進(jìn)行并集操作。交集:使用
ZINTERSTORE
命令可以對(duì)多個(gè)有序集合進(jìn)行交集操作。
ZUNIONSTORE destination_set 2 set1 set2 WEIGHTS 1 2
ZINTERSTORE destination_set 2 set1 set2 WEIGHTS 0.5 0.5
這只是有序集合的基本操作,你還可以使用其他命令進(jìn)行更復(fù)雜的操作,如獲取成員排名、計(jì)算分?jǐn)?shù)之差等。使用有序集合時(shí),要根據(jù)實(shí)際需求選擇合適的命令和操作,以充分利用其有序性和高效性。
需要注意的地方
在使用Redis的有序集合(Sorted Set)時(shí),有一些注意事項(xiàng)可以幫助你避免一些常見(jiàn)的問(wèn)題,以及優(yōu)化性能和數(shù)據(jù)管理。以下是一些需要注意的地方:
1. 成員的唯一性: 有序集合的成員是唯一的,重復(fù)的成員不會(huì)被插入。確保你向有序集合中添加的成員是唯一的,以免出現(xiàn)預(yù)期之外的數(shù)據(jù)情況。
2. 分?jǐn)?shù)的重復(fù)性: 雖然成員是唯一的,但是不同成員之間的分?jǐn)?shù)可以是重復(fù)的。這在一些場(chǎng)景中是正常的,但需要根據(jù)具體需求處理。
3. 數(shù)據(jù)量: 盡管有序集合可以處理大量的數(shù)據(jù),但仍需謹(jǐn)慎處理數(shù)據(jù)量較大的有序集合。大數(shù)據(jù)集合可能會(huì)影響性能和內(nèi)存使用。
4. 分?jǐn)?shù)范圍: 在進(jìn)行范圍查詢時(shí),確保分?jǐn)?shù)范圍是合理的。大范圍查詢可能會(huì)消耗較多的計(jì)算資源。
5. 數(shù)據(jù)結(jié)構(gòu)選擇: 有序集合適用于需要有序性的場(chǎng)景,但不適合用于僅僅需要存儲(chǔ)唯一性成員的情況。對(duì)于僅需要唯一性的數(shù)據(jù),使用集合(Set)數(shù)據(jù)類型更合適。
6. 集合操作的影響: 在執(zhí)行集合操作(并集、交集、差集)時(shí),考慮其對(duì)性能的影響。集合操作可能會(huì)消耗更多的計(jì)算資源,特別是在有大量成員的情況下。
7. 選擇適當(dāng)?shù)姆謹(jǐn)?shù)類型: 分?jǐn)?shù)可以是整數(shù)或浮點(diǎn)數(shù)。根據(jù)實(shí)際需求,選擇適合的分?jǐn)?shù)類型。
8. 性能和內(nèi)存優(yōu)化: 合理使用Redis的配置參數(shù),考慮分片、持久化、內(nèi)存管理等策略,以優(yōu)化性能和內(nèi)存使用。
9. 避免全量遍歷: 避免使用ZRANGE
等命令獲取所有成員,特別是在大數(shù)據(jù)集合中。考慮使用ZSCAN
進(jìn)行分頁(yè)式遍歷。
10. 持久化和備份: 在重要的生產(chǎn)環(huán)境中,考慮持久化和備份策略,以防止數(shù)據(jù)丟失。
11. 內(nèi)存占用: 有序集合會(huì)占用一定的內(nèi)存,要注意監(jiān)控和管理內(nèi)存使用,防止內(nèi)存溢出。
總之,使用Redis的有序集合時(shí),要根據(jù)實(shí)際需求合理規(guī)劃和優(yōu)化,以保證系統(tǒng)的性能和穩(wěn)定性。
五、哈希表(Hash)
適用場(chǎng)景
Redis的哈希表(Hash)是一種存儲(chǔ)鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),其中的鍵是唯一的,而值則可以是字符串、整數(shù)、浮點(diǎn)數(shù)等。哈希表適用于許多場(chǎng)景,特別是需要存儲(chǔ)和查詢多個(gè)字段的情況。以下是一些適用場(chǎng)景:
1. 存儲(chǔ)對(duì)象信息: 如果你需要存儲(chǔ)一個(gè)對(duì)象的多個(gè)字段信息,例如用戶信息(用戶名、年齡、郵箱等),可以使用哈希表來(lái)存儲(chǔ)每個(gè)用戶的字段信息。
2. 緩存數(shù)據(jù): 哈希表適用于緩存大量的鍵值對(duì)數(shù)據(jù),例如緩存數(shù)據(jù)庫(kù)查詢結(jié)果,以減少數(shù)據(jù)庫(kù)的訪問(wèn)頻率。
3. 存儲(chǔ)配置信息: 將配置信息存儲(chǔ)在哈希表中,可以方便地獲取和修改配置項(xiàng),而無(wú)需在內(nèi)存中存儲(chǔ)多個(gè)單獨(dú)的鍵。
4. 計(jì)數(shù)器: 可以使用哈希表來(lái)實(shí)現(xiàn)計(jì)數(shù)器功能,每個(gè)字段存儲(chǔ)一個(gè)計(jì)數(shù),比如網(wǎng)站的點(diǎn)贊數(shù)、閱讀數(shù)等。
5. 存儲(chǔ)多種屬性: 如果你需要為一組對(duì)象存儲(chǔ)多種屬性,例如商品的名稱、價(jià)格、庫(kù)存等,可以使用哈希表來(lái)存儲(chǔ)每個(gè)商品的多個(gè)屬性。
6. 聯(lián)合索引: 在關(guān)系型數(shù)據(jù)庫(kù)中,聯(lián)合索引常用于加速多字段的查詢。在Redis中,可以使用哈希表來(lái)存儲(chǔ)多個(gè)字段,并通過(guò)一個(gè)字段作為主鍵,實(shí)現(xiàn)類似的聯(lián)合索引效果。
7. 實(shí)時(shí)統(tǒng)計(jì): 哈希表可以用于實(shí)時(shí)統(tǒng)計(jì)信息,例如統(tǒng)計(jì)用戶每天的登錄次數(shù)、訂單數(shù)等。
8. 用戶會(huì)話: 可以使用哈希表來(lái)存儲(chǔ)用戶會(huì)話信息,每個(gè)字段存儲(chǔ)一個(gè)會(huì)話屬性,如用戶ID、登錄時(shí)間、過(guò)期時(shí)間等。
9. 圖數(shù)據(jù)結(jié)構(gòu): 如果需要實(shí)現(xiàn)圖數(shù)據(jù)結(jié)構(gòu),例如社交網(wǎng)絡(luò)關(guān)系圖,可以使用哈希表來(lái)表示節(jié)點(diǎn)和邊。
10. 多字段查詢: 哈希表適用于存儲(chǔ)多個(gè)字段,可以更快速地查詢和更新多個(gè)字段的值。
總之,哈希表適用于需要存儲(chǔ)多個(gè)字段信息的情況,可以在一次查詢中獲取和更新多個(gè)字段,從而提高了數(shù)據(jù)的訪問(wèn)效率。它在多種應(yīng)用場(chǎng)景中都能發(fā)揮作用,特別是需要存儲(chǔ)和操作多個(gè)屬性的數(shù)據(jù)。
底層實(shí)現(xiàn)是什么
Redis的哈希表(Hash)數(shù)據(jù)類型在底層的實(shí)現(xiàn)上是使用哈希表(Hash Table)來(lái)存儲(chǔ)鍵值對(duì)的。哈希表是一種非常高效的數(shù)據(jù)結(jié)構(gòu),它能夠在平均情況下以 O(1) 的時(shí)間復(fù)雜度進(jìn)行插入、刪除和查詢操作。下面是Redis哈希表底層實(shí)現(xiàn)的一些細(xì)節(jié):
1. 散列函數(shù)(Hash Function): 在哈希表中,鍵通過(guò)散列函數(shù)計(jì)算得到一個(gè)哈希值(hash),這個(gè)哈希值被用作數(shù)組(桶)的索引。Redis使用MurmurHash2等散列函數(shù)來(lái)均勻地將鍵分散到不同的桶中。
2. 桶數(shù)組: 哈希表底層維護(hù)了一個(gè)桶數(shù)組,每個(gè)桶中存儲(chǔ)了一個(gè)或多個(gè)鍵值對(duì)。這個(gè)數(shù)組的大小通常會(huì)動(dòng)態(tài)調(diào)整,以保證桶的填充因子不會(huì)過(guò)高。
3. 沖突處理: 由于不同的鍵可能會(huì)經(jīng)過(guò)散列函數(shù)映射到同一個(gè)桶中,這就產(chǎn)生了沖突。Redis使用鏈?zhǔn)浇鉀Q沖突的方法,每個(gè)桶中可以存儲(chǔ)一個(gè)鏈表,當(dāng)有多個(gè)鍵映射到同一個(gè)桶時(shí),它們會(huì)按照插入順序形成鏈表。
4. 動(dòng)態(tài)擴(kuò)容: 當(dāng)哈希表中的元素?cái)?shù)量逐漸增加時(shí),Redis會(huì)根據(jù)負(fù)載因子動(dòng)態(tài)擴(kuò)容桶數(shù)組,以保持桶的填充因子在一個(gè)合適的范圍內(nèi)。這可以保證插入、刪除和查詢操作的高效性。
5. 遷移: 在擴(kuò)容時(shí),Redis會(huì)將原有的鍵值對(duì)重新散列到新的桶數(shù)組中。這個(gè)過(guò)程稱為“遷移”,它會(huì)在后臺(tái)進(jìn)行,以免影響正常的讀寫(xiě)操作。
6. 哈希表的嵌套: 在Redis的源碼中,哈希表本身也可以被嵌套使用,這種嵌套的哈希表常常用于實(shí)現(xiàn)數(shù)據(jù)類型的復(fù)雜結(jié)構(gòu),例如用于存儲(chǔ)集合和有序集合等。
綜上所述,Redis的哈希表底層是通過(guò)散列函數(shù)、桶數(shù)組、鏈?zhǔn)浇鉀Q沖突等機(jī)制來(lái)實(shí)現(xiàn)的。這種設(shè)計(jì)使得Redis能夠高效地存儲(chǔ)和查詢鍵值對(duì)數(shù)據(jù),哈希表在Redis中扮演著非常重要的角色。
如何使用
使用Redis的哈希表(Hash)數(shù)據(jù)類型涉及一系列命令,這些命令可以幫助你對(duì)哈希表中的鍵值對(duì)進(jìn)行添加、查詢、刪除等操作。以下是一些常見(jiàn)的哈希表操作示例:
1. 添加鍵值對(duì):
使用 HSET
命令可以向哈希表中添加一個(gè)鍵值對(duì)。
HSET user:id123 name "John" age 30
2. 獲取單個(gè)鍵的值:
使用 HGET
命令可以獲取指定鍵的值。
HGET user:id123 name
3. 獲取多個(gè)鍵的值:
使用 HMGET
命令可以同時(shí)獲取多個(gè)鍵的值。
HMGET user:id123 name age
4. 獲取所有鍵值對(duì):
使用 HGETALL
命令可以獲取哈希表中所有的鍵值對(duì)。
HGETALL user:id123
5. 增加或更新鍵的值:
使用 HINCRBY
命令可以為鍵的值增加一個(gè)整數(shù)。如果鍵不存在,會(huì)創(chuàng)建一個(gè)新的鍵。
HINCRBY user:id123 age 1
6. 刪除鍵值對(duì):
使用 HDEL
命令可以從哈希表中刪除一個(gè)或多個(gè)鍵值對(duì)。
HDEL user:id123 age
7. 獲取所有鍵或值:
使用 HKEYS
命令可以獲取哈希表中所有的鍵,使用 HVALS
命令可以獲取哈希表中所有的值。
HKEYS user:id123
HVALS user:id123
8. 獲取鍵值對(duì)數(shù)量:
使用 HLEN
命令可以獲取哈希表中鍵值對(duì)的數(shù)量。
HLEN user:id123
9. 檢查鍵是否存在:
使用 HEXISTS
命令可以檢查指定鍵是否存在于哈希表中。
HEXISTS user:id123 name
這些只是哈希表的基本操作,你還可以使用其他命令來(lái)進(jìn)行更高級(jí)的操作,如迭代、批量添加、獲取字段數(shù)量等。在使用哈希表時(shí),要根據(jù)實(shí)際需求選擇合適的命令和操作,以充分利用其靈活性和高效性。
需要注意的地方
在使用Redis的哈希表(Hash)數(shù)據(jù)類型時(shí),有一些注意事項(xiàng)可以幫助你避免常見(jiàn)問(wèn)題,優(yōu)化性能,以及更好地管理數(shù)據(jù)。以下是一些需要注意的地方:
1. 鍵的命名: 選擇有意義的鍵名,以便更好地區(qū)分不同的哈希表。避免過(guò)長(zhǎng)或者冗余的鍵名,以減少內(nèi)存占用。
2. 數(shù)據(jù)量: 雖然Redis可以處理大量的數(shù)據(jù),但仍需謹(jǐn)慎處理大數(shù)據(jù)量的哈希表。大數(shù)據(jù)量可能會(huì)影響性能和內(nèi)存使用。
3. 單個(gè)哈希表的字段數(shù)量: 雖然Redis能夠高效地處理多個(gè)字段,但是如果單個(gè)哈希表中的字段數(shù)量非常多,可能會(huì)影響性能。如果需要存儲(chǔ)大量的字段,考慮拆分成多個(gè)哈希表或其他數(shù)據(jù)結(jié)構(gòu)。
4. 復(fù)雜度: 在哈希表中的字段數(shù)量不宜過(guò)多,以保持讀寫(xiě)操作的高效性。過(guò)多的字段可能會(huì)增加內(nèi)存消耗和操作復(fù)雜度。
5. 適用場(chǎng)景: 哈希表適用于存儲(chǔ)和查詢多個(gè)字段的情況。如果只需要存儲(chǔ)單一的值或者簡(jiǎn)單的數(shù)據(jù),考慮使用字符串(String)數(shù)據(jù)類型。
6. 批量操作: 如果需要一次操作多個(gè)鍵值對(duì),使用批量操作命令如 HMSET
,而不是多次使用單個(gè)鍵的操作命令。
7. 緩存失效: 設(shè)置適當(dāng)?shù)木彺媸r(shí)間,避免過(guò)期的鍵值對(duì)占用內(nèi)存。
8. 鍵值大?。?/strong> 如果哈希表中的字段值較大,考慮其對(duì)內(nèi)存的影響。大字段值可能會(huì)增加內(nèi)存占用。
9. 深度嵌套: 避免在哈希表中使用太多嵌套的鍵值對(duì),這可能會(huì)增加查找和維護(hù)的復(fù)雜度。
10. 數(shù)據(jù)持久化: 對(duì)于重要的數(shù)據(jù),考慮開(kāi)啟持久化以防止數(shù)據(jù)丟失。
11. 數(shù)據(jù)備份: 定期備份數(shù)據(jù),以防止意外數(shù)據(jù)丟失。
總之,使用哈希表時(shí),要根據(jù)實(shí)際需求合理規(guī)劃和優(yōu)化,以確保系統(tǒng)的性能和穩(wěn)定性??紤]數(shù)據(jù)模型、數(shù)據(jù)量、操作頻率等因素,以及根據(jù)需要選擇合適的Redis配置和命令來(lái)使用哈希表。