欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis 的查詢很快的原因解析及Redis 如何保證查詢的高效

 更新時(shí)間:2022年03月14日 14:45:54   作者:Zhan-LiZ  
由于redis是內(nèi)存數(shù)據(jù)庫(kù),歸功于它的數(shù)據(jù)結(jié)構(gòu)所以查詢效率非常高,今天通過(guò)本文給大家介紹下Redis 的查詢很快的原因解析及Redis 如何保證查詢的高效,感興趣的朋友一起看看吧

Redis 如何保證高效的查詢效率

為什么 Redis 比較快

Redis 中的查詢速度為什么那么快呢?

1、因?yàn)樗莾?nèi)存數(shù)據(jù)庫(kù);

2、歸功于它的數(shù)據(jù)結(jié)構(gòu);

3、Redis 中是單線程;

4、Redis 中使用了多路復(fù)用。

Redis 中的數(shù)據(jù)結(jié)構(gòu)

這里借用一張來(lái)自[Redis核心技術(shù)與實(shí)戰(zhàn)] Redis 中數(shù)據(jù)結(jié)構(gòu)和底層結(jié)構(gòu)的對(duì)應(yīng)圖片

1、簡(jiǎn)單動(dòng)態(tài)字符串

Redis 中并沒(méi)有使用 C 中 char 來(lái)表示字符串,而是引入了 簡(jiǎn)單動(dòng)態(tài)字符串(Simple Dynamic Strings,SDS)來(lái)存儲(chǔ)字符串和整型數(shù)據(jù)。那么 SDS 對(duì)比傳統(tǒng)的字符串有什么優(yōu)點(diǎn)呢?

先來(lái)看下 SDS 的結(jié)構(gòu)

struct sdshdr {
    // 記錄 buf 數(shù)組中已使用字節(jié)的數(shù)量
    // 等于 SDS 保存字符串的長(zhǎng)度,不包含'\0'
    long len;
    
    // 記錄buf數(shù)組中未使用字節(jié)的數(shù)量
    long free;
    
    // 字節(jié)數(shù)組,用于保存字符串
    char buf[];
};

舉個(gè)栗子:

使用 SDS 存儲(chǔ)了一個(gè)字符串 hello,對(duì)應(yīng)的 len 就是5,同時(shí)也申請(qǐng)了5個(gè)為未使用的空間,所以 free 就是5。

在 3.2 版本后,sds 會(huì)根據(jù)字符串實(shí)際的長(zhǎng)度,選擇不同的數(shù)據(jù)結(jié)構(gòu),以更好的提升內(nèi)存效率。當(dāng)前 sdshdr 結(jié)構(gòu)分為 5 種子類(lèi)型,分別為 sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64。其中 sdshdr5 只有 flags 和 buf 字段,其他幾種類(lèi)型的 len 和 alloc 采用從 uint8_t 到 uint64_t 的不同類(lèi)型,以節(jié)省內(nèi)存空間。

SDS 對(duì)比 c 字符串的優(yōu)勢(shì)
SDS可以常數(shù)級(jí)別獲取字符串的長(zhǎng)度

因?yàn)榻Y(jié)構(gòu)里面已經(jīng)記錄了字符串的長(zhǎng)度,所以獲取字符串的長(zhǎng)度復(fù)雜度為O(1),c 中字符串沒(méi)記錄長(zhǎng)度,需要遍歷整個(gè)長(zhǎng)度,復(fù)雜度為O(N)。

杜絕緩沖區(qū)溢出

如果在修改字符的時(shí)候,沒(méi)有分配足夠的內(nèi)存大小,就很容易造成緩存溢出,內(nèi)存越界。

strcat 函數(shù)常見(jiàn)的錯(cuò)誤就是數(shù)組越界,即兩個(gè)字符串連接后,長(zhǎng)度超過(guò)第一個(gè)字符串?dāng)?shù)組定義的長(zhǎng)度,導(dǎo)致越界。

SDS 中的空間分配策略可以杜絕這種情況,當(dāng)對(duì) SDS 進(jìn)行修改時(shí),API 會(huì)檢查 SDS 的空間是否滿足修改所需的要求,如果不滿足的話,API 會(huì)自動(dòng)將 SDS 的空間擴(kuò)展至執(zhí)行修改所需的大小,然后才執(zhí)行實(shí)際的修改操作??臻g的申請(qǐng)是自動(dòng)完成的,所以就避免了緩存溢出。

