<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>

          高并發(fā)存儲(chǔ)番外篇:Redis套路,一網(wǎng)打盡

          共 5186字,需瀏覽 11分鐘

           ·

          2021-08-05 09:24

          幾乎涵蓋了Redis常見(jiàn)知識(shí)點(diǎn),希望對(duì)大家有幫助

          本文內(nèi)容提要

          1. Redis為什么這么快
            1.1. 數(shù)據(jù)結(jié)構(gòu)SDS的妙用
            1.2. 性能優(yōu)良的事件模型驅(qū)動(dòng)
            1.3. 基于內(nèi)存的操作
          2. Redis為什么這么靠譜
            2.1. AOF持久化
            2.2. RDB持久化
            2.3. Sentinel高可用
          3. Redis6.x多線程一覽
          4. Redis最佳實(shí)踐

          Part1Redis為什么這么快

          1.1數(shù)據(jù)結(jié)構(gòu)SDS的妙用

          我們知道redis的底層是用c語(yǔ)言來(lái)編寫(xiě)的,但是,數(shù)據(jù)結(jié)構(gòu)確沒(méi)有直接套用C的結(jié)構(gòu),而是根據(jù)redis的定位自建了一套數(shù)據(jù)結(jié)構(gòu)。

          C語(yǔ)言中的字符串結(jié)構(gòu):

          SDS定義下的字符串結(jié)構(gòu):

          可以看到,相比于C語(yǔ)言來(lái)說(shuō),也就多了幾個(gè)字段,分別用來(lái)標(biāo)識(shí)空閑空間和當(dāng)前數(shù)據(jù)長(zhǎng)度,但簡(jiǎn)直是神來(lái)之筆:

          • 可以O(shè)(1)復(fù)雜度獲取字符串長(zhǎng)度;有len字段的存在,無(wú)需像C結(jié)構(gòu)一樣遍歷計(jì)數(shù)。
          • 杜絕緩存區(qū)溢出;C字符串不記錄已占用的長(zhǎng)度,所以需要提前分配足夠空間,一旦空間不夠則會(huì)溢出。而有free字段的存在,讓SDS在執(zhí)行前可以判斷并分配足夠空間給程序
          • 減少字符串修改帶來(lái)的內(nèi)存重分配次數(shù);有free字段的存在,使SDS有了空間預(yù)分配和惰性釋放的能力。
          • 對(duì)二進(jìn)制是安全的;二進(jìn)制可能會(huì)有字符和C字符串結(jié)尾符 '\0' 沖突,在遍歷和獲取數(shù)據(jù)時(shí)產(chǎn)生截?cái)喈惓#鳶DS有了len字段,準(zhǔn)確了標(biāo)識(shí)了數(shù)據(jù)長(zhǎng)度,不需擔(dān)心被中間的 '\0' 截?cái)唷?/span>

          上面的內(nèi)容以字符串來(lái)說(shuō)明SDS和C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)的差異和優(yōu)勢(shì)。順便來(lái)看看鏈表、hash表、跳表分別被Redis設(shè)計(jì)成了什么樣的數(shù)據(jù)結(jié)構(gòu):

          <<< 左右滑動(dòng)見(jiàn)更多 >>>

          可以看到,Redis在設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)的時(shí)候出發(fā)點(diǎn)是一致的。總結(jié)起來(lái)就是一句話:空間換時(shí)間。

          用犧牲存儲(chǔ)空間和微小的計(jì)算代價(jià),來(lái)?yè)Q取數(shù)據(jù)的快速操作

          1.2性能優(yōu)良的事件驅(qū)動(dòng)模式

          redis6.x之前,一直在說(shuō)單線程如何如之何的好。

          那么,具體單線程體現(xiàn)在哪里,又是怎么完成數(shù)據(jù)讀寫(xiě)工作的呢?

          $ 單線程

          關(guān)于新版本的多線程模型在后面小節(jié)單獨(dú)說(shuō),這里先說(shuō)單線程。

          所謂單線程是指對(duì)數(shù)據(jù)的所有操作都是由一個(gè)線程按順序挨個(gè)執(zhí)行的,使用單線程可以:

          • 避免了不必要的上下文切換和競(jìng)爭(zhēng)條件,也不存在多進(jìn)程或者多線程導(dǎo)致的切換而消耗CPU;
          • 不用去考慮各種鎖的問(wèn)題,不存在加鎖釋放鎖操作,沒(méi)有因?yàn)榭赡艹霈F(xiàn)死鎖而導(dǎo)致的性能消耗。

          然而,使用了單線程的處理方式,就意味著到達(dá)服務(wù)端的請(qǐng)求不可能被立即處理。

          那么怎么來(lái)保證單線程的資源利用率和處理效率呢?

          $ IO多路復(fù)用和事件驅(qū)動(dòng)

          Redis服務(wù)端,從整體上來(lái)看,其實(shí)是一個(gè)事件驅(qū)動(dòng)的程序,所有的操作都以事件的方式來(lái)進(jìn)行。

          如圖所示,Redis的事件驅(qū)動(dòng)架構(gòu)由套接字、I/O多路復(fù)用、文件事件分派器、事件處理器四個(gè)部分組成:

          套接字(Socket),是對(duì)網(wǎng)絡(luò)中不同主機(jī)上的應(yīng)用進(jìn)程之間進(jìn)行雙向通信的端點(diǎn)的抽象。

          I/O多路復(fù)用,通過(guò)監(jiān)視多個(gè)描述符,當(dāng)描述符就緒,則通知程序進(jìn)行相應(yīng)的操作,來(lái)幫助單個(gè)線程高效的處理多個(gè)連接請(qǐng)求。

          Redis為每個(gè)IO多路復(fù)用函數(shù)都實(shí)現(xiàn)了相同的API,因此,底層實(shí)現(xiàn)是可以互換的。

          Reids默認(rèn)的IO多路復(fù)用機(jī)制是epoll,和select/poll等其他多路復(fù)用機(jī)制相比,epoll具有諸多優(yōu)點(diǎn):


          并發(fā)連接限制內(nèi)存拷貝活躍連接感知
          epoll沒(méi)有最大并發(fā)連接的限制共享內(nèi)存,無(wú)需內(nèi)存拷貝基于event callback方式,只感知活躍連接
          select受fd限制,32位機(jī)默認(rèn)1024個(gè)/64位機(jī)默認(rèn)2048個(gè)把fd集合從用戶(hù)態(tài)拷貝到內(nèi)核態(tài)只能感知有fd就緒,但無(wú)法定位,需要遍歷+輪詢(xún)
          poll采用鏈表存儲(chǔ)fd無(wú)最大并發(fā)連接數(shù)限制同select同select,需遍歷+輪詢(xún)

          事件驅(qū)動(dòng),Redis設(shè)計(jì)的事件分為兩種,文件事件和時(shí)間事件,文件事件是對(duì)套接字操作的抽象,而時(shí)間事件則是對(duì)一些定時(shí)操作的抽象。

          文件事件:

          • 客戶(hù)端連接請(qǐng)求(AE_READABLE事件)
          • 客戶(hù)端命令請(qǐng)求(AE_READABLE事件)和事
          • 服務(wù)端命令回復(fù)(AE_WRITABLE事件)

          時(shí)間事件: 分為定時(shí)事件和周期性時(shí)間;redis的所有時(shí)間事件都存放在一個(gè)無(wú)序鏈表中,當(dāng)時(shí)間事件執(zhí)行器運(yùn)行時(shí),需要遍歷鏈表以確保已經(jīng)到達(dá)時(shí)間的事件被全部處理。

          可以看到,Redis整個(gè)執(zhí)行方案是通過(guò)高效的I/O多路復(fù)用件驅(qū)動(dòng)方式加上單線程內(nèi)存操作來(lái)達(dá)到優(yōu)秀的處理效率和極高的吞吐量。

          1.3基于內(nèi)存的操作

          上面的小節(jié)也提到了,redis之所以可以使用單線程來(lái)處理,其中的一個(gè)原因是,內(nèi)存操作對(duì)資源損耗較小,保證了處理的高效性。

          如此寶貴的內(nèi)存資源,Redis是怎么維護(hù)和管理的呢?

          $ 除了增刪改查還有哪些維護(hù)性操作[1]

          命中率統(tǒng)計(jì),在讀取一個(gè)鍵之后,服務(wù)器會(huì)根據(jù)鍵是否存在來(lái)更新服務(wù)器的鍵空間命中次數(shù)或鍵空間不命中次數(shù)。

          LRU時(shí)間更新,在讀取一個(gè)鍵之后,服務(wù)器會(huì)更新鍵的LRU時(shí)間,這個(gè)值可以用于計(jì)算鍵的閑置時(shí)間。

          惰性刪除,如果服務(wù)器在讀取一個(gè)鍵時(shí)發(fā)現(xiàn)該鍵已經(jīng)過(guò)期,那么服務(wù)器會(huì)先刪除這個(gè)過(guò)期鍵,然后才執(zhí)行余下的其他操作。

          鍵的dirty標(biāo)識(shí),如果有客戶(hù)端使用WATCH命令監(jiān)視了該鍵,服務(wù)器會(huì)將這個(gè)鍵標(biāo)記為dirty,讓事務(wù)程序注意到這個(gè)鍵已經(jīng)被修改過(guò)。每次修改都會(huì)對(duì)dirty加一,用于觸發(fā)持久化和復(fù)制

          數(shù)據(jù)庫(kù)通知,“如果服務(wù)器開(kāi)啟了數(shù)據(jù)庫(kù)通知功能,那么在對(duì)鍵進(jìn)行修改之后,服務(wù)器將按配置發(fā)送相應(yīng)的數(shù)據(jù)庫(kù)通知”

          $ Redis何如管理內(nèi)存

          過(guò)期鍵刪除,內(nèi)存和CPU資源都是寶貴的,Redis通過(guò)定期刪除設(shè)定合理的執(zhí)行時(shí)長(zhǎng)和執(zhí)行頻率,配合惰性刪除兜底的方式,來(lái)達(dá)到CPU時(shí)間占用和內(nèi)存浪費(fèi)之間的平衡。

          數(shù)據(jù)淘汰,如果key生產(chǎn)的太快,定期刪除操作跟不上新生產(chǎn)的速率,而這些key又很少被訪問(wèn)無(wú)法觸發(fā)惰性刪除,是否會(huì)把內(nèi)存撐爆?回答是不會(huì),因?yàn)閞edis有數(shù)據(jù)淘汰策略:

          • noeviction:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),新寫(xiě)入操作會(huì)報(bào)錯(cuò)。
          • allkeys-lru:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),,移除最近最少使用的 Key。
          • allkeys-random:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),隨機(jī)移除某個(gè) Key。
          • volatile-lru:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中,移除最近最少使用的 Key。
          • volatile-random:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中,隨機(jī)移除某個(gè) Key。
          • volatile-ttl:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中,有更早過(guò)期時(shí)間的 Key 優(yōu)先移除。

          值得一提的是,這里的lru和平常我們所熟知的lru還不完全一樣,redis使用的是采樣概率的思想,省略了雙向鏈表的內(nèi)存消耗。

          Redis 會(huì)在每一次處理命令的時(shí)候判斷是否達(dá)到了最大限制,如果達(dá)到則使用對(duì)應(yīng)的算法去刪除涉及到的Key,這時(shí),我們前面所維護(hù)過(guò)鍵的LRU值就會(huì)派上用場(chǎng)了。

          Part2Redis為什么這么靠譜

          天有不測(cè)風(fēng)云,服務(wù)器也有趴窩的時(shí)候,Redis這個(gè)基于內(nèi)存的存儲(chǔ)遇到服務(wù)器宕機(jī)該怎么應(yīng)對(duì)呢?

          2.1RDB持久化

          持久化是一種常見(jiàn)的解決方案,那么,我們首先能想到的最簡(jiǎn)單的持久化方案,就是每隔一段時(shí)間把內(nèi)存里的數(shù)據(jù)保存一次,來(lái)避免絕大部分?jǐn)?shù)據(jù)的丟失。這也是Redis的RDB持久化得思路。

          RDB有兩種方式,save和bgsave

          save,會(huì)阻塞服務(wù)器的其他操作,直到save執(zhí)行完成,所以,這個(gè)期間的所有命令請(qǐng)求都會(huì)被拒絕。對(duì)客戶(hù)端影響較大。

          BGSave,由子進(jìn)程進(jìn)行數(shù)據(jù)保存,期間redis仍然可以繼續(xù)處理客戶(hù)端請(qǐng)求。為了防止競(jìng)爭(zhēng)和沖突,bgsave被設(shè)計(jì)成和save/bgrewriteaof操作互斥。

          Redis服務(wù)器默認(rèn)每100毫秒執(zhí)行一次,如果數(shù)據(jù)庫(kù)修改次數(shù)(dirty計(jì)數(shù)器)大于設(shè)置的閾值,并且距離上次執(zhí)行保存的時(shí)間(lastsave屬性)大于設(shè)置的閾值,則執(zhí)行保存操作。

          因?yàn)槭墙y(tǒng)一批量的保存操作,rdb文件有二進(jìn)制存儲(chǔ)、結(jié)構(gòu)緊湊、空間消耗少、恢復(fù)速度快等特點(diǎn),在持久化方案上不可或缺。

          2.2AOF持久化

          然而,因?yàn)閎gsave的周期間隔和保存觸發(fā)條件等原因,在服務(wù)器宕機(jī)時(shí),不可避免的會(huì)丟失一部分最新的數(shù)據(jù)。這就需要一些輔助手段來(lái)做持久化補(bǔ)充。

          RDB保存的是鍵值對(duì),而AOF則用來(lái)保存寫(xiě)命令。

          為什么AOF保存的是命令,而不是鍵值對(duì)呢?

          Coder的技術(shù)之路認(rèn)為,一是因?yàn)閍of刷盤(pán),是在文件事件處理過(guò)程當(dāng)中的,具體位置是在結(jié)束一個(gè)事件循環(huán)之前,調(diào)用追加函數(shù)進(jìn)行,所以,使用請(qǐng)求命令來(lái)存儲(chǔ)更方便;二是如果遇到追加過(guò)程中命令被破壞,也可以通過(guò)redis-check-aof來(lái)恢復(fù)(命令恢復(fù)起來(lái)比較方便)。

          AOF刷盤(pán)策略,由于aof追加動(dòng)作是和客戶(hù)端請(qǐng)求處理串行執(zhí)行的,所以每次都刷盤(pán)對(duì)性能影響較大,因此都是先追加到aof_buf緩存區(qū)里,而是否同步到AOF文件中則依賴(lài)always、everysec(默認(rèn))、no的刷盤(pán)配置。想比everysec ,always對(duì)性能影響較大,而no則容易丟失數(shù)據(jù)。

          AOF文件重寫(xiě)壓縮,AOF因?yàn)楸4媪苏?qǐng)求命令,自然要比RDB更大,并且隨著程序的運(yùn)行,會(huì)越來(lái)越大,然而,文件中有很多冗余的命令數(shù)據(jù)是可以壓縮的,因?yàn)閷?duì)于某個(gè)鍵值對(duì),某一時(shí)刻只會(huì)有一個(gè)狀態(tài)。

          那么,在重寫(xiě)過(guò)程中新產(chǎn)生的操作該怎么辦呢?

          2.3Sentinel高可用解決方案

          上面兩個(gè)小節(jié),主要是在闡述單機(jī)服務(wù)器的數(shù)據(jù)穩(wěn)定性保障,那么,如果是多機(jī)、多進(jìn)程該怎么來(lái)保障呢?

          哨兵的作用:監(jiān)視服務(wù)節(jié)點(diǎn)的健康

          當(dāng)主節(jié)點(diǎn)宕機(jī)時(shí),由哨兵感知,并在從節(jié)點(diǎn)中重新選舉主節(jié)點(diǎn):

          同時(shí),sentinel還會(huì)監(jiān)視宕機(jī)的master節(jié)點(diǎn),恢復(fù)之后會(huì)將其設(shè)置為從節(jié)點(diǎn)加入集群。

          除了主從切換的sentinel方案,還有Cluster集群模式來(lái)保障redis的高可用,用來(lái)解決主從復(fù)制的存儲(chǔ)浪費(fèi)問(wèn)題。

          Part3Redis6.x的多線程

          之前已經(jīng)闡述過(guò)了單線程模型的整體流程,這里不太贅述。

          Redis的多線程模型,不是傳統(tǒng)意義上的多線程并發(fā),而是把socket解析回寫(xiě)的這部分操作并行化,以解決IO上的時(shí)間消耗帶來(lái)的系統(tǒng)瓶頸。

          對(duì)客戶(hù)端的任何請(qǐng)求,其實(shí)還是主線程在執(zhí)行,避免了操作相同數(shù)據(jù)時(shí)線程間的競(jìng)爭(zhēng),把io部分并行化,降低了io對(duì)資源的損耗,從而提升了系統(tǒng)的吞吐量。仔細(xì)想來(lái),感覺(jué)和rpc中的異步調(diào)用差不多意思,都是綁定來(lái)源,等待處理完成后給給各來(lái)源返回對(duì)應(yīng)結(jié)果。

          Part4Redis最佳實(shí)踐

          Redis被當(dāng)做分布式緩存的應(yīng)用場(chǎng)景非常普遍,有關(guān)緩存穿透、緩存擊穿、緩存雪崩、數(shù)據(jù)漂移、緩存踩踏、緩存污染、熱點(diǎn)key等常見(jiàn)問(wèn)題,在上一篇文章 諸多策略,緩存為王中已經(jīng)有了詳細(xì)闡述,這里不再重復(fù)。

          這里主要給出一些日常開(kāi)發(fā)中的關(guān)注點(diǎn):

          • Key的設(shè)計(jì)。盡量控制key的長(zhǎng)度,一是過(guò)長(zhǎng)會(huì)占用較多空間,二是我們知道鍵空間是字典類(lèi)型,即時(shí)本身在查找過(guò)程中很快,過(guò)長(zhǎng)的鍵也會(huì)對(duì)比較判斷時(shí)間有所增加。
          • 批量命令的使用。因?yàn)閞edis操作絕大部分都耗在網(wǎng)絡(luò)傳輸上,將多次傳輸改為一次傳輸,大概率會(huì)提升效果。
          • value的大小。盡量避免大value,原因同上,value太大會(huì)影響網(wǎng)絡(luò)傳輸效率。比如,之前的一次經(jīng)歷,批量獲取了200個(gè)商品的信息(信息比較多,可以認(rèn)為是大value),發(fā)現(xiàn)很慢,后來(lái)把200拆成了4個(gè)50,并行去調(diào)用,效果提升的比較明顯。這個(gè)問(wèn)題也可以考慮用數(shù)據(jù)壓縮的方式進(jìn)行優(yōu)化
          • 復(fù)雜命令的使用。比如排序、聚合等等操作,應(yīng)該在離線階段就處理完畢,然后再存入緩存,而不是在線使用復(fù)雜命令去計(jì)算。
          • 善用數(shù)據(jù)結(jié)構(gòu)。redis豐富的數(shù)據(jù)結(jié)構(gòu)對(duì)支撐業(yè)務(wù)有天然的優(yōu)勢(shì),比如,之前曾用消息隊(duì)列配合bitmap數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)和維護(hù)商品的多個(gè)狀態(tài)(庫(kù)存、上下架、秒殺、黑白名單等),getbit來(lái)直接判斷該商品是否允許展示。

          其實(shí)沒(méi)有什么最佳實(shí)踐,業(yè)務(wù)各有各的不同,都需要在實(shí)踐中研究嘗試,如果大家有非常好的實(shí)際案例,也歡迎補(bǔ)充,歡迎留言交流~

          — 【 THE END 】—
          本公眾號(hào)全部博文已整理成一個(gè)目錄,請(qǐng)?jiān)诠娞?hào)里回復(fù)「m」獲取!

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) PDF 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)


          瀏覽 52
          點(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>
                  69激情视频 | 久久亚洲福利视频 | 91偷拍视频 | 欧美经典怡红院肏肥屄淫荡视频在线观看 | 国产激情视频久久久久久久 |