作者:京東零售 李磊
redis集群介紹
Redis集群一般有四種方式,分別為:主從復制、哨兵模式、Cluster以及各大廠的集群方案。在3.0版本之前只支持單實例模式,3.0之后支持了集群方式。在3.0之前各大廠為了解決單實例Redis的存儲瓶頸問題各自推出了自己的集群方案,其核心思想就是數據分片,主要有客戶端分片、代理分片、服務端分片。這里咱們只介紹前三種方式:主從、哨兵、Cluster。
1、主從復制
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之間的命令操作同步給從庫就行。
2、哨兵模式
sentinel,中文名哨兵。Redis的sentinel系統用于管理多個Redis實例,該系統主要執行以下四個任務:
1.監控(Monitoring):Sentinel會不斷的檢查主服務器和從服務器是否正常運作。
2.自動故障轉移(Automatic fAIlover):當主節點不能正常工作時,哨兵會開始自動故障轉移操作,它會將失效主節點的其中一個從節點升級為新的主節點,并讓其他從節點改為復制新的主節點。
3.通知(Notification):哨兵可以將故障轉移的結果發送給客戶端。
4.配置提供者(Configuration provider):客戶端在初始化時,通過連接哨兵來獲得當前Redis服務的主節點地址。
其中,監控和自動故障轉移功能,使得哨兵可以及時發現主節點故障并完成轉移;而配置提供者和通知功能,則需要在與客戶端的交互中才能體現。
哨兵用于實現Redis集群的高可用性,本身也是分布式的,作為一個哨兵集群去運行。Sentinel的進程之間使用流言協議(gossip protocols)來接收關于主服務器是否下線的信息, 并使用投票協議(agreement protocols)來決定是否執行自動故障遷移, 以及選擇哪個從服務器作為新的主服務器。下面分別介紹一下監控和自動故障轉移的基本原理:
Sentinel集群監控原理
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。
Sentinel集群故障自動轉移
故障轉移簡單來說有以下三個流程:
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的從服務器,集群恢復原有狀態。
3、Cluster集群
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集群可以看成多個主從架構組合起來的,每一個主從架構可以看成一個節點。
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 所在的槽位。?
Cluster集群請求路由方式
客戶端直連 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指令則更新客戶端本地緩存,讓后續指令都發往新實例。
Cluster集群選舉算法
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.如果在一個配置紀元里面沒有從節點能收集到足夠多的支持票,那么集群進入一個新的配置紀元,并再次進行選舉,直到選出新的主節點為止。
流程如下圖所示:?
Cluster集群故障轉移
Redis集群的故障轉移主要有三個流程:故障檢測、選主流程、故障轉移,下面分別簡單介紹一下。
•故障檢查
一個節點認為某個節點失聯了并不代表所有的節點都認為它失聯了。只有當大多數負責處理 slot 的節點都認定了某個節點下線了,集群才認為該節點需要進行主從切換。Redis 集群節點采用 Gossip協議來廣播自己的狀態以及自己對整個集群認知的改變。比如一個節點發現某個節點失聯了 (PFail),它會將這條信息向整個集群廣播,其它節點也就可以收到這個節點的失聯信息。
如果一個節點收到了某個節點失聯的數量 (PFail Count) 已經達到了集群的大多數,就可以標記該節點為確定下線狀態 (Fail),然后向整個集群廣播,強迫其它節點也接收該節點已經下線的事實,并立即對該失聯節點進行主從切換。
•選主流程
參考上一節的“Cluster集群選舉算法”
•故障轉移
當一個 Slave 發現自己的主節點進入已下線狀態后,從節點將開始對下線的主節點進行故障轉移。
1.從下線的 Master 節點的 Slave 節點列表選擇一個節點成為新主節點。
2.新主節點會撤銷所有對已下線主節點的 slot 指派,并將這些 slots 指派給自己。
3.新的主節點向集群廣播一條 PONG 消息,這條 PONG 消息可以讓集群中的其他節點立即知道這個節點已經由從節點變成了主節點,并且這個主節點已經接管了原本由已下線節點負責處理的槽。
4.新的主節點開始接收處理槽有關的命令請求,故障轉移完成。
Cluster集群擴容與縮容
擴容
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節點
•其他節點都忘記了下線的節點之后,此節點就可以正常停止服務了
Redis集群測試思路及常見問題
集群搭建好之后,就可以對集群的各種功能和使用進行測試了。一般我們會從兩個方面來制定測試計劃:1、集群功能測試;2、集群調優測試
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.html https://cloud.tencent.com/developer/article/1981186