減少修改字符串時(shí)帶來(lái)的內(nèi)存分配次數(shù)

對(duì)于 C 字符串來(lái)說(shuō),如果修改字符串的長(zhǎng)度,都需要重新執(zhí)行內(nèi)存分配操作;但是對(duì)于 Redis 數(shù)據(jù)庫(kù)來(lái)說(shuō),如果頻繁執(zhí)行內(nèi)存分配/釋放操作,必然會(huì)對(duì)性能產(chǎn)生一定影響。為了避免 C 字符串的缺陷,SDS 采用了空間預(yù)分配和惰性空間釋放兩種優(yōu)化策略。

空間預(yù)分配

空間預(yù)分配用于優(yōu)化 SDS 的字符串增長(zhǎng)操作,當(dāng) SDS 的 api 對(duì) SDS 進(jìn)行修改,同時(shí)需要進(jìn)行空間擴(kuò)展的時(shí)候,除了會(huì)給 SDS 分配修改需要的空間,同時(shí)還會(huì)給 SDS 分配額外的未使用空間。

1、如果對(duì) SDS 修改之后,SDS 的長(zhǎng)度小于1MB,那么程序分配和 len 同樣大小的未使用空間,也就是這時(shí)候 SDS 中的 len 和 free 長(zhǎng)度相同;

2、如果對(duì) SDS 修改之后,SDS 的長(zhǎng)度大于等于1MB,那么程序分配1MB的未使用空間。

在對(duì) SDS 空間進(jìn)行擴(kuò)展的時(shí)候,首先會(huì)判斷未使用空間的大小是否能滿足要求,如果足夠,就不用在進(jìn)行內(nèi)存分配了,這樣能夠減少內(nèi)存的重新分配的次數(shù)。

惰性空間釋放

惰性空間釋放用于優(yōu)化 SDS 字符串的縮短操作,當(dāng) SDS 的 API 需要縮短 SDS 保護(hù)的字符串時(shí),程序并不會(huì)立即使用內(nèi)存重分配來(lái)回收縮短后多出來(lái)的內(nèi)存,而是使用 free 屬性將這些字節(jié)的數(shù)量記錄起來(lái),等待之后的重新使用。

二進(jìn)制安全

對(duì)于 C 字符串來(lái)說(shuō),字符串中不能包含空字符,否則最先被程序讀入的空字符串被誤認(rèn)為是字符串結(jié)尾,這使得 C 字符串只能保存文本數(shù)據(jù),而不能保存圖片、音視頻等二進(jìn)制文件。對(duì)于 SDS 來(lái)說(shuō),所有 SDS 都會(huì)以處理二進(jìn)制的方式來(lái)處理 SDS 保存在 buf 數(shù)組中的內(nèi)容,程序不會(huì)對(duì)里面的內(nèi)容做任何限制。

兼容部分C字符串函數(shù)

SDS 末尾設(shè)置空字符的操作使得它可以和部分 C 字符串函數(shù)兼容。

2、鏈表

鏈表是一種很常見(jiàn)的數(shù)據(jù)結(jié)構(gòu),在很多高級(jí)語(yǔ)言中都能看到,Redis 中的 list 就用到了雙向鏈表,當(dāng)一個(gè)列表鍵包含了數(shù)量比較多的元素,或者是列表中包含的元素都是比較長(zhǎng)的字符串的時(shí),Redis 就會(huì)使用鏈表作為 list 的底層實(shí)現(xiàn)。

這里雙向鏈表不展開(kāi)討論了。

3、字典

redisDb 主要包括 2 個(gè)核心 dict 字典、3 個(gè)非核心 dict 字典、dbID 和其他輔助屬性。2 個(gè)核心 dict 包括一個(gè) dict 主字典和一個(gè) expires 過(guò)期字典。主 dict 字典用來(lái)存儲(chǔ)當(dāng)前 DB 中的所有數(shù)據(jù),它將 key 和各種數(shù)據(jù)類(lèi)型的 value 關(guān)聯(lián)起來(lái),該 dict 也稱 key space。過(guò)期字典用來(lái)存儲(chǔ)過(guò)期時(shí)間 key,存的是 key 與過(guò)期時(shí)間的映射。日常的數(shù)據(jù)存儲(chǔ)和訪問(wèn)基本都會(huì)訪問(wèn)到 redisDb 中的這兩個(gè) dict。

