選單

Redis套路,一網打盡

Redis套路,一網打盡

幾乎涵蓋了Redis常見知識點,希望對大家有幫助

本文內容提要

Redis為什麼這麼快

1。1。 資料結構SDS的妙用

1。2。 效能優良的事件模型驅動

1。3。 基於記憶體的操作

Redis為什麼這麼靠譜

2。1。 AOF持久化

2。2。 RDB持久化

2。3。 Sentinel高可用

Redis6。x多執行緒一覽

Redis最佳實踐

Part1Redis為什麼這麼快

1。1

資料結構SDS的妙用

我們知道redis的底層是用c語言來編寫的,但是,資料結構確沒有直接套用C的結構,而是根據redis的定位自建了一套資料結構。

C語言中的字串結構:

SDS定義下的字串結構:

可以看到,相比於C語言來說,也就多了幾個欄位,分別用來標識空閒空間和當前資料長度,但簡直是神來之筆:

可以O(1)複雜度獲取字串長度;有

len

欄位的存在,無需像C結構一樣遍歷計數。

杜絕快取區溢位;C字串不記錄已佔用的長度,所以需要提前分配足夠空間,一旦空間不夠則會溢位。而有

free

欄位的存在,讓SDS在執行前可以判斷並分配足夠空間給程式

減少字串修改帶來的記憶體重分配次數;有

free

欄位的存在,使SDS有了空間預分配和惰性釋放的能力。

對二進位制是安全的;二進位制可能會有字元和C字串結尾符

'\0'

衝突,在遍歷和獲取資料時產生截斷異常,而SDS有了

len

欄位,準確了標識了資料長度,不需擔心被中間的

'\0'

截斷。

上面的內容以字串來說明SDS和C語言資料結構的差異和優勢。順便來看看連結串列、hash表、跳錶分別被Redis設計成了什麼樣的資料結構:

Redis套路,一網打盡

Redis套路,一網打盡

Redis套路,一網打盡

可以看到,Redis在設計資料結構的時候出發點是一致的。總結起來就是一句話:空間換時間。

用犧牲儲存空間和微小的計算代價,來換取資料的快速操作

1。2

效能優良的事件驅動模式

redis6。x之前,一直在說單執行緒如何如之何的好。

那麼,具體單執行緒體現在哪裡,又是怎麼完成資料讀寫工作的呢?

$ 單執行緒

關於新版本的多執行緒模型在後面小節單獨說,這裡先說單執行緒。

所謂單執行緒是指對資料的所有操作都是由一個執行緒按順序挨個執行的,使用單執行緒可以:

避免了不必要的上下文切換和競爭條件,也不存在多程序或者多執行緒導致的切換而消耗CPU;

不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的效能消耗。

然而,使用了單執行緒的處理方式,就意味著到達服務端的請求不可能被立即處理。

那麼怎麼來保證單執行緒的資源利用率和處理效率呢?

$ IO多路複用和事件驅動

Redis服務端,從整體上來看,其實是一個事件驅動的程式,所有的操作都以事件的方式來進行。

Redis套路,一網打盡

如圖所示,Redis的事件驅動架構由套接字、I/O多路複用、檔案事件分派器、事件處理器四個部分組成:

套接字(Socket)

,是對網路中不同主機上的應用程序之間進行雙向通訊的端點的抽象。

I/O多路複用

,透過監視多個描述符,當描述符就緒,則通知程式進行相應的操作,來幫助單個執行緒高效的處理多個連線請求。

Redis為每個IO多路複用函式都實現了相同的API,因此,底層實現是可以互換的。

Reids預設的IO多路複用機制是epoll,和select/poll等其他多路複用機制相比,epoll具有諸多優點:

事件驅動

,Redis設計的事件分為兩種,檔案事件和時間事件,檔案事件是對套接字操作的抽象,而時間事件則是對一些定時操作的抽象。

檔案事件:

客戶端連線請求(AE_READABLE事件)

客戶端命令請求(AE_READABLE事件)和事

服務端命令回覆(AE_WRITABLE事件)

時間事件:分為定時事件和週期性時間;redis的所有時間事件都存放在一個無序連結串列中,當時間事件執行器執行時,需要遍歷連結串列以確保已經到達時間的事件被全部處理。

