作者:京東零售 李磊
Redis叢集一般有四種方式,分別為:主從複製、哨兵模式、Cluster以及各大廠的叢集方案。在3.0版本之前只支援單範例模式,3.0之後支援了叢集方式。在3.0之前各大廠為了解決單範例Redis的儲存瓶頸問題各自推出了自己的叢集方案,其核心思想就是資料分片,主要有使用者端分片、代理分片、伺服器端分片。這裡咱們只介紹前三種方式:主從、哨兵、Cluster。
Redis單節點的資料是儲存在一臺伺服器上的,如果伺服器出現故障,會導致資料不可用,而且讀寫都是在同一臺伺服器上,請求量大時會出現I/O瓶頸。為了避免單點故障和讀寫不分離,Redis提供了複製功能來實現Master中的資料向Slave資料庫的同步。Master可以有多個Slave節點,Slave節點也可以有Slave節點,從節點是級聯結構,如下圖所示:
一般情況下為了讓資料讀寫分離,Master節點用來執行寫操作,Slave節點提供讀操作,Master執行寫操作時將變化的資料同步到Slave,其工作原理如下圖所示:
Redis主從複製基本原理有三種:全量複製、基於長連線的命令傳播、增量複製。
首先介紹一下全量複製,當主從伺服器剛建立連線的時候,會按照三個階段完成資料的第一次同步。假設現在有範例1(192.168.1.1)和範例2(192.168.1.2),當我們在範例2上執行「replicaof 192.168.1.1 6379」命令後,範例2就變成了範例1的從庫,並開始從範例1上覆制資料,有如下三個階段:
第一個階段,是主從庫之間建立連線、協商同步的過程,為全量複製做準備。具體來說,從庫給主庫傳送psync命令,表示要進行資料同步,主庫根據這個命令的引數來啟動複製。psync命令包含了主庫的runID和複製進度offset兩個引數。
•runID:是每個Redis範例啟動時自動生成的一個隨機ID,用來唯一標記這個範例。當從庫和主庫第一次複製時,因為不知道主庫的runID,所以將runID設定為「?」。
•offset:設定為-1,表示第一次複製。
主庫收到psync命令後,會用FULLRESYNC響應命令帶上兩個引數:主庫runID和主庫目前的複製進度offset,返回給從庫,從庫收到響應後會記錄下這兩個引數。FULLRESYNC響應表示第一次複製採用的全量複製,也就是說,主庫會把當前所有的資料都複製給從庫。
第二個階段,主庫將所有資料同步給從庫,從庫收到資料後,首先清空現有資料,然後在本地完成資料載入。這個過程依賴於記憶體快照生成的RDB檔案。具體來說,主庫執行bgsave命令,生成RDB檔案,接著將檔案發給從庫。
第三個階段,主庫會把第二階段執行過程中新接收到的寫命令,再傳送給從庫。具體來說,當主庫完成RDB檔案傳送後,就會把此時replication buffer中的修改和新增操作發給從庫,從庫再重新執行這些操作。這樣一來,主從庫就實現同步了。
以上是全量複製的基本流程,一旦主從庫完成了全量複製,它們之間就會一直維護一個網路連線,主庫會通過這個連線將後續陸續收到的命令操作再同步給從庫,這個過程也稱為基於長連線的命令傳播,可以避免頻繁建立連線的開銷。
長連線是基於網路的,那麼它就存在網路斷開的風險,在Redis2.8之前,如果主從庫在命令傳播時出現了網路閃斷,那麼從庫會和主庫重新進行一次全量複製,開銷非常大。在Redis2.8開始,網路閃斷之後,主從庫會採用增量複製的方式繼續同步,就只會把主從網路斷連期間主庫收到的命令同步給從庫。
增量複製核心在於repl_backlog_buffer這個緩衝區。當主從庫斷連後,主庫會把斷連期間收到的寫操作命令寫入replication buffer,同時也會寫入repl_backlog_buffer這個緩衝區。repl_backlog_buffer是一個環形緩衝區,主庫記錄自己寫到的位置,從庫也記錄自己讀到的位置。主從連線恢復之後,從庫首先給主庫傳送psync命令,並把自己當前的slave_repl_offset發給主庫,主庫會判斷自己的master_repl_offset和slave_repl_offset之間的差距,一般來說master_repl_offset會大於slave_repl_offset。此時,主庫只用把master_repl_offset和slave_repl_offset之間的命令操作同步給從庫就行。
sentinel,中文名哨兵。Redis的sentinel系統用於管理多個Redis範例,該系統主要執行以下四個任務:
1.監控(Monitoring):Sentinel會不斷的檢查主伺服器和從伺服器是否正常運作。
2.自動故障轉移(Automatic failover):當主節點不能正常工作時,哨兵會開始自動故障轉移操作,它會將失效主節點的其中一個從節點升級為新的主節點,並讓其他從節點改為複製新的主節點。
3.通知(Notification):哨兵可以將故障轉移的結果傳送給使用者端。
4.設定提供者(Configuration provider):使用者端在初始化時,通過連線哨兵來獲得當前Redis服務的主節點地址。
其中,監控和自動故障轉移功能,使得哨兵可以及時發現主節點故障並完成轉移;而設定提供者和通知功能,則需要在與使用者端的互動中才能體現。
哨兵用於實現Redis叢集的高可用性,本身也是分散式的,作為一個哨兵叢集去執行。Sentinel的程序之間使用流言協定(gossip protocols)來接收關於主伺服器是否下線的資訊, 並使用投票協定(agreement protocols)來決定是否執行自動故障遷移, 以及選擇哪個從伺服器作為新的主伺服器。下面分別介紹一下監控和自動故障轉移的基本原理:
1.每個 Sentinel 以每秒一次的頻率向它所知的主從伺服器以及其它 Sentinel 範例傳送一個 PING 命令。
2.如果一個範例距離最後一次有效回覆 PING 命令的時間超過指定的值, 那麼這個範例會被 Sentinel 標記為主觀下線。
3.正在監視這個主伺服器的所有 Sentinel 要以每秒一次的頻率確認主伺服器的確進入了主觀下線狀態。
4.有足夠數量的 Sentinel 在指定的時間範圍內同意這一判斷, 那麼這個主伺服器被標記為客觀下線。
5.每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有主從伺服器傳送 INFO 命令。當一個主伺服器被 Sentinel 標記為客觀下線時, Sentinel 向下線主伺服器的所有從伺服器傳送 INFO 命令的頻率會從 10 秒一次改為每秒一次。
6.Sentinel 和其它 Sentinel 協商主節點的狀態,如果主節點處於 ODOWN(客觀下線) 狀態,則投票自動選出新的主節點,將剩餘的從節點指向新的主節點進行資料複製。
7.當沒有足夠數量的 Sentinel 同意主伺服器 下線時, 主伺服器的客觀下線狀態就會被移除。主伺服器重新向 Sentinel 的 PING 命令返回有效回覆時,主伺服器主觀下線狀態就會被移除 。
哨兵是如何對Slave進行監控的呢?當然是通過Master來實現的,哨兵向Master傳送INFO命令,Master收到命令後便將Slave列表告訴哨兵。然後哨兵根據Slave列表資訊與每一個Slave建立連線,並且根據這個連線持續監控Slave。
故障轉移簡單來說有以下三個流程:
1.Sentinel系統挑選出現故障的主伺服器屬下的其中一個從伺服器,並將選中的從伺服器升級為新的主伺服器。
2.Sentinel系統向出現故障的主伺服器屬下的所有從伺服器傳送新的複製命令,讓他們成為新的主伺服器的從伺服器,當所有從伺服器都開始複製新的主伺服器時,故障轉移操作執行完畢。
3.Sentinel系統還會繼續監聽已下線的故障伺服器,如果它重新上線時,會將它設定為新的主伺服器的從伺服器。
示意圖
如上圖所示,Server1為Master節點,Server2、Server3、Server4為主伺服器Server1的從節點,而Sentinel系統正在監視所有4個伺服器
故障轉移
如上圖所示,主服務server1掛掉了,處於下線狀態,那麼server2、server3、server4對主伺服器的複製操作將被終止,Server2被Sentinel系統升級為新的Master,然後將Server2和Server3轉為新Master的從伺服器,完成故障轉移。同時繼續監聽已下線的Server1。
如上圖所示當Server1恢復後,Sentinel系統將它設定為新的主伺服器Server2的從伺服器,叢集恢復原有狀態。
Redis的哨兵模式基本已經可以實現高可用,讀寫分離 ,但是在這種模式下每臺Redis伺服器都儲存相同的資料,很浪費記憶體。所以在redis3.0上加入了Cluster叢集模式,實現了Redis的分散式儲存,也就是說每臺 Redis 節點上儲存不同的內容。Redis叢集是由多個主從節點群組成的分散式服務叢集,具有複製、高可用和分片特性。這種叢集模式沒有中心節點,可水平擴充套件,主要是針對海量資料、高並行、高可用的場景。
Cluster叢集模式主要有以下三個特性:
1.分片儲存:Redis3.0加入了 Redis 的叢集模式,實現了資料的分散式儲存,對資料進行分片,將不同的資料儲存在不同的master節點上面,從而解決了海量資料的儲存問題。
2.指令轉換:Redis叢集採用去中心化的思想,沒有中心節點的說法,對於使用者端來說,整個叢集可以看成一個整體,可以連線任意一個節點進行操作,就像操作單一Redis範例一樣,不需要任何代理中介軟體,當用戶端操作的key沒有分配到該node上時,Redis會返回轉向指令,指向正確的Redis節點。
3.主從和哨兵:Redis也內建了高可用機制,支援N個master節點,每個master節點都可以掛載多個slave節點,當master節點掛掉時,叢集會提升它的某個slave節點作為新的master節點。
如上圖所示,Redis叢集可以看成多個主從架構組合起來的,每一個主從架構可以看成一個節點。
叢集的整個資料庫被分為 16384 個槽(slot),資料庫中的每個鍵都屬於這 16384 個槽的其中一個,叢集中的每個節點可以處理 0 個或最多 16384 個槽。
Key 與雜湊槽對映過程可以分為兩大步驟:
1.根據鍵值對的 key,使用 CRC16 演演算法,計算出一個 16 bit 的值。
2.將 16 bit 的值對 16384 執行取模,得到 0 ~ 16383 的數表示 key 對應的雜湊槽。
另外,Cluster 還允許使用者強制某個 key 掛在特定槽位上,通過在 key 字串裡面嵌入 tag 標記,這就可以強制 key 所掛在的槽位等於 tag 所在的槽位。
使用者端直連 Redis 服務,進行讀寫操作時,若Key 對應的 Slot在當前直連的節點上,則可直接讀寫,但也有可能並不在當前直連的節點上,則經過「重定向」才能轉發到正確的節點,如下圖所示
和普通的查詢路由相比,Redis Cluster 藉助使用者端實現的請求路由是一種混合形式的查詢路由,它並非從一個 Redis 節點到另外一個 Redis,而是藉助使用者端轉發到正確的節點。實際應用中,可以在使用者端快取 Slot 與 Redis 節點的對映關係,當接收到 MOVED 響應時修改快取中的對映關係。如此,基於儲存的對映關係,請求時會直接傳送到正確的節點上,從而減少一次互動,提升效率。
那麼使用者端具體是怎麼確定存取的資料到底分佈在哪個範例上呢?
Redis 範例會將自己的雜湊槽資訊通過 Gossip 協定傳送給叢集中其他的範例,實現了雜湊槽分配資訊的擴散。這樣,叢集中的每個範例都有所有雜湊槽與範例之間的對映關係資訊。在切片資料的時候是將 key 通過 CRC16 計算出一個值再對 16384 取模得到對應的 Slot,這個計算任務可以在使用者端上執行傳送請求的時候執行。但是,定位到槽以後還需要進一步定位到該 Slot 所在 Redis 範例。當用戶端連線任何一個範例,範例就將雜湊槽與範例的對映關係響應給使用者端,使用者端就會將雜湊槽與範例對映資訊快取在本地。當用戶端請求時,會計算出鍵所對應的雜湊槽,在通過本地快取的雜湊槽範例對映資訊定位到資料所在範例上,再將請求傳送給對應的範例。
這個時候大家可能會有個疑問:雜湊槽與範例之間的對映關係由於新增範例或者負載均衡重新分配導致改變了咋辦?
叢集中的範例通過 Gossip 協定互相傳遞訊息獲取最新的雜湊槽分配資訊,但是,使用者端無法感知。Redis Cluster 提供了重定向機制:使用者端將請求傳送到範例上,這個範例沒有相應的資料,該 Redis 範例會告訴使用者端將請求傳送到其他的範例上。
Redis 如何告知使用者端重定向存取新範例呢?分為兩種情況:MOVED 錯誤、ASK 錯誤。
MOVED 錯誤(負載均衡,資料已經遷移到其他範例上):當用戶端將一個鍵值對操作請求傳送給某個範例,而這個鍵所在的槽並非由自己負責的時候,該範例會返回一個 MOVED 錯誤指引轉向正在負責該槽的節點。
(error) MOVED 16330 172.17.18.2:6379
該響應表示使用者端請求的鍵值對所在的雜湊槽 16330 遷移到了 172.17.18.2 這個範例上,埠是 6379。這樣使用者端就與 172.17.18.2:6379 建立連線,並行送 GET 請求。同時,使用者端還會更新本地快取,將該 slot 與 Redis 範例對應關係更新正確。
ASK 錯誤:如果某個 slot 的資料比較多,部分遷移到新範例,還有一部分沒有遷移咋辦?
如果請求的 key 在當前節點找到就直接執行命令,否則就需要 ASK 錯誤響應了,槽部分遷移未完成的情況下,如果需要存取的 key 所在 Slot 正在從從 範例 1 遷移到 範例 2,範例 1 會返回使用者端一條 ASK 報錯資訊:使用者端請求的 key 所在的雜湊槽正在遷移到範例 2 上,你先給範例 2 傳送一個 ASKING 命令,接著傳送操作命令。
(error) ASK 16330 172.17.18.2:6379
比如使用者端請求定位到 key的槽16330 在範例 172.17.18.1 上,節點1如果找得到就直接執行命令,否則響應 ASK 錯誤資訊,並指引使用者端轉向正在遷移的目標節點 172.17.18.2:6379
注意:ASK 錯誤指令並不會更新使用者端快取的雜湊槽分配資訊。所以使用者端再次請求 Slot 16330 的資料,還是會先給 172.17.18.1 範例傳送請求,只不過節點會響應 ASK 命令讓使用者端給新範例傳送一次請求。MOVED指令則更新使用者端本地快取,讓後續指令都發往新範例。
1.叢集的設定紀元 +1,是一個自曾計數器,初始值 0 ,每次執行故障轉移都會 +1。
2.檢測到主節點下線的從節點向叢集廣播一條
CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST訊息,要求所有收到這條訊息、並且具有投票權的主節點向這個從節點投票。
3.這個主節點尚未投票給其他從節點,那麼主節點將向要求投票的從節點返回一條
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK訊息,表示這個主節點支援從節點成為新的主節點。
4.參與選舉的從節點都會接收
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK訊息,如果收集到的票 >= (N/2) + 1 支援,那麼這個從節點就被選舉為新主節點。
5.如果在一個設定紀元裡面沒有從節點能收集到足夠多的支援票,那麼叢集進入一個新的設定紀元,並再次進行選舉,直到選出新的主節點為止。
流程如下圖所示:
Redis叢集的故障轉移主要有三個流程:故障檢測、選主流程、故障轉移,下面分別簡單介紹一下。
•故障檢查
一個節點認為某個節點失聯了並不代表所有的節點都認為它失聯了。只有當大多數負責處理 slot 的節點都認定了某個節點下線了,叢集才認為該節點需要進行主從切換。Redis 叢集節點採用 Gossip協定來廣播自己的狀態以及自己對整個叢集認知的改變。比如一個節點發現某個節點失聯了 (PFail),它會將這條資訊向整個叢集廣播,其它節點也就可以收到這個節點的失聯資訊。
如果一個節點收到了某個節點失聯的數量 (PFail Count) 已經達到了叢集的大多數,就可以標記該節點為確定下線狀態 (Fail),然後向整個叢集廣播,強迫其它節點也接收該節點已經下線的事實,並立即對該失聯節點進行主從切換。
•選主流程
參考上一節的「Cluster叢集選舉演演算法」
•故障轉移
當一個 Slave 發現自己的主節點進入已下線狀態後,從節點將開始對下線的主節點進行故障轉移。
1.從下線的 Master 節點的 Slave 節點列表選擇一個節點成為新主節點。
2.新主節點會復原所有對已下線主節點的 slot 指派,並將這些 slots 指派給自己。
3.新的主節點向叢集廣播一條 PONG 訊息,這條 PONG 訊息可以讓叢集中的其他節點立即知道這個節點已經由從節點變成了主節點,並且這個主節點已經接管了原本由已下線節點負責處理的槽。
4.新的主節點開始接收處理槽有關的命令請求,故障轉移完成。
擴容
Redis叢集主要有兩種擴容方式:垂直擴容和水平擴容。
垂直擴容:增加記憶體方式來增加快取範例的系統容量,比如從2G增加到4G。
水平擴容:通過增加節點的方式來增加整個快取系統的容量。
垂直擴容比較方便,但是受制於機制記憶體的限制,一個機器不可能無限增大記憶體, 所以到了一定階段肯定要進行水平擴容。下面我們主要講一下水平擴容。
水平擴容又有兩種方式:1、主節點數量不變;2、增加主節點數量
1、主節點數量不變:比如,當前有一臺物理機 A,構建了一個包含3個 Redis 範例的叢集;擴容時,我們新增一臺物理機 B,拉起一個 Redis 範例並加入物理機 A 的叢集;B 上 Redis 範例對 A 上的一個主節點進行復制,然後進行主備倒換;如此,Redis 叢集還是3個主節點,只不過變成了 A2-B1 的結構,將一部分請求壓力分擔到了新增的節點上,同時物理容量上限也會增加,主要步驟如下:
1.將新增節點加入叢集;
2.將新增節點設定為某個主節點的從節點,進而對其進行復制;
3.進行主備倒換,將新增的節點調整為主。
2、增加主節點數量:不增加主節點數量的方式擴容比較簡單,但是,從負載均衡的角度來看,並不是很好的選擇。例如,如果主節點數量較少,那麼單個節點所負責的 Slot 的數量必然較多,很容易出現大量 Key 的讀寫集中於少數節點的現象,而增加主節點的數量,可以更有效的分攤存取壓力,充分利用資源。主要步驟如下:
1.將新增節點加入叢集;
2.將叢集中的部分 Slot 遷移至新增的節點。
縮容
•如果下線的是slave,那麼通知其他節點忘記下線的節點
•如果下線的是master,那麼將此master的slot遷移到其他master之後,通知其他節點忘記此master節點
•其他節點都忘記了下線的節點之後,此節點就可以正常停止服務了
叢集搭建好之後,就可以對叢集的各種功能和使用進行測試了。一般我們會從兩個方面來制定測試計劃:1、叢集功能測試;2、叢集調優測試
叢集功能測試屬於最基本的測試,是為了驗證叢集所提供的各種功能是否能正常使用,主要有以下方面的內容:
•主從節點的資料備份是否正常
•主從節點的切換功能是否正常
•監控及故障轉移功能是否正常
•叢集擴縮容功能是否正常
叢集的功能測試類似於黑箱測試,內容比較簡單,在這裡我們就不展開介紹了,下面主要介紹一下叢集在使用過程中的調優測試。
叢集調優測試,是為了驗證叢集在提供服務時,如何最大限度避免因各種異常導致資料丟失或者快取功能失效,提前對設定進行調優或者提前預案,從而保證快取架構設計是最優的。需要注意的點主要有:叢集腦裂、快取穿透、快取擊穿、快取雪崩、快取預熱、快取降級、快取更新,下面分別介紹一下定義以及解決方案。
定義
當Redis主從叢集環境出現兩個主節點為使用者端提供服務,這時使用者端請求命令可能會發生資料丟失的情況。腦裂產生的場景主要有兩個:
1.如果哨兵正在進行選舉,故障轉移的過程中原主節點恢復和使用者端的通訊,那麼證明原主節點沒有真正的故障,這時使用者端依舊可以向原主節點正常通訊,但是當故障轉移結束後,就又產生了一個主節點,這就是腦裂產生的第一個場景,如下圖所示:
2. 網路分割區,主節點和使用者端,哨兵和從庫分割為了兩個網路,主庫和使用者端處在一個網路中,從庫和哨兵在另外一個網路中,此時哨兵也會發起主從切換,出現兩個主節點的情況,如下圖所示:
腦裂出現後帶來最嚴重的後果就是資料丟失,為什麼會出現資料丟失的問題呢?
主要原因是新主庫確定後會向所有的範例傳送slave of命令,讓所有範例重新進行全量同步,而全量同步首先就會將範例上的資料先清空,所以在主從同步期間在原主庫執行的命令將會被清空(上面場景二是同樣的道理,在網路分割區恢復後原主節點將被降級為從節點,並且執行全量同步導致資料丟失),所以這就是資料丟失的具體原因,如下圖所示:
解決方案
應對腦裂的解決辦法應該是去限制原主庫接收請求,Redis提供了兩個設定項:
1.min-replicas-to-write:主庫能進行資料同步的最少從庫數量,否則主節點拒絕寫入。
2.min-replicas-max-lag:主從庫間進行資料複製時,從庫給主庫傳送ACK訊息的最大延遲(單位s),否則主節點拒絕寫入。
這兩個設定項必須同時滿足,不然主節點拒絕寫入。
即使原主假故障,假故障期間也無法響應哨兵心跳,也不能和從庫進行同步,自然就無法和從庫進行ACK確認。這倆設定項組合要求就無法得到滿足,原主庫就會被限制接收使用者端請求,使用者端也就不能在原主庫中寫新資料。等新主上線,就只有新主能接收和處理使用者端請求,此時,新寫的資料會被直接寫到新主。而原主會被哨兵降為從庫,即使它的資料被清空,也不會有新資料的丟失。範例如下:
假設:
min-replicas-to-write=1
min-replicas-max-lag設為12s
哨兵的down-after-milliseconds設為10s
主庫因某原因卡住15s,導致哨兵判斷主庫客觀下線,開始進行主從切換。 同時,因原主庫卡住15s,沒有一個從庫能和原主庫在12s內進行資料複製,原主庫也無法接收使用者端請求。主從切換完成後,也只有新主庫能接收請求,不會發生腦裂,也就不會發生資料丟失。
但是,在實際應用中,真的能完全避免資料的丟失嗎?我們看下面的例子:
假設:
min-replicas-to-write 置 1
min-replicas-max-lag 設定為 15s
哨兵的down-after-milliseconds 設定為 10s 哨兵主從切換需要 5s,主庫因為某些原因卡住12s,此時,還會發生腦裂嗎?主從切換完成後,資料會丟失嗎?
主庫卡住 12s,達到哨兵設定的切換閾值,所以哨兵會觸發主從切換。但哨兵切換時間5s,即哨兵還未切換完成,主庫就會從阻塞狀態中恢復回來,且沒有觸發 min-slaves-max-lag 閾值,所以主庫在哨兵切換剩下的 3s 內,依舊可以接收使用者端的寫操作,如果這些寫操作還未同步到從庫,哨兵就把從庫提升為主庫了,那麼此時也會出現腦裂的情況,之後舊主庫降級為從庫,重新同步新主庫的資料,新主庫也會發生資料丟失。
所以,即使 Redis 設定了 min-replicas-to-write 和 min-replicas-max-lag,當腦裂發生時,還是無法嚴格保證資料不丟失,只是儘量減少資料的丟失。我們需要根據被測系統的效能、網路情況、流量情況來調優這兩個引數的設定,當出現異常的時候儘可能最小的縮減資料丟失的時間。
定義
當查詢Redis中沒有的資料時,該查詢會下沉到資料庫層,同時資料庫層也沒有該資料,當這種情況大量出現或被惡意攻擊時,介面的存取全部透過Redis存取資料庫,而資料庫中也沒有這些資料,我們稱這種現象為"快取穿透"。快取穿透會穿透Redis的保護,提升底層資料庫的負載壓力,同時這類穿透查詢沒有資料返回也造成了網路和計算資源的浪費。
解決方案
1.在介面存取層對使用者做校驗,如介面傳參、登陸狀態、n秒記憶體取介面的次數;
2.利用布隆過濾器,將資料庫層有的資料key儲存在位陣列中,以判斷存取的key在底層資料庫中是否存在;核心思想是布隆過濾器,在redis裡也有bitmap點陣圖的類似實現,布隆過濾器不能實現動態刪除,有時間可以研究下布穀鳥過濾器,是布隆過濾器增強版本。布隆過濾器有誤判率,雖然不能完全避免資料穿透的現象,但已經可以將99.99%的穿透查詢給遮蔽在Redis層了,極大的降低了底層資料庫的壓力,減少了資源浪費。
基於布隆過濾器,我們可以先將資料庫中資料的key儲存在布隆過濾器的位陣列中,每次使用者端查詢資料時先存取Redis:
•如果Redis內不存在該資料,則通過布隆過濾器判斷資料是否在底層資料庫內;
•如果布隆過濾器告訴我們該key在底層庫內不存在,則直接返回null給使用者端即可,避免了查詢底層資料庫的動作;
•如果布隆過濾器告訴我們該key極有可能在底層資料庫記憶體在,那麼將查詢下推到底層資料庫即可;
定義
快取擊穿和快取穿透從名詞上可能很難區分開來,它們的區別是:穿透表示底層資料庫沒有資料且快取內也沒有資料,擊穿表示底層資料庫有資料而快取內沒有資料。當熱點資料key從快取內失效時,大量存取同時請求這個資料,就會將查詢下沉到資料庫層,此時資料庫層的負載壓力會驟增,我們稱這種現象為"快取擊穿"。
解決方案
•延長熱點key的過期時間或者設定永不過期,如排行榜,首頁等一定會有高並行的介面;
•利用互斥鎖保證同一時刻只有一個使用者端可以查詢底層資料庫的這個資料,一旦查到資料就快取至Redis內,避免其他大量請求同時穿過Redis存取底層資料庫;
定義
快取雪崩是快取擊穿的"大面積"版,快取擊穿是資料庫快取到Redis內的熱點資料失效導致大量並行查詢穿過redis直接擊打到底層資料庫,而快取雪崩是指Redis中大量的key幾乎同時過期,然後大量並行查詢穿過redis擊打到底層資料庫上,此時資料庫層的負載壓力會驟增,我們稱這種現象為"快取雪崩"。
事實上快取雪崩相比於快取擊穿更容易發生,對於大多數公司來講,同時超大並行量存取同一個過時key的場景的確太少見了,而大量key同時過期,大量使用者存取這些key的機率相比快取擊穿來說明顯更大。
解決方案
1.在可接受的時間範圍內隨機設定key的過期時間,分散key的過期時間,以防止大量的key在同一時刻過期;
2.對於一定要在固定時間讓key失效的場景(例如每日12點準時更新所有最新排名),可以在固定的失效時間時在介面伺服器端設定隨機延時,將請求的時間打散,讓一部分查詢先將資料快取起來;
3.延長熱點key的過期時間或者設定永不過期,這一點和快取擊穿中的方案一樣;
如字面意思,當系統上線時,快取內還沒有資料,如果直接提供給使用者使用,每個請求都會穿過快取去存取底層資料庫,如果並行大的話,很有可能在上線當天就會宕機,因此我們需要在上線前先將資料庫內的熱點資料快取至Redis內再提供出去使用,這種操作就成為"快取預熱"。
快取預熱的實現方式有很多,比較通用的方式是寫個批任務,在啟動專案時或定時去觸發將底層資料庫內的熱點資料載入到快取內。
•快取降級是指當存取量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的效能時,即使是有損部分其他服務,仍然需要保證主服務可用。可以將其他次要服務的資料進行快取降級,從而提升主服務的穩定性。
•降級的目的是保證核心服務可用,即使是有損的。如某年雙十一的時候淘寶購物車無法修改地址只能使用預設地址,這個服務就是被降級了,這裡阿里保證了訂單可以正常提交和付款,但修改地址的服務可以在伺服器壓力降低,並行量相對減少的時候再恢復。
•降級可以根據實時的監控資料進行自動降級也可以設定開關人工降級。是否需要降級,哪些服務需要降級,在什麼情況下再降級,取決於大家對於系統功能的取捨。
快取服務(Redis)和資料服務(底層資料庫)是相互獨立且異構的系統,在更新快取或更新資料的時候無法做到原子性的同時更新兩邊的資料,因此在並行讀寫或第二步操作異常時會遇到各種資料不一致的問題。如何解決並行場景下更新操作的雙寫一致是快取系統的一個重要知識點。
參考檔案: 《Redis設計與實現》-黃健宏著
https://www.cnblogs.com/yizhiamumu/p/16704556.htmlhttps://cloud.tencent.com/developer/article/1981186