所以對(duì)于任何類(lèi)型的數(shù)據(jù)查詢都需要經(jīng)過(guò)一次 dict 的查詢,也就是 hash 的過(guò)程。通過(guò)這個(gè) dict 將 key 映射到對(duì)應(yīng)的數(shù)據(jù)類(lèi)型的 value 中。

3 個(gè)非核心 dict 包括一個(gè)字段名叫 blocking_keys 的阻塞 dict,一個(gè)字段名叫 ready_keys 的解除阻塞 dict,還有一個(gè)是字段名叫 watched_keys 的 watch 監(jiān)控 dict。

Redis 的字典使用哈希表作為底層實(shí)現(xiàn),一個(gè)哈希表里面可以有多個(gè)哈希節(jié)點(diǎn),每個(gè)哈希表節(jié)點(diǎn)就保存了字典中的一個(gè)鍵值對(duì)。

使用哈希表就難免遇到哈希碰撞,兩個(gè)key的哈希值和哈希桶計(jì)算對(duì)應(yīng)關(guān)系時(shí),正好落在了同一個(gè)哈希桶中。

Redis 解決哈希沖突的方式,就是鏈?zhǔn)焦?。鏈?zhǔn)焦R埠苋菀桌斫猓褪侵竿粋€(gè)哈希桶中的多個(gè)元素用一個(gè)鏈表來(lái)保存,它們之間依次用指針連接。

隨著寫(xiě)入的消息越來(lái)越多,哈希沖突的幾率也隨之升高,這時(shí)候就需要對(duì)哈希表進(jìn)行擴(kuò)容,Redis 中的擴(kuò)容使用的是漸進(jìn)式rehash。

其實(shí),為了使 rehash 操作更高效,Redis 默認(rèn)使用了兩個(gè)全局哈希表:哈希表1和哈希表2。一開(kāi)始,當(dāng)你剛插入數(shù)據(jù)時(shí),默認(rèn)使用哈希表1,此時(shí)的哈希表2并沒(méi)有被分配空間。隨著數(shù)據(jù)逐步增多,Redis 開(kāi)始執(zhí)行 rehash,這個(gè)過(guò)程分為三步:

1、給哈希表2分配更大的空間,例如是當(dāng)前哈希表1大小的兩倍;

2、把哈希表1中的數(shù)據(jù)重新映射并拷貝到哈希表2中;

3、釋放哈希表1的空間。

當(dāng)然數(shù)據(jù)很大的話,一次遷移所有的數(shù)據(jù),顯然是不合理的,會(huì)造成Redis線程阻塞,無(wú)法服務(wù)其他請(qǐng)求。這里 Redis 使用的是漸進(jìn)式 rehash。

在 rehash 期間,每次執(zhí)行添加,刪除,查找或者更新操作時(shí),除了對(duì)命令本身的處理,還會(huì)順帶將哈希表1中的數(shù)據(jù)拷貝到哈希表2中。從索引0開(kāi)始,每執(zhí)行一次操作命令,拷貝一個(gè)索引位置的數(shù)據(jù)。

在進(jìn)行 rehash 期間,刪除,查找或者更新操作都會(huì)在兩個(gè)哈希表中執(zhí)行,添加操作就直接添加到哈希表2中了。查找會(huì)把兩個(gè)哈希表都找一遍,直到找到或者兩個(gè)都找不到。

4、跳表

其中 Redis 中的有序集合就是使用跳表來(lái)實(shí)現(xiàn)的

對(duì)于一個(gè)單鏈表來(lái)講,即便鏈表中存儲(chǔ)的數(shù)據(jù)是有序的,如果我們要想在其中查找某個(gè)數(shù)據(jù),也只能從頭到尾遍歷鏈表。這樣查找效率就會(huì)很低,時(shí)間復(fù)雜度會(huì)很高,是O(n)。

鏈表加多級(jí)索引的結(jié)構(gòu),就是跳表,跳表查詢的時(shí)間復(fù)雜度是O(logn)。通過(guò)在每個(gè)節(jié)點(diǎn)中維持多個(gè)指向其他節(jié)點(diǎn)的指針,從而實(shí)現(xiàn)快速訪問(wèn)的節(jié)點(diǎn)的目的。