可以看到,Redis整個執行方案是透過高效的I/O多路複用件驅動方式加上單執行緒記憶體操作來達到優秀的處理效率和極高的吞吐量。

1。3

基於記憶體的操作

上面的小節也提到了,redis之所以可以使用單執行緒來處理,其中的一個原因是,記憶體操作對資源損耗較小,保證了處理的高效性。

如此寶貴的記憶體資源,Redis是怎麼維護和管理的呢?

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

命中率統計

,在讀取一個鍵之後,伺服器會根據鍵是否存在來更新伺服器的鍵空間命中次數或鍵空間不命中次數。

LRU時間更新

,在讀取一個鍵之後,伺服器會更新鍵的LRU時間,這個值可以用於計算鍵的閒置時間。

惰性刪除

,如果伺服器在讀取一個鍵時發現該鍵已經過期,那麼伺服器會先刪除這個過期鍵,然後才執行餘下的其他操作。

鍵的dirty標識

,如果有客戶端使用WATCH命令監視了該鍵,伺服器會將這個鍵標記為dirty,讓事務程式注意到這個鍵已經被修改過。每次修改都會對dirty加一,用於觸發持久化和複製。

資料庫通知

,“如果伺服器開啟了資料庫通知功能,那麼在對鍵進行修改之後,伺服器將按配置傳送相應的資料庫通知”

$ Redis何如管理記憶體

過期鍵刪除

,記憶體和CPU資源都是寶貴的,Redis透過定期刪除設定合理的執行時長和執行頻率,配合惰性刪除兜底的方式,來達到CPU時間佔用和記憶體浪費之間的平衡。

資料淘汰

,如果key生產的太快,定期刪除操作跟不上新生產的速率,而這些key又很少被訪問無法觸發惰性刪除,是否會把記憶體撐爆?回答是不會,因為redis有資料淘汰策略:

noeviction:當記憶體不足以容納新寫入資料時,新寫入操作會報錯。

allkeys-lru:當記憶體不足以容納新寫入資料時,,移除最近最少使用的 Key。

allkeys-random:當記憶體不足以容納新寫入資料時,隨機移除某個 Key。

volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的 Key。

volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個 Key。

volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的 Key 優先移除。

值得一提的是,這裡的lru和平常我們所熟知的lru還不完全一樣,redis使用的是取樣機率的思想,省略了雙向連結串列的記憶體消耗。

Redis 會在每一次處理命令的時候判斷是否達到了最大限制,如果達到則使用對應的演算法去刪除涉及到的Key,這時,我們前面所維護過鍵的LRU值就會派上用場了。

Part2Redis為什麼這麼靠譜

天有不測風雲,伺服器也有趴窩的時候,Redis這個基於記憶體的儲存遇到伺服器宕機該怎麼應對呢?

2。1

RDB持久化

持久化是一種常見的解決方案,那麼,我們首先能想到的最簡單的持久化方案,就是每隔一段時間把記憶體裡的資料儲存一次,來避免絕大部分資料的丟失。這也是Redis的RDB持久化得思路。

RDB有兩種方式,save和bgsave

save

,會阻塞伺服器的其他操作,直到save執行完成,所以,這個期間的所有命令請求都會被拒絕。對客戶端影響較大。

BGSave

,由子程序進行資料儲存,期間redis仍然可以繼續處理客戶端請求。為了防止競爭和衝突,bgsave被設計成和save/bgrewriteaof操作互斥。

Redis伺服器預設每100毫秒執行一次,如果資料庫修改次數(dirty計數器)大於設定的閾值,並且距離上次執行儲存的時間(lastsave屬性)大於設定的閾值,則執行儲存操作。

因為是統一批次的儲存操作,rdb檔案有二進位制儲存、結構緊湊、空間消耗少、恢復速度快等特點,在持久化方案上不可或缺。

2。2

AOF持久化

然而,因為bgsave的週期間隔和儲存觸發條件等原因,在伺服器宕機時,不可避免的會丟失一部分最新的資料。這就需要一些輔助手段來做持久化補充。

RDB儲存的是鍵值對,而AOF則用來儲存寫命令。

