<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          大廠面試官:Redis為什么這么快?

          共 4061字,需瀏覽 9分鐘

           ·

          2021-07-11 16:25

          前言

          大家好呀,我是撿田螺的小男孩。我們都知道Redis很快,它QPS可達(dá)10萬(wàn)(每秒請(qǐng)求數(shù))。Redis為什么這么快呢,本文將跟大家一起學(xué)習(xí)。

          • 公眾號(hào):撿田螺的小男孩

          基于內(nèi)存實(shí)現(xiàn)

          我們都知道內(nèi)存讀寫(xiě)是比磁盤(pán)讀寫(xiě)快很多的。Redis是基于內(nèi)存存儲(chǔ)實(shí)現(xiàn)的數(shù)據(jù)庫(kù),相對(duì)于數(shù)據(jù)存在磁盤(pán)的數(shù)據(jù)庫(kù),就省去磁盤(pán)磁盤(pán)I/O的消耗。MySQL等磁盤(pán)數(shù)據(jù)庫(kù),需要建立索引來(lái)加快查詢(xún)效率,而Redis數(shù)據(jù)存放在內(nèi)存,直接操作內(nèi)存,所以就很快。

          高效的數(shù)據(jù)結(jié)構(gòu)

          我們知道,MySQL索引為了提高效率,選擇了B+樹(shù)的數(shù)據(jù)結(jié)構(gòu)。其實(shí)合理的數(shù)據(jù)結(jié)構(gòu),就是可以讓你的應(yīng)用/程序更快。先看下Redis的數(shù)據(jù)結(jié)構(gòu)&內(nèi)部編碼圖:

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

          struct sdshdr { //SDS簡(jiǎn)單動(dòng)態(tài)字符串
              int len;    //記錄buf中已使用的空間
              int free;   // buf中空閑空間長(zhǎng)度
              char buf[]; //存儲(chǔ)的實(shí)際內(nèi)容
          }

          字符串長(zhǎng)度處理

          在C語(yǔ)言中,要獲取撿田螺的小男孩這個(gè)字符串的長(zhǎng)度,需要從頭開(kāi)始遍歷,復(fù)雜度為O(n); 在Redis中, 已經(jīng)有一個(gè)len字段記錄當(dāng)前字符串的長(zhǎng)度啦,直接獲取即可,時(shí)間復(fù)雜度為O(1)。

          減少內(nèi)存重新分配的次數(shù)

          在C語(yǔ)言中,修改一個(gè)字符串,需要重新分配內(nèi)存,修改越頻繁,內(nèi)存分配就越頻繁,而分配內(nèi)存是會(huì)消耗性能的。而在Redis中,SDS提供了兩種優(yōu)化策略:空間預(yù)分配和惰性空間釋放。

          空間預(yù)分配

          當(dāng)SDS簡(jiǎn)單動(dòng)態(tài)字符串修改和空間擴(kuò)充時(shí),除了分配必需的內(nèi)存空間,還會(huì)額外分配未使用的空間。分配規(guī)則是醬紫的:

          • SDS修改后,len的長(zhǎng)度小于1M,那么將額外分配與len相同長(zhǎng)度的未使用空間。比如len=100,重新分配后,buf 的實(shí)際長(zhǎng)度會(huì)變?yōu)?00(已使用空間)+100(額外空間)+1(空字符)=201。
          • SDS修改后, len長(zhǎng)度大于1M,那么程序?qū)⒎峙?M的未使用空間。

          惰性空間釋放

          當(dāng)SDS縮短時(shí),不是回收多余的內(nèi)存空間,而是用free記錄下多余的空間。后續(xù)再有修改操作,直接使用free中的空間,減少內(nèi)存分配。

          哈希

          Redis 作為一個(gè)K-V的內(nèi)存數(shù)據(jù)庫(kù),它使用用一張全局的哈希來(lái)保存所有的鍵值對(duì)。這張哈希表,有多個(gè)哈希桶組成,哈希桶中的entry元素保存了*key*value指針,其中*key指向了實(shí)際的鍵,*value指向了實(shí)際的值。

          哈希表查找速率很快的,有點(diǎn)類(lèi)似于Java中的HashMap,它讓我們?cè)?strong style="font-weight: border;color: #0e88eb;">O(1) 的時(shí)間復(fù)雜度快速找到鍵值對(duì)。首先通過(guò)key計(jì)算哈希值,找到對(duì)應(yīng)的哈希桶位置,然后定位到entry,在entry找到對(duì)應(yīng)的數(shù)據(jù)。

          有些小伙伴可能會(huì)有疑問(wèn):你往哈希表中寫(xiě)入大量數(shù)據(jù)時(shí),不是會(huì)遇到哈希沖突問(wèn)題嘛,那效率就會(huì)降下來(lái)啦。

          哈希沖突: 通過(guò)不同的key,計(jì)算出一樣的哈希值,導(dǎo)致落在同一個(gè)哈希桶中。

          Redis為了解決哈希沖突,采用了鏈?zhǔn)焦?/strong>。鏈?zhǔn)焦J侵竿粋€(gè)哈希桶中,多個(gè)元素用一個(gè)鏈表來(lái)保存,它們之間依次用指針連接。

          有些小伙伴可能還會(huì)有疑問(wèn):哈希沖突鏈上的元素只能通過(guò)指針逐一查找再操作。當(dāng)往哈希表插入數(shù)據(jù)很多,沖突也會(huì)越多,沖突鏈表就會(huì)越長(zhǎng),那查詢(xún)效率就會(huì)降低了。

          為了保持高效,Redis 會(huì)對(duì)哈希表做rehash操作,也就是增加哈希桶,減少?zèng)_突。為了rehash更高效,Redis還默認(rèn)使用了兩個(gè)全局哈希表,一個(gè)用于當(dāng)前使用,稱(chēng)為主哈希表,一個(gè)用于擴(kuò)容,稱(chēng)為備用哈希表。

          跳躍表

          跳躍表是Redis特有的數(shù)據(jù)結(jié)構(gòu),它其實(shí)就是在鏈表的基礎(chǔ)上,增加多級(jí)索引,以提高查找效率。跳躍表的簡(jiǎn)單原理圖如下:

          • 每一層都有一條有序的鏈表,最底層的鏈表包含了所有的元素。
          • 跳躍表支持平均 O(logN),最壞 O(N)復(fù)雜度的節(jié)點(diǎn)查找,還可以通過(guò)順序性操作批量處理節(jié)點(diǎn)。

          壓縮列表ziplist

          壓縮列表ziplist是列表鍵和字典鍵的的底層實(shí)現(xiàn)之一。它是由一系列特殊編碼的內(nèi)存塊構(gòu)成的列表, 一個(gè)ziplist可以包含多個(gè)entry, 每個(gè)entry可以保存一個(gè)長(zhǎng)度受限的字符數(shù)組或者整數(shù),如下:

          • zlbytes :記錄整個(gè)壓縮列表占用的內(nèi)存字節(jié)數(shù)
          • zltail: 尾節(jié)點(diǎn)至起始節(jié)點(diǎn)的偏移量
          • zllen : 記錄整個(gè)壓縮列表包含的節(jié)點(diǎn)數(shù)量
          • entryX: 壓縮列表包含的各個(gè)節(jié)點(diǎn)
          • zlend : 特殊值0xFF(十進(jìn)制255),用于標(biāo)記壓縮列表末端

          由于內(nèi)存是連續(xù)分配的,所以遍歷速度很快。。

          合理的數(shù)據(jù)編碼

          Redis支持多種數(shù)據(jù)基本類(lèi)型,每種基本類(lèi)型對(duì)應(yīng)不同的數(shù)據(jù)結(jié)構(gòu),每種數(shù)據(jù)結(jié)構(gòu)對(duì)應(yīng)不一樣的編碼。為了提高性能,Redis設(shè)計(jì)者總結(jié)出,數(shù)據(jù)結(jié)構(gòu)最適合的編碼搭配。

          Redis是使用對(duì)象(redisObject)來(lái)表示數(shù)據(jù)庫(kù)中的鍵值,當(dāng)我們?cè)?Redis 中創(chuàng)建一個(gè)鍵值對(duì)時(shí),至少創(chuàng)建兩個(gè)對(duì)象,一個(gè)對(duì)象是用做鍵值對(duì)的鍵對(duì)象,另一個(gè)是鍵值對(duì)的值對(duì)象。

          //關(guān)注公眾號(hào):撿田螺的小男孩
          typedef struct redisObject{
              //類(lèi)型
             unsigned type:4;
             //編碼
             unsigned encoding:4;
             //指向底層數(shù)據(jù)結(jié)構(gòu)的指針
             void *ptr;
              //...
           }robj;

          redisObject中,type 對(duì)應(yīng)的是對(duì)象類(lèi)型,包含String對(duì)象、List對(duì)象、Hash對(duì)象、Set對(duì)象、zset對(duì)象。encoding 對(duì)應(yīng)的是編碼。

          • String:如果存儲(chǔ)數(shù)字的話(huà),是用int類(lèi)型的編碼;如果存儲(chǔ)非數(shù)字,小于等于39字節(jié)的字符串,是embstr;大于39個(gè)字節(jié),則是raw編碼。
          • List:如果列表的元素個(gè)數(shù)小于512個(gè),列表每個(gè)元素的值都小于64字節(jié)(默認(rèn)),使用ziplist編碼,否則使用linkedlist編碼
          • Hash:哈希類(lèi)型元素個(gè)數(shù)小于512個(gè),所有值小于64字節(jié)的話(huà),使用ziplist編碼,否則使用hashtable編碼。
          • Set:如果集合中的元素都是整數(shù)且元素個(gè)數(shù)小于512個(gè),使用intset編碼,否則使用hashtable編碼。
          • Zset:當(dāng)有序集合的元素個(gè)數(shù)小于128個(gè),每個(gè)元素的值小于64字節(jié)時(shí),使用ziplist編碼,否則使用skiplist(跳躍表)編碼

          合理的線(xiàn)程模型

          單線(xiàn)程模型:避免了上下文切換

          Redis是單線(xiàn)程的,其實(shí)是指Redis的網(wǎng)絡(luò)IO和鍵值對(duì)讀寫(xiě)是由一個(gè)線(xiàn)程來(lái)完成的。但Redis的其他功能,比如持久化、異步刪除、集群數(shù)據(jù)同步等等,實(shí)際是由額外的線(xiàn)程執(zhí)行的。

          Redis的單線(xiàn)程模型,避免了CPU不必要的上下文切換競(jìng)爭(zhēng)鎖的消耗。也正因?yàn)槭菃尉€(xiàn)程,如果某個(gè)命令執(zhí)行過(guò)長(zhǎng)(如hgetall命令),會(huì)造成阻塞。Redis是面向快速執(zhí)行場(chǎng)景的內(nèi)存數(shù)據(jù)庫(kù),所以要慎用如lrange和smembers、hgetall等命令。

          什么是上下文切換?舉個(gè)粟子:

          • 比如你在看一本英文小說(shuō),你看到某一頁(yè),發(fā)現(xiàn)有個(gè)單詞不會(huì)讀,你加了個(gè)書(shū)簽,然后去查字典。查完字典后,你回來(lái)從書(shū)簽?zāi)抢锢^續(xù)開(kāi)始讀,這個(gè)流程就很舒暢。
          • 如果你一個(gè)人讀這本書(shū),肯定沒(méi)啥問(wèn)題。但是如果你去查字典的時(shí)候,別的小伙伴翻了一下你的書(shū),然后溜了。你再回來(lái)看的時(shí)候,發(fā)現(xiàn)書(shū)不是你看的那一頁(yè)了,你得花時(shí)間找到你的那一頁(yè)。
          • 一本書(shū),你一個(gè)人怎么看怎么打標(biāo)簽都沒(méi)事,但是人多了翻來(lái)翻去,這本書(shū)各種標(biāo)記就很亂了。可能這個(gè)解釋很粗糙,但是道理應(yīng)該是一樣的。

          I/O 多路復(fù)用

          什么是I/O多路復(fù)用?

          • I/O :網(wǎng)絡(luò) I/O
          • 多路 :多個(gè)網(wǎng)絡(luò)連接
          • 復(fù)用:復(fù)用同一個(gè)線(xiàn)程。
          • IO多路復(fù)用其實(shí)就是一種同步IO模型,它實(shí)現(xiàn)了一個(gè)線(xiàn)程可以監(jiān)視多個(gè)文件句柄;一旦某個(gè)文件句柄就緒,就能夠通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫(xiě)操作;而沒(méi)有文件句柄就緒時(shí),就會(huì)阻塞應(yīng)用程序,交出cpu。

          多路I/O復(fù)用技術(shù)可以讓單個(gè)線(xiàn)程高效的處理多個(gè)連接請(qǐng)求,而Redis使用用epoll作為I/O多路復(fù)用技術(shù)的實(shí)現(xiàn)。并且Redis自身的事件處理模型將epoll中的連接、讀寫(xiě)、關(guān)閉都轉(zhuǎn)換為事件,不在網(wǎng)絡(luò)I/O上浪費(fèi)過(guò)多的時(shí)間。

          虛擬內(nèi)存機(jī)制

          Redis直接自己構(gòu)建了VM機(jī)制 ,不會(huì)像一般的系統(tǒng)會(huì)調(diào)用系統(tǒng)函數(shù)處理,會(huì)浪費(fèi)一定的時(shí)間去移動(dòng)和請(qǐng)求。

          Redis的虛擬內(nèi)存機(jī)制是啥呢?

          虛擬內(nèi)存機(jī)制就是暫時(shí)把不經(jīng)常訪問(wèn)的數(shù)據(jù)(冷數(shù)據(jù))從內(nèi)存交換到磁盤(pán)中,從而騰出寶貴的內(nèi)存空間用于其它需要訪問(wèn)的數(shù)據(jù)(熱數(shù)據(jù))。通過(guò)VM功能可以實(shí)現(xiàn)冷熱數(shù)據(jù)分離,使熱數(shù)據(jù)仍在內(nèi)存中、冷數(shù)據(jù)保存到磁盤(pán)。這樣就可以避免因?yàn)閮?nèi)存不足而造成訪問(wèn)速度下降的問(wèn)題。


          參考資料
          [1]

          Redis之VM機(jī)制: https://www.codenong.com/cs106843764/

          [2]

          一文揭秘單線(xiàn)程的Redis為什么這么快?: https://zhuanlan.zhihu.com/p/57089960

          [3]

          洞察|Redis是單線(xiàn)程的,但Redis為什么這么快?: https://zhuanlan.zhihu.com/p/42272979



          瀏覽 60
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  蜜桃91在线观看 | 三级网站视频在线观看 | 在线播放国产一 | 日韩一级片在线观看 | 91国產乱老熟 |