5、整數(shù)數(shù)組

整數(shù)集合(intset)是集合鍵的底層實(shí)現(xiàn)之一,當(dāng)一個(gè)集合只包含整數(shù)值元素,并且這個(gè)集合的元素?cái)?shù)量不多時(shí),Redis 就會(huì)使用整數(shù)集合作為集合鍵的底層實(shí)現(xiàn)。是 Redis 用戶保存整數(shù)值的集合抽象數(shù)據(jù)結(jié)構(gòu),可以保存的類(lèi)型是int16_t,int32_t,int64_t的整數(shù)值,并且保證集合中不會(huì)出現(xiàn)重復(fù)的元素。

typedef struct intset{
    // 編碼方法,指定當(dāng)前存儲(chǔ)的是 16 位,32 位,還是 64 位的整數(shù)
    int32 encoding;
    // 集合中的元素?cái)?shù)量
    int32 length;
    // 保存元素的數(shù)組
    int<T> contents;
}

這樣看,這個(gè)集合倒也沒(méi)有什么特殊的點(diǎn)。這時(shí)候需要來(lái)看下整數(shù)集合的升級(jí)。

每當(dāng)一個(gè)整數(shù)被添加到整數(shù)集合時(shí),首先需要判斷下 新元素的類(lèi)型和集合中現(xiàn)有元素類(lèi)型的長(zhǎng)短,如果新元素是一個(gè)32位的數(shù)字,現(xiàn)有集合類(lèi)型是16位的,那么就需要將整數(shù)集合進(jìn)行升級(jí),然后才能將新的元素加入進(jìn)來(lái)。

這樣做的優(yōu)點(diǎn):

1、提升整數(shù)集合的靈活性,可以隨意的添加整數(shù),而不用關(guān)心整數(shù)的類(lèi)型;

2、可以盡可能的節(jié)約內(nèi)存。

缺點(diǎn):

不支持降級(jí),一旦對(duì)數(shù)組進(jìn)行了升級(jí),就會(huì)一直保持升級(jí)后的狀態(tài)。

6、壓縮列表

壓縮列表(ziplist)是列表鍵和哈希鍵的底層實(shí)現(xiàn)之一。當(dāng)一個(gè)元素只包含少量的列表項(xiàng),并且每個(gè)列表項(xiàng)是小整數(shù)值或者是長(zhǎng)度比較短的字符串,redis 就會(huì)使用壓縮列表來(lái)作為列表鍵的底層實(shí)現(xiàn)。

壓縮列表(ziplist)的目的是為了節(jié)約內(nèi)存,通過(guò)一片連續(xù)的內(nèi)存空間來(lái)存儲(chǔ)數(shù)據(jù)。這樣看起來(lái)好像和數(shù)組很像,數(shù)組中每個(gè)節(jié)點(diǎn)的內(nèi)存大小是一樣的,對(duì)于壓縮列表,每個(gè)節(jié)點(diǎn)的內(nèi)存大小是不同的,每個(gè)節(jié)點(diǎn)可以保存字節(jié)數(shù)組或一個(gè)整數(shù)值。通過(guò)可變的節(jié)點(diǎn)內(nèi)存大小,來(lái)節(jié)約內(nèi)存的使用。

ziplist 的結(jié)構(gòu):

1、zlbytes : 是壓縮列表所占用的總內(nèi)存字節(jié)數(shù);

2、Zltail : 尾節(jié)點(diǎn)到起始位置的字節(jié)數(shù),(目的是為了直接定位到尾節(jié)點(diǎn),方便反向查詢);

3、Zllen : 總共包含的節(jié)點(diǎn)/內(nèi)存塊數(shù),也就是壓縮列表的節(jié)點(diǎn)數(shù)量;

4、Entry : 是 ziplist 保存的各個(gè)數(shù)據(jù)節(jié)點(diǎn),這些數(shù)據(jù)點(diǎn)長(zhǎng)度隨意;

5、Zlend : 是一個(gè)魔數(shù) 255,用來(lái)標(biāo)記壓縮列表的結(jié)束。

