都知道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)圖片

image.png

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峦剔。

image.png

在 3.2 版本后档礁,sds 會(huì)根據(jù)字符串實(shí)際的長(zhǎng)度,選擇不同的數(shù)據(jù)結(jié)構(gòu)吝沫,以更好的提升內(nèi)存效率呻澜。當(dāng)前 sdshdr 結(jié)構(gòu)分為 5 種子類型,分別為 sdshdr5惨险、sdshdr8羹幸、sdshdr16、sdshdr32辫愉、sdshdr64栅受。其中 sdshdr5 只有 flags 和 buf 字段,其他幾種類型的 len 和 alloc 采用從 uint8_t 到 uint64_t 的不同類型恭朗,以節(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í)際的修改操作『停空間的申請(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)绩社。

這里雙向鏈表不展開討論了摔蓝。

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ù)類型的 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ì)于任何類型的數(shù)據(jù)查詢都需要經(jīng)過(guò)一次 dict 的查詢览芳,也就是 hash 的過(guò)程。通過(guò)這個(gè) dict 將 key 映射到對(duì)應(yīng)的數(shù)據(jù)類型的 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)焦!f準(zhǔn)焦R埠苋菀桌斫馕吖褪侵竿粋€(gè)哈希桶中的多個(gè)元素用一個(gè)鏈表來(lái)保存鞠呈,它們之間依次用指針連接。

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

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

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

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

3惯裕、釋放哈希表1的空間温数。

image.png

當(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開始,每執(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)。

image.png

鏈表加多級(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)甘磨,可以保存的類型是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í)椎侠,首先需要判斷下 新元素的類型和集合中現(xiàn)有元素類型的長(zhǎng)短第租,如果新元素是一個(gè)32位的數(shù)字,現(xiàn)有集合類型是16位的我纪,那么就需要將整數(shù)集合進(jìn)行升級(jí)慎宾,然后才能將新的元素加入進(jìn)來(lái)丐吓。

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

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

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)存的使用铃诬。

image.png

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)存和拷貝的開銷就會(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):

image.png

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ù)的類型以及長(zhǎng)度

3滴某、content : 節(jié)點(diǎn)的 content 屬性負(fù)責(zé)保存節(jié)點(diǎn)的值, 節(jié)點(diǎn)值可以是一個(gè)字節(jié)數(shù)組或者整數(shù)滋迈, 值的類型和長(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ì)讀寫是由一個(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模型

首先,Redis 是跑在單線程中的折汞,所有的操作都是按照順序線性執(zhí)行的倔幼,但是由于讀寫操作等待用戶輸入或輸出都是阻塞的,所以 I/O 操作在一般情況下往往不能直接返回爽待,這會(huì)導(dǎo)致某一文件的 I/O 阻塞導(dǎo)致整個(gè)進(jìn)程無(wú)法對(duì)其它客戶提供服務(wù)损同,而 I/O 多路復(fù)用 就是為了解決這個(gè)問(wèn)題而出現(xiàn)的。

Linux 中的IO多路復(fù)用機(jī)制是指一個(gè)線程處理多個(gè)IO流鸟款。多路是指網(wǎng)絡(luò)連接膏燃,復(fù)用指的是同一個(gè)線程。簡(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)求琅翻、寫入響應(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)生的事件類型我注,調(diào)用相應(yīng)的事件處理器。

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

image.png

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)行方案藕帜。

image.png

單線程處理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 在寫入和刪除的時(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)存上限后帐要,每次寫入都需要淘汰一些 key,也會(huì)造成耗時(shí)變長(zhǎng)弥奸;

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

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

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

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

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

為了提升網(wǎng)絡(luò) IO 的讀寫性能措译,Redis 在6.0推出了多線程别凤,同過(guò)多線程的 IO 來(lái)處理網(wǎng)絡(luò)請(qǐng)求。不過(guò)需要注意的是這里的多線程僅僅是針對(duì)客戶端的讀寫是并行的领虹,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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末获诈,一起剝皮案震驚了整個(gè)濱河市仍源,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舔涎,老刑警劉巖笼踩,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異亡嫌,居然都是意外死亡嚎于,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門挟冠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)于购,“玉大人,你說(shuō)我怎么就攤上這事圃郊〖劾裕” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵持舆,是天一觀的道長(zhǎng)色瘩。 經(jīng)常有香客問(wèn)我伪窖,道長(zhǎng),這世上最難降的妖魔是什么居兆? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任覆山,我火速辦了婚禮,結(jié)果婚禮上泥栖,老公的妹妹穿的比我還像新娘簇宽。我一直安慰自己,他們只是感情好吧享,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布魏割。 她就那樣靜靜地躺著,像睡著了一般钢颂。 火紅的嫁衣襯著肌膚如雪钞它。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天殊鞭,我揣著相機(jī)與錄音遭垛,去河邊找鬼。 笑死操灿,一個(gè)胖子當(dāng)著我的面吹牛锯仪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趾盐,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼庶喜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了救鲤?” 一聲冷哼從身側(cè)響起溃卡,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜒简,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漩仙,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搓茬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了队他。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卷仑。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖麸折,靈堂內(nèi)的尸體忽然破棺而出锡凝,到底是詐尸還是另有隱情,我是刑警寧澤垢啼,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布窜锯,位于F島的核電站张肾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏锚扎。R本人自食惡果不足惜吞瞪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驾孔。 院中可真熱鬧芍秆,春花似錦、人聲如沸翠勉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)对碌。三九已至荆虱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俭缓,已是汗流浹背克伊。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留华坦,地道東北人愿吹。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惜姐,于是被迫代替她去往敵國(guó)和親犁跪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1. 基于內(nèi)存實(shí)現(xiàn) 內(nèi)存讀寫比磁盤讀寫快很多,redis是基于內(nèi)存存儲(chǔ)實(shí)現(xiàn)的數(shù)據(jù)庫(kù)条舔,省去了磁盤i/o的消耗枫耳。mys...
    一笑奈何_abe4閱讀 318評(píng)論 0 0
  • Redis 持久化機(jī)制 緩存雪崩、緩存穿透孟抗、緩存預(yù)熱迁杨、緩存更新、緩存降級(jí)等問(wèn)題 熱點(diǎn)數(shù)據(jù)和冷數(shù)據(jù)是什么凄硼? Memc...
    安曉生閱讀 324評(píng)論 0 0
  • 天下武功铅协,無(wú)堅(jiān)不摧,唯快不破摊沉! 學(xué)習(xí)一個(gè)技術(shù)狐史,通常只接觸了零散的技術(shù)點(diǎn),沒(méi)有在腦海里建立一個(gè)完整的知識(shí)框架和架構(gòu)體...
    MageByte_青葉閱讀 345評(píng)論 0 2
  • 面試時(shí)候的常見(jiàn)問(wèn)題,可以從 Redis 不同數(shù)據(jù)類型底層的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)骏全、完全基于內(nèi)存苍柏、IO 多路復(fù)用網(wǎng)絡(luò)模型、線程...
    啦普拉斯逆變換閱讀 976評(píng)論 0 13
  • 這里總結(jié)一下我使用Redis的一些心得吟温,主要是參考了Redis設(shè)計(jì)與實(shí)現(xiàn) 和 Redis開發(fā)與運(yùn)維 這兩本書序仙。 一...
    wo883721閱讀 386評(píng)論 0 0