Redis 的查詢很快的原因解析及Redis 如何保證查詢的高效
Redis 如何保證高效的查詢效率
為什么 Redis 比較快
Redis 中的查詢速度為什么那么快呢?
1、因?yàn)樗莾?nèi)存數(shù)據(jù)庫;
2、歸功于它的數(shù)據(jù)結(jié)構(gòu);
3、Redis 中是單線程;
4、Redis 中使用了多路復(fù)用。
Redis 中的數(shù)據(jù)結(jié)構(gòu)
這里借用一張來自[Redis核心技術(shù)與實(shí)戰(zhàn)] Redis 中數(shù)據(jù)結(jié)構(gòu)和底層結(jié)構(gòu)的對(duì)應(yīng)圖片
1、簡單動(dòng)態(tài)字符串
Redis 中并沒有使用 C 中 char 來表示字符串,而是引入了 簡單動(dòng)態(tài)字符串(Simple Dynamic Strings,SDS)來存儲(chǔ)字符串和整型數(shù)據(jù)。那么 SDS 對(duì)比傳統(tǒng)的字符串有什么優(yōu)點(diǎn)呢?
先來看下 SDS 的結(jié)構(gòu)
struct sdshdr { // 記錄 buf 數(shù)組中已使用字節(jié)的數(shù)量 // 等于 SDS 保存字符串的長度,不包含'\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í)際的長度,選擇不同的數(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í)別獲取字符串的長度
因?yàn)榻Y(jié)構(gòu)里面已經(jīng)記錄了字符串的長度,所以獲取字符串的長度復(fù)雜度為O(1),c 中字符串沒記錄長度,需要遍歷整個(gè)長度,復(fù)雜度為O(N)。
杜絕緩沖區(qū)溢出
如果在修改字符的時(shí)候,沒有分配足夠的內(nèi)存大小,就很容易造成緩存溢出,內(nèi)存越界。
strcat 函數(shù)常見的錯(cuò)誤就是數(shù)組越界,即兩個(gè)字符串連接后,長度超過第一個(gè)字符串?dāng)?shù)組定義的長度,導(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í)帶來的內(nèi)存分配次數(shù)
對(duì)于 C 字符串來說,如果修改字符串的長度,都需要重新執(zhí)行內(nèi)存分配操作;但是對(duì)于 Redis 數(shù)據(jù)庫來說,如果頻繁執(zhí)行內(nèi)存分配/釋放操作,必然會(huì)對(duì)性能產(chǎn)生一定影響。為了避免 C 字符串的缺陷,SDS 采用了空間預(yù)分配和惰性空間釋放兩種優(yōu)化策略。
空間預(yù)分配
空間預(yù)分配用于優(yōu)化 SDS 的字符串增長操作,當(dāng) SDS 的 api 對(duì) SDS 進(jìn)行修改,同時(shí)需要進(jìn)行空間擴(kuò)展的時(shí)候,除了會(huì)給 SDS 分配修改需要的空間,同時(shí)還會(huì)給 SDS 分配額外的未使用空間。
1、如果對(duì) SDS 修改之后,SDS 的長度小于1MB
,那么程序分配和 len 同樣大小的未使用空間,也就是這時(shí)候 SDS 中的 len 和 free 長度相同;
2、如果對(duì) SDS 修改之后,SDS 的長度大于等于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)存重分配來回收縮短后多出來的內(nèi)存,而是使用 free 屬性將這些字節(jié)的數(shù)量記錄起來,等待之后的重新使用。
二進(jìn)制安全
對(duì)于 C 字符串來說,字符串中不能包含空字符,否則最先被程序讀入的空字符串被誤認(rèn)為是字符串結(jié)尾,這使得 C 字符串只能保存文本數(shù)據(jù),而不能保存圖片、音視頻等二進(jìn)制文件。對(duì)于 SDS 來說,所有 SDS 都會(huì)以處理二進(jìn)制的方式來處理 SDS 保存在 buf 數(shù)組中的內(nèi)容,程序不會(huì)對(duì)里面的內(nèi)容做任何限制。
兼容部分C字符串函數(shù)
SDS 末尾設(shè)置空字符的操作使得它可以和部分 C 字符串函數(shù)兼容。
2、鏈表
鏈表是一種很常見的數(shù)據(jù)結(jié)構(gòu),在很多高級(jí)語言中都能看到,Redis 中的 list 就用到了雙向鏈表,當(dāng)一個(gè)列表鍵包含了數(shù)量比較多的元素,或者是列表中包含的元素都是比較長的字符串的時(shí),Redis 就會(huì)使用鏈表作為 list 的底層實(shí)現(xiàn)。
這里雙向鏈表不展開討論了。
3、字典
redisDb 主要包括 2 個(gè)核心 dict 字典、3 個(gè)非核心 dict 字典、dbID 和其他輔助屬性。2 個(gè)核心 dict 包括一個(gè) dict 主字典和一個(gè) expires 過期字典。主 dict 字典用來存儲(chǔ)當(dāng)前 DB 中的所有數(shù)據(jù),它將 key 和各種數(shù)據(jù)類型的 value 關(guān)聯(lián)起來,該 dict 也稱 key space。過期字典用來存儲(chǔ)過期時(shí)間 key,存的是 key 與過期時(shí)間的映射。日常的數(shù)據(jù)存儲(chǔ)和訪問基本都會(huì)訪問到 redisDb 中的這兩個(gè) dict。
所以對(duì)于任何類型的數(shù)據(jù)查詢都需要經(jīng)過一次 dict 的查詢,也就是 hash 的過程。通過這個(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)焦?。鏈?zhǔn)焦R埠苋菀桌斫?,就是指同一個(gè)哈希桶中的多個(gè)元素用一個(gè)鏈表來保存,它們之間依次用指針連接。
隨著寫入的消息越來越多,哈希沖突的幾率也隨之升高,這時(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并沒有被分配空間。隨著數(shù)據(jù)逐步增多,Redis 開始執(zhí)行 rehash,這個(gè)過程分為三步:
1、給哈希表2分配更大的空間,例如是當(dāng)前哈希表1大小的兩倍;
2、把哈希表1中的數(shù)據(jù)重新映射并拷貝到哈希表2中;
3、釋放哈希表1的空間。
當(dāng)然數(shù)據(jù)很大的話,一次遷移所有的數(shù)據(jù),顯然是不合理的,會(huì)造成Redis線程阻塞,無法服務(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 中的有序集合就是使用跳表來實(shí)現(xiàn)的
對(duì)于一個(gè)單鏈表來講,即便鏈表中存儲(chǔ)的數(shù)據(jù)是有序的,如果我們要想在其中查找某個(gè)數(shù)據(jù),也只能從頭到尾遍歷鏈表。這樣查找效率就會(huì)很低,時(shí)間復(fù)雜度會(huì)很高,是O(n)。
鏈表加多級(jí)索引的結(jié)構(gòu),就是跳表,跳表查詢的時(shí)間復(fù)雜度是O(logn)
。通過在每個(gè)節(jié)點(diǎn)中維持多個(gè)指向其他節(jié)點(diǎn)的指針,從而實(shí)現(xià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è)集合倒也沒有什么特殊的點(diǎn)。這時(shí)候需要來看下整數(shù)集合的升級(jí)。
每當(dāng)一個(gè)整數(shù)被添加到整數(shù)集合時(shí),首先需要判斷下 新元素的類型和集合中現(xiàn)有元素類型的長短,如果新元素是一個(gè)32位的數(shù)字,現(xiàn)有集合類型是16位的,那么就需要將整數(shù)集合進(jìn)行升級(jí),然后才能將新的元素加入進(jìn)來。
這樣做的優(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ù)值或者是長度比較短的字符串,redis 就會(huì)使用壓縮列表來作為列表鍵的底層實(shí)現(xiàn)。
壓縮列表(ziplist)的目的是為了節(jié)約內(nèi)存,通過一片連續(xù)的內(nèi)存空間來存儲(chǔ)數(shù)據(jù)。這樣看起來好像和數(shù)組很像,數(shù)組中每個(gè)節(jié)點(diǎn)的內(nèi)存大小是一樣的,對(duì)于壓縮列表,每個(gè)節(jié)點(diǎn)的內(nèi)存大小是不同的,每個(gè)節(jié)點(diǎn)可以保存字節(jié)數(shù)組或一個(gè)整數(shù)值。通過可變的節(jié)點(diǎn)內(nè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)長度隨意;
5、Zlend : 是一個(gè)魔數(shù) 255,用來標(biāo)記壓縮列表的結(jié)束。
由于 ziplist 是連續(xù)緊湊存儲(chǔ),沒有冗余空間,所以插入新的元素需要 realloc 擴(kuò)展內(nèi)存,所以如果 ziplist 占用空間太大,realloc 重新分配內(nèi)存和拷貝的開銷就會(huì)很大,所以 ziplist 不適合存儲(chǔ)過多元素,也不適合存儲(chǔ)過大的字符串。
因此只有在元素?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)不超過 512 個(gè),value 值默認(rèn)不超過 64 字節(jié)??梢酝ㄟ^修改配置來調(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)不超過 128 個(gè),value 值默認(rèn)不超過 64 字節(jié)??梢酝ㄟ^修改配置來調(diào)整 zset_max_ziplist_entries 和 zset_max_ziplist_value 這兩個(gè)閥值的大小。
在壓縮列表中,如果我們要查找定位第一個(gè)元素和最后一個(gè)元素,可以通過表頭三個(gè)字段的長度直接定位,復(fù)雜度是O(1)。而查找其他元素時(shí),就沒有這么高效了,只能逐個(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)的長度
如果前一節(jié)點(diǎn)的長度小于 254 字節(jié), 那么 previous_entry_length 屬性的長度為 1 字節(jié): 前一節(jié)點(diǎn)的長度就保存在這一個(gè)字節(jié)里面;
如果前一節(jié)點(diǎn)的長度大于等于 254 字節(jié), 那么 previous_entry_length 屬性的長度為 5 字節(jié): 其中屬性的第一字節(jié)會(huì)被設(shè)置為 0xFE (十進(jìn)制值 254), 而之后的四個(gè)字節(jié)則用于保存前一節(jié)點(diǎn)的長度。
2、encoding : 記錄了節(jié)點(diǎn)的 content 屬性所保存數(shù)據(jù)的類型以及長度
3、content : 節(jié)點(diǎn)的 content 屬性負(fù)責(zé)保存節(jié)點(diǎn)的值, 節(jié)點(diǎn)值可以是一個(gè)字節(jié)數(shù)組或者整數(shù), 值的類型和長度由節(jié)點(diǎn)的 encoding 屬性決定。
舉個(gè)栗子:
如果壓縮列表中每個(gè)節(jié)點(diǎn)的長度都是250,因?yàn)槭切∮?54,所以每個(gè)節(jié)點(diǎn)中的 previous_entry_length 長度1字節(jié)就能夠保存了。
這時(shí)候,在頭部節(jié)點(diǎn)插入了一個(gè)新的元素 entryNew,然后長度是大于254,那么后面的節(jié)點(diǎn)中 entry1 的 previous_entry_length 長度為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è)線程來完成的,這也是 Redis 對(duì)外提供鍵值存儲(chǔ)服務(wù)的主要流程。
多線程必然會(huì)面臨對(duì)于共享資源的訪問,這時(shí)候通常的做法就是加鎖,雖然是多線程,這時(shí)候就會(huì)變成串行的訪問。也就是多線程編程模式會(huì)面臨的共享資源的并發(fā)訪問控制問題。
同時(shí)多線程也會(huì)引入同步原語來保護(hù)共享資源的并發(fā)訪問,代碼的可維護(hù)性和易讀性將會(huì)下降。
基于多路復(fù)用的高性能I/O模型
Linux 中的IO多路復(fù)用機(jī)制是指一個(gè)線程處理多個(gè)IO流。簡單來說,在 Redis 只運(yùn)行單線程的情況下,該機(jī)制允許內(nèi)核中,同時(shí)存在多個(gè)監(jiān)聽套接字和已連接套接字。內(nèi)核會(huì)一直監(jiān)聽這些套接字上的連接請(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)聽 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)聽多個(gè) socket,當(dāng)這些 socket 產(chǎn)生文件事件時(shí),就會(huì)觸發(fā)事件通知,文件分派器就會(huì)感知并獲取到這些事件。
雖然多個(gè)文件事件可能會(huì)并發(fā)出現(xiàn),但 IO 多路復(fù)用程序總會(huì)將所有產(chǎn)生事件的 socket 放入一個(gè)隊(duì)列中,通過這個(gè)隊(duì)列,有序的把這些文件事件通知給文件分派器。
文件事件分派器接收 I/O 多路復(fù)用程序傳來的套接字,并根據(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)作。
Redis 封裝了 4 種多路復(fù)用程序,每種封裝實(shí)現(xiàn)都提供了相同的 API 實(shí)現(xiàn)。編譯時(shí),會(huì)按照性能和系統(tǒng)平臺(tái),選擇最佳的 IO 多路復(fù)用函數(shù)作為底層實(shí)現(xiàn),選擇順序是,首先嘗試選擇 Solaries 中的 evport,如果沒有,就嘗試選擇 Linux 中的 epoll,否則就選擇大多 UNIX 系統(tǒng)都支持的 kqueue,這 3 個(gè)多路復(fù)用函數(shù)都直接使用系統(tǒng)內(nèi)核內(nèi)部的結(jié)構(gòu),可以服務(wù)數(shù)十萬的文件描述符。
如果當(dāng)前編譯環(huán)境沒有上述函數(shù),就會(huì)選擇 select 作為底層實(shí)現(xiàn)方案。select 方案的性能較差,事件發(fā)生時(shí),會(huì)掃描全部監(jiān)聽的描述符,事件復(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 通過監(jiān)聽處理事件隊(duì)列中的消息,來單線程的處理命令,如果一個(gè)命令的執(zhí)行時(shí)間很久,就會(huì)影響整個(gè) server 的性能;
耗時(shí)的操作命令有下面幾種:
1、操作 bigkey:bigkey 在寫入和刪除的時(shí)候,需要的時(shí)間都會(huì)很長;
2、使用復(fù)雜度過高的命令;
3、大量 key 集中過期:Redis 的過期機(jī)制也是在主線程中執(zhí)行的,大量 key 集中過期會(huì)導(dǎo)致處理一個(gè)請(qǐng)求時(shí),耗時(shí)都在刪除過期 key,耗時(shí)變長;
4、淘汰策略:淘汰策略也是在主線程執(zhí)行的,當(dāng)內(nèi)存超過 Redis 內(nèi)存上限后,每次寫入都需要淘汰一些 key,也會(huì)造成耗時(shí)變長;
5、AO F刷盤開啟 always 機(jī)制:每次寫入都需要把這個(gè)操作刷到磁盤,寫磁盤的速度遠(yuǎn)比寫內(nèi)存慢,會(huì)拖慢 Redis 的性能;
6、主從全量同步生成 RDB:雖然采用 fork 子進(jìn)程生成數(shù)據(jù)快照,但 fork 這一瞬間也是會(huì)阻塞整個(gè)線程的,實(shí)例越大,阻塞時(shí)間越久;
上面的這幾種問題,我們?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ù),無法利用到CPU多核。
為了提升網(wǎng)絡(luò) IO 的讀寫性能,Redis 在6.0推出了多線程,同過多線程的 IO 來處理網(wǎng)絡(luò)請(qǐng)求。不過需要注意的是這里的多線程僅僅是針對(duì)客戶端的讀寫是并行的,Redis 處理事件隊(duì)列中的命令,還是單線程處理的。
總結(jié)
Redis 使用單線程,來避免共享資源的競爭,使用多路復(fù)用實(shí)現(xiàn)高性能的I/O,它是內(nèi)存數(shù)據(jù)庫,所有操作都在內(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(簡單動(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學(xué)習(xí)教程之命令的執(zhí)行過程詳解
這篇文章主要給大家介紹了關(guān)于Redis學(xué)習(xí)教程之命令的執(zhí)行過程的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03redis過期回調(diào)功能實(shí)現(xiàn)示例
Redis提供了一種過期回調(diào)的機(jī)制,可以在某個(gè)鍵過期時(shí)觸發(fā)一個(gè)回調(diào)函數(shù),本文就來介紹一下redis過期回調(diào)功能實(shí)現(xiàn)示例,感興趣的可以了解一下2023-09-09Redis?中使用?list,streams,pub/sub?幾種方式實(shí)現(xiàn)消息隊(duì)列的問題
這篇文章主要介紹了Redis?中使用?list,streams,pub/sub?幾種方式實(shí)現(xiàn)消息隊(duì)列,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03