由于 ziplist 是連續(xù)緊湊存儲(chǔ),沒(méi)有冗余空間,所以插入新的元素需要 realloc 擴(kuò)展內(nèi)存,所以如果 ziplist 占用空間太大,realloc 重新分配內(nèi)存和拷貝的開(kāi)銷(xiāo)就會(huì)很大,所以 ziplist 不適合存儲(chǔ)過(guò)多元素,也不適合存儲(chǔ)過(guò)大的字符串。

因此只有在元素?cái)?shù)和 value 數(shù)都不大的時(shí)候,ziplist 才作為 hash 和 zset 的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。其中 hash 使用 ziplist 作為內(nèi)部數(shù)據(jù)結(jié)構(gòu)的限制時(shí),元素?cái)?shù)默認(rèn)不超過(guò) 512 個(gè),value 值默認(rèn)不超過(guò) 64 字節(jié)??梢酝ㄟ^(guò)修改配置來(lái)調(diào)整 hash_max_ziplist_entries 、hash_max_ziplist_value 這兩個(gè)閥值的大小。

zset 有序集合,使用 ziplist 作為內(nèi)部數(shù)據(jù)結(jié)構(gòu)的限制元素?cái)?shù)默認(rèn)不超過(guò) 128 個(gè),value 值默認(rèn)不超過(guò) 64 字節(jié)??梢酝ㄟ^(guò)修改配置來(lái)調(diào)整 zset_max_ziplist_entries 和 zset_max_ziplist_value 這兩個(gè)閥值的大小。

在壓縮列表中,如果我們要查找定位第一個(gè)元素和最后一個(gè)元素,可以通過(guò)表頭三個(gè)字段的長(zhǎng)度直接定位,復(fù)雜度是O(1)。而查找其他元素時(shí),就沒(méi)有這么高效了,只能逐個(gè)查找,此時(shí)的復(fù)雜度就是O(N)了。

連鎖更新

壓縮列表 ziplist 的存儲(chǔ)節(jié)點(diǎn) Entry 數(shù)據(jù)節(jié)點(diǎn)的結(jié)構(gòu):

1、previous_entry_length : 記錄了前一個(gè)節(jié)點(diǎn)的長(zhǎng)度

如果前一節(jié)點(diǎn)的長(zhǎng)度小于 254 字節(jié), 那么 previous_entry_length 屬性的長(zhǎng)度為 1 字節(jié): 前一節(jié)點(diǎn)的長(zhǎng)度就保存在這一個(gè)字節(jié)里面;

如果前一節(jié)點(diǎn)的長(zhǎng)度大于等于 254 字節(jié), 那么 previous_entry_length 屬性的長(zhǎng)度為 5 字節(jié): 其中屬性的第一字節(jié)會(huì)被設(shè)置為 0xFE (十進(jìn)制值 254), 而之后的四個(gè)字節(jié)則用于保存前一節(jié)點(diǎn)的長(zhǎng)度。

2、encoding : 記錄了節(jié)點(diǎn)的 content 屬性所保存數(shù)據(jù)的類(lèi)型以及長(zhǎng)度

3、content : 節(jié)點(diǎn)的 content 屬性負(fù)責(zé)保存節(jié)點(diǎn)的值, 節(jié)點(diǎn)值可以是一個(gè)字節(jié)數(shù)組或者整數(shù), 值的類(lèi)型和長(zhǎng)度由節(jié)點(diǎn)的 encoding 屬性決定。

舉個(gè)栗子:

如果壓縮列表中每個(gè)節(jié)點(diǎn)的長(zhǎng)度都是250,因?yàn)槭切∮?54,所以每個(gè)節(jié)點(diǎn)中的 previous_entry_length 長(zhǎng)度1字節(jié)就能夠保存了。

這時(shí)候,在頭部節(jié)點(diǎn)插入了一個(gè)新的元素 entryNew,然后長(zhǎng)度是大于254,那么后面的節(jié)點(diǎn)中 entry1 的 previous_entry_length 長(zhǎng)度為1字節(jié),就不能保存了,需要擴(kuò)容成5字節(jié),然后 entry1 節(jié)點(diǎn)進(jìn)行擴(kuò)容了,變成了254,所以后面的節(jié)點(diǎn)也就需要一次擴(kuò)容,這就引發(fā)一連串的擴(kuò)容。也就是連鎖更新。

為什么單線程還能很快