為什麼AOF儲存的是命令,而不是鍵值對呢?

Coder的技術之路認為,一是因為aof刷盤,是在檔案事件處理過程當中的,具體位置是在結束一個事件迴圈之前,呼叫追加函式進行,所以,使用請求命令來儲存更方便;二是如果遇到追加過程中命令被破壞,也可以透過redis-check-aof來恢復(命令恢復起來比較方便)。

AOF刷盤策略

,由於aof追加動作是和客戶端請求處理序列執行的,所以每次都刷盤對效能影響較大,因此都是先追加到aof_buf快取區裡,而是否同步到AOF檔案中則依賴always、everysec(預設)、no的刷盤配置。想比everysec ,always對效能影響較大,而no則容易丟失資料。

AOF檔案重寫壓縮

,AOF因為儲存了請求命令,自然要比RDB更大,並且隨著程式的執行,會越來越大,然而,檔案中有很多冗餘的命令資料是可以壓縮的,因為對於某個鍵值對,某一時刻只會有一個狀態。

Redis套路,一網打盡

那麼,在重寫過程中新產生的操作該怎麼辦呢?

Redis套路,一網打盡

2。3

Sentinel高可用解決方案

上面兩個小節,主要是在闡述單機伺服器的資料穩定性保障,那麼,如果是多機、多程序該怎麼來保障呢?

哨兵的作用:監視服務節點的健康

Redis套路,一網打盡

當主節點宕機時,由哨兵感知,並在從節點中重新選舉主節點:

Redis套路,一網打盡

同時,sentinel還會監視宕機的master節點,恢復之後會將其設定為從節點加入叢集。

除了主從切換的sentinel方案,還有Cluster叢集模式來保障redis的高可用,用來解決主從複製的儲存浪費問題。

Part3Redis6。x的多執行緒

之前已經闡述過了單執行緒模型的整體流程,這裡不太贅述。

Redis的多執行緒模型,不是傳統意義上的多執行緒併發,而是把socket解析回寫的這部分操作並行化,以解決IO上的時間消耗帶來的系統瓶頸。

Redis套路,一網打盡

對客戶端的任何請求,其實還是主執行緒在執行,避免了操作相同資料時執行緒間的競爭,把io部分並行化,降低了io對資源的損耗,從而提升了系統的吞吐量。仔細想來,感覺和rpc中的非同步呼叫差不多意思,都是繫結來源,等待處理完成後給給各來源返回對應結果。

Part4Redis最佳實踐

Redis被當做分散式快取的應用場景非常普遍,有關快取穿透、快取擊穿、快取雪崩、資料漂移、快取踩踏、快取汙染、熱點key等常見問題,在上一篇文章諸多策略,快取為王中已經有了詳細闡述,這裡不再重複。

這裡主要給出一些日常開發中的關注點:

Key的設計。儘量控制key的長度,一是過長會佔用較多空間,二是我們知道鍵空間是字典型別,即時本身在查詢過程中很快,過長的鍵也會對比較判斷時間有所增加。

批次命令的使用。因為redis操作絕大部分都耗在網路傳輸上,將多次傳輸改為一次傳輸,大機率會提升效果。

value的大小。儘量避免大value,原因同上,value太大會影響網路傳輸效率。比如,之前的一次經歷,批次獲取了200個商品的資訊(資訊比較多,可以認為是大value),發現很慢,後來把200拆成了4個50,並行去呼叫,效果提升的比較明顯。這個問題也可以考慮用資料壓縮的方式進行最佳化

複雜命令的使用。比如排序、聚合等等操作,應該在離線階段就處理完畢,然後再存入快取,而不是線上使用複雜命令去計算。

善用資料結構。redis豐富的資料結構對支撐業務有天然的優勢,比如,之前曾用訊息佇列配合bitmap資料結構儲存和維護商品的多個狀態(庫存、上下架、秒殺、黑白名單等),getbit來直接判斷該商品是否允許展示。

最近面試BAT,整理一份面試資料《

Java面試BATJ通關手冊

》,覆蓋了Java核心技術、JVM、Java併發、SSM、微服務、資料庫、資料結構等等。

文章有幫助的話,在看,轉發吧。

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