Redis 是單線程,主要是指 Redis 的網(wǎng)絡(luò)IO和鍵值對(duì)讀寫(xiě)是由一個(gè)線程來(lái)完成的,這也是 Redis 對(duì)外提供鍵值存儲(chǔ)服務(wù)的主要流程。

多線程必然會(huì)面臨對(duì)于共享資源的訪問(wèn),這時(shí)候通常的做法就是加鎖,雖然是多線程,這時(shí)候就會(huì)變成串行的訪問(wèn)。也就是多線程編程模式會(huì)面臨的共享資源的并發(fā)訪問(wèn)控制問(wèn)題。

同時(shí)多線程也會(huì)引入同步原語(yǔ)來(lái)保護(hù)共享資源的并發(fā)訪問(wèn),代碼的可維護(hù)性和易讀性將會(huì)下降。

基于多路復(fù)用的高性能I/O模型

Linux 中的IO多路復(fù)用機(jī)制是指一個(gè)線程處理多個(gè)IO流。簡(jiǎn)單來(lái)說(shuō),在 Redis 只運(yùn)行單線程的情況下,該機(jī)制允許內(nèi)核中,同時(shí)存在多個(gè)監(jiān)聽(tīng)套接字和已連接套接字。內(nèi)核會(huì)一直監(jiān)聽(tīng)這些套接字上的連接請(qǐng)求或數(shù)據(jù)請(qǐng)求。一旦有請(qǐng)求到達(dá),就會(huì)交給 Redis 線程處理,這就實(shí)現(xiàn)了一個(gè) Redis 線程處理多個(gè)IO流的效果。

文件事件是對(duì)連接 socket 操作的一個(gè)抽象。當(dāng)端口監(jiān)聽(tīng) socket 準(zhǔn)備 accept 新連接,或者連接 socket 準(zhǔn)備好讀取請(qǐng)求、寫(xiě)入響應(yīng)、關(guān)閉時(shí),就會(huì)產(chǎn)生一個(gè)文件事件。IO 多路復(fù)用程序負(fù)責(zé)同時(shí)監(jiān)聽(tīng)多個(gè) socket,當(dāng)這些 socket 產(chǎn)生文件事件時(shí),就會(huì)觸發(fā)事件通知,文件分派器就會(huì)感知并獲取到這些事件。

雖然多個(gè)文件事件可能會(huì)并發(fā)出現(xiàn),但 IO 多路復(fù)用程序總會(huì)將所有產(chǎn)生事件的 socket 放入一個(gè)隊(duì)列中,通過(guò)這個(gè)隊(duì)列,有序的把這些文件事件通知給文件分派器。

文件事件分派器接收 I/O 多路復(fù)用程序傳來(lái)的套接字,并根據(jù)套接字產(chǎn)生的事件類(lèi)型,調(diào)用相應(yīng)的事件處理器。

服務(wù)器會(huì)為執(zhí)行不同任務(wù)的套接字關(guān)聯(lián)不同的事件處理器,這些處理器是一個(gè)個(gè)的函數(shù),他們定義了這個(gè)事件發(fā)生時(shí),服務(wù)器應(yīng)該執(zhí)行的動(dòng)作。

Redis 封裝了 4 種多路復(fù)用程序,每種封裝實(shí)現(xiàn)都提供了相同的 API 實(shí)現(xiàn)。編譯時(shí),會(huì)按照性能和系統(tǒng)平臺(tái),選擇最佳的 IO 多路復(fù)用函數(shù)作為底層實(shí)現(xiàn),選擇順序是,首先嘗試選擇 Solaries 中的 evport,如果沒(méi)有,就嘗試選擇 Linux 中的 epoll,否則就選擇大多 UNIX 系統(tǒng)都支持的 kqueue,這 3 個(gè)多路復(fù)用函數(shù)都直接使用系統(tǒng)內(nèi)核內(nèi)部的結(jié)構(gòu),可以服務(wù)數(shù)十萬(wàn)的文件描述符。

如果當(dāng)前編譯環(huán)境沒(méi)有上述函數(shù),就會(huì)選擇 select 作為底層實(shí)現(xiàn)方案。select 方案的性能較差,事件發(fā)生時(shí),會(huì)掃描全部監(jiān)聽(tīng)的描述符,事件復(fù)雜度是 O(n),并且只能同時(shí)服務(wù)有限個(gè)文件描述符,32 位機(jī)默認(rèn)是 1024 個(gè),64 位機(jī)默認(rèn)是 2048 個(gè),所以一般情況下,并不會(huì)選擇 select 作為線上運(yùn)行方案。

單線程處理IO請(qǐng)求性能瓶頸

1、后臺(tái) Redis 通過(guò)監(jiān)聽(tīng)處理事件隊(duì)列中的消息,來(lái)單線程的處理命令,如果一個(gè)命令的執(zhí)行時(shí)間很久,就會(huì)影響整個(gè) server 的性能;

耗時(shí)的操作命令有下面幾種:

1、操作 bigkey:bigkey 在寫(xiě)入和刪除的時(shí)候,需要的時(shí)間都會(huì)很長(zhǎng);

2、使用復(fù)雜度過(guò)高的命令;

3、大量 key 集中過(guò)期:Redis 的過(guò)期機(jī)制也是在主線程中執(zhí)行的,大量 key 集中過(guò)期會(huì)導(dǎo)致處理一個(gè)請(qǐng)求時(shí),耗時(shí)都在刪除過(guò)期 key,耗時(shí)變長(zhǎng);

4、淘汰策略:淘汰策略也是在主線程執(zhí)行的,當(dāng)內(nèi)存超過(guò) Redis 內(nèi)存上限后,每次寫(xiě)入都需要淘汰一些 key,也會(huì)造成耗時(shí)變長(zhǎng);

5、AO F刷盤(pán)開(kāi)啟 always 機(jī)制:每次寫(xiě)入都需要把這個(gè)操作刷到磁盤(pán),寫(xiě)磁盤(pán)的速度遠(yuǎn)比寫(xiě)內(nèi)存慢,會(huì)拖慢 Redis 的性能;

6、主從全量同步生成 RDB:雖然采用 fork 子進(jìn)程生成數(shù)據(jù)快照,但 fork 這一瞬間也是會(huì)阻塞整個(gè)線程的,實(shí)例越大,阻塞時(shí)間越久;

上面的這幾種問(wèn)題,我們?cè)趯?xiě)業(yè)務(wù)的時(shí)候需要去避免,對(duì)于 bigkey,Redis 在4.0推出了 lazy-free 機(jī)制,把 bigkey 釋放內(nèi)存的耗時(shí)操作放在了異步線程中執(zhí)行,降低對(duì)主線程的影響。

2、并發(fā)量非常大時(shí),單線程讀寫(xiě)客戶端 IO 數(shù)據(jù)存在性能瓶頸

使用 Redis 時(shí),幾乎不存在 CPU 成為瓶頸的情況, Redis 主要受限于內(nèi)存和網(wǎng)絡(luò)。隨著硬件水平的提升,Redis 中的性能慢慢主要出現(xiàn)在網(wǎng)絡(luò) IO 的讀寫(xiě)上。雖然采用 IO 多路復(fù)用機(jī)制,但是讀寫(xiě)客戶端數(shù)據(jù)依舊是同步IO,只能單線程依次讀取客戶端的數(shù)據(jù),無(wú)法利用到CPU多核。

為了提升網(wǎng)絡(luò) IO 的讀寫(xiě)性能,Redis 在6.0推出了多線程,同過(guò)多線程的 IO 來(lái)處理網(wǎng)絡(luò)請(qǐng)求。不過(guò)需要注意的是這里的多線程僅僅是針對(duì)客戶端的讀寫(xiě)是并行的,Redis 處理事件隊(duì)列中的命令,還是單線程處理的。

總結(jié)

Redis 使用單線程,來(lái)避免共享資源的競(jìng)爭(zhēng),使用多路復(fù)用實(shí)現(xiàn)高性能的I/O,它是內(nèi)存數(shù)據(jù)庫(kù),所有操作都在內(nèi)存上完成,使用哈希表,跳表等一系列高效的數(shù)據(jù)結(jié)構(gòu),這些特性保證了 Redis 的高性能。

參考

【Redis核心技術(shù)與實(shí)戰(zhàn)】https://time.geekbang.org/column/intro/100056701
【Redis6.0為什么引入多線程?】https://juejin.cn/post/7004683161695158309
【Redis設(shè)計(jì)與實(shí)現(xiàn)】https://book.douban.com/subject/25900156/
【redis---sds(簡(jiǎn)單動(dòng)態(tài)字符串)詳解】https://blog.csdn.net/u010765526/article/details/89065607
【為什么 Redis 的查詢很快】https://boilingfrog.github.io/2022/01/07/為什么redis的查詢比較快/
【redis知識(shí)點(diǎn)】https://github.com/boilingfrog/Go-POINT/tree/master/redis

到此這篇關(guān)于Redis 的查詢很快的原因解析及Redis 如何保證查詢的高效的文章就介紹到這了,更多相關(guān)redis查詢高效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入淺析Redis 集群伸縮原理

    深入淺析Redis 集群伸縮原理

    Redis 集群提供了靈活的節(jié)點(diǎn)擴(kuò)容和收縮方案。在不影響集群對(duì)外服務(wù)的情況下,可以為集群添加節(jié)點(diǎn)進(jìn)行擴(kuò)容,也可以下線部分節(jié)點(diǎn)進(jìn)行縮容,接下來(lái)通過(guò)本文給大家分享Redis 集群伸縮原理,感興趣的朋友一起看看吧
    2021-05-05
  • Redis 分片集群的實(shí)現(xiàn)

    Redis 分片集群的實(shí)現(xiàn)

    本文主要介紹了Redis 分片集群的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • Redis學(xué)習(xí)教程之命令的執(zhí)行過(guò)程詳解

    Redis學(xué)習(xí)教程之命令的執(zhí)行過(guò)程詳解

    這篇文章主要給大家介紹了關(guān)于Redis學(xué)習(xí)教程之命令的執(zhí)行過(guò)程的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-03-03
  • Redis 執(zhí)行性能測(cè)試

    Redis 執(zhí)行性能測(cè)試

    這篇文章主要介紹了Redis 執(zhí)行性能測(cè)試的方法,文中講解非常細(xì)致,幫助大家更好的理解和學(xué)習(xí)redis,感興趣的朋友可以了解下
    2020-08-08
  • Linux下Redis安裝使用教程

    Linux下Redis安裝使用教程

    這篇文章主要為大家詳細(xì)介紹了Linux下Redis安裝使用教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • Redis服務(wù)器的啟動(dòng)過(guò)程分析

    Redis服務(wù)器的啟動(dòng)過(guò)程分析

    這篇文章主要介紹了Redis服務(wù)器的啟動(dòng)過(guò)程分析,本文講解了初始化Redis服務(wù)器全局配置、加載配置文件、初始化服務(wù)器、加載數(shù)據(jù)、開(kāi)始網(wǎng)絡(luò)監(jiān)聽(tīng)等內(nèi)容,需要的朋友可以參考下
    2015-04-04
  • redis過(guò)期回調(diào)功能實(shí)現(xiàn)示例

    redis過(guò)期回調(diào)功能實(shí)現(xiàn)示例

    Redis提供了一種過(guò)期回調(diào)的機(jī)制,可以在某個(gè)鍵過(guò)期時(shí)觸發(fā)一個(gè)回調(diào)函數(shù),本文就來(lái)介紹一下redis過(guò)期回調(diào)功能實(shí)現(xiàn)示例,感興趣的可以了解一下
    2023-09-09
  • 詳解Redis實(shí)現(xiàn)限流的三種方式

    詳解Redis實(shí)現(xiàn)限流的三種方式

    這篇文章主要介紹了詳解Redis實(shí)現(xiàn)限流的三種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • 淺析Redis如何保證數(shù)據(jù)不丟失

    淺析Redis如何保證數(shù)據(jù)不丟失

    Redis是一種Nosql類(lèi)型的數(shù)據(jù)存儲(chǔ),全稱Remote?Dictionary?Server,也就是遠(yuǎn)程字典服務(wù)器,本文主要來(lái)和大家討論一下Redis如何保證數(shù)據(jù)不丟失,需要的可以參考下
    2024-02-02
  • Redis?中使用?list,streams,pub/sub?幾種方式實(shí)現(xiàn)消息隊(duì)列的問(wèn)題

    Redis?中使用?list,streams,pub/sub?幾種方式實(shí)現(xiàn)消息隊(duì)列的問(wèn)題

    這篇文章主要介紹了Redis?中使用?list,streams,pub/sub?幾種方式實(shí)現(xiàn)消息隊(duì)列,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03

最新評(píng)論