原文:https://blog.csdn.net/zy345293721/article/details/87536144
1.集群
先來簡單了解下redis中提供的集群策略, 雖然redis有持久化功能能夠保障redis服務器宕機也能恢復並且只有少量的數據損失,但是由於所有數據在一台服務器上,如果這台服務器出現硬盤故障,那就算是有備份也仍然不可避免數據丟失的問題。
在實際生產環境中,我們不可能只使用一台redis服務器作為我們的緩存服務器,必須要多台實現集群,避免出現單點故障;
2.主從復制
復制的作用是把redis的數據庫復制多個副本部署在不同的服務器上,如果其中一台服務器出現故障,也能快速遷移到其他服務器上提供服務。 復制功能可以實現當一台redis服務器的數據更新后,自動將新的數據同步到其他服務器上主從復制就是我們常見的master/slave模式, 主數據庫可以進行讀寫操作,當寫操作導致數據發生變化時會自動將數據同步給從數據庫。而一般情況下,從數據庫是只讀的,並接收主數據庫同步過來的數據。 一個主數據庫可以有多個從數據庫:
3.配置
在redis中配置master/slave是非常容易的,只需要在從數據庫的配置文件中加入slaveof 主數據庫地址 端口。 而master 數據庫不需要做任何改變准備兩台服務器,分別安裝redis , server1 server2:
1)在server2的redis.conf文件中增加 slaveof server1-ip 6379,同時將bindip注釋掉,允許所有ip訪問
2) 啟動server2
3) 訪問server2的redis客戶端,輸入 INFO replication
4) 通過在master機器上輸入命令,比如set foo bar 、 在slave服務器就能看到該值已經同步過來了
4.原理
1)全量復制
2)增量復制
3)無磁盤復制
1))全量復制
Redis全量復制一般發生在Slave初始化階段,這時Slave需要將Master上的所有數據都復制一份。具體步驟:
完成上面幾個步驟后就完成了slave服務器數據初始化的所有操作,savle服務器此時可以接收來自用戶的讀請求。master/slave 復制策略是采用樂觀復制,也就是說可以容忍在一定時間內master/slave數據的內容是不同的,但是兩者的數據會最終同步。具體來說,redis的主從同步過程本身是異步的,意味着master執行完客戶端請求的命令后會立即返回結果給客戶端,然后異步的方式把命令同步給slave。
這一特征保證啟用master/slave后 master的性能不會受到影響。但是另一方面,如果在這個數據不一致的窗口期間,master/slave因為網絡問題斷開連接,而這個時候,master是無法得知某個命令最終同步給了多少個slave數據庫。不過redis提供了一個配置項來限制只有數據至少同步給多少個slave的時候,master才是可寫的:
min-slaves-to-write 3 表示只有當3個或以上的slave連接到master,master才是可寫的;
min-slaves-max-lag 10 表示允許slave最長失去連接的時間,如果10秒還沒收到slave的響應,則master認為該slave以斷開
2))增量復制
從redis 2.8開始,就支持主從復制的斷點續傳,如果主從復制過程中,網絡連接斷掉了,那么可以接着上次復制的地方,繼續復制下去,而不是從頭開始復制一份master node會在內存中創建一個backlog,master和slave都會保存一個replica offset還有一個master id,offset就是保存在backlog中的。如果master和slave網絡連接斷掉了,slave會讓master從上次的replica offset開始繼續復制但是如果沒有找到對應的offset,那么就會執行一次全量同步;
3))無硬盤復制
前面我們說過,Redis復制的工作原理基於RDB方式的持久化實現的,也就是master在后台保存RDB快照,slave接收到rdb文件並載入,但是這種方式會存在一些問題:
\1. 當master禁用RDB時,如果執行了復制初始化操作,Redis依然會生成RDB快照,當master下次啟動時執行該RDB文件的恢復,但是因為復制發生的時間點不確定,所以恢復的數據可能是任何時間點的。就會造成數據出現問題。
\2. 當硬盤性能比較慢的情況下(網絡硬盤),那初始化復制過程會對性能產生影響因此2.8.18以后的版本,Redis引入了無硬盤復制選項,可以不需要通過RDB文件去同步,直接發送數據,通過以下配置來開啟該功能
repl-diskless-sync yes
master**在內存中直接創建rdb,然后發送給slave,不會在自己本地落地磁盤了。
5.哨兵機制
在前面講的master/slave模式,在一個典型的一主多從的系統中,slave在整個體系中起到了數據冗余備份和讀寫分離的作用。當master遇到異常終端后,需要從slave中選舉一個新的master繼續對外提供服務,這種機制在前面提到過N次,比如在zk中通過leader選舉、kafka中可以基於zk的節點實現master選舉。所以在redis中也需要一種機制去實現master的決策,redis並沒有提供自動master選舉功能,而是需要借助一個哨兵來進行監控。
6.什么是哨兵
顧名思義,哨兵的作用就是監控Redis系統的運行狀況,它的功能包括兩個:
\1. 監控master和slave是否正常運行
\2. master出現故障時自動將slave數據庫升級為master,哨兵是一個獨立的進程,使用哨兵后的架構圖:
為了解決master選舉問題,又引出了一個單點問題,也就是哨兵的可用性如何解決,在一個一主多從的Redis系統中,可以使用多個哨兵進行監控任務以保證系統足夠穩定。此時哨兵不僅會監控master和slave,同時還會互相監控;這種方式稱為哨兵集群,哨兵集群需要解決故障發現、和master決策的協商機制問題:
sentinel之間的相互感知:
sentinel節點之間會因為共同監視同一個master從而產生了關聯,一個新加入的sentinel節點需要和其他監視相同master節點的sentinel相互感知,首先
\1. 需要相互感知的sentinel都向他們共同監視的master節點訂閱channel:sentinel:hello
\2. 新加入的sentinel節點向這個channel發布一條消息,包含自己本身的信息,這樣訂閱了這個channel的sentinel
就可以發現這個新的sentinel
\3. 新加入得sentinel和其他sentinel節點建立長連接
7.master的故障發現
sentinel節點會定期向master節點發送心跳包來判斷存活狀態,一旦master節點沒有正確響應,sentinel會把master設置為“主觀不可用狀態”,然后它會把“主觀不可用”發送給其他所有的sentinel節點去確認,當確認的sentinel節點數大於>quorum時,則會認為master是“客觀不可用”,接着就開始進入選舉新的master流程;但是這里又會遇到一個問題,就是sentinel中,本身是一個集群,如果多個節點同時發現master節點達到客觀不可用狀態,那誰來決策選擇哪個節點作為maste呢?這個時候就需要從sentinel集群中選擇一個leader來做決策。而這里用到了一致性算法Raft算法、它和Paxos算法類似,都是分布式一致性算法。但是它比Paxos算法要更容易理解;Raft和Paxos算法一樣,也是基於投票算法,只要保證過半數節點通過提議即可;
8.配置實現
通過在這個配置的基礎上增加哨兵機制。在其中任意一台服務器上創建一個sentinel.conf文件,文件內容
sentinel monitor name ip port quorum
其中name表示要監控的master的名字,這個名字是自己定義。 ip和port表示master的ip和端口號。 最后一個1表示最低通過票數,也就是說至少需要幾個哨兵節點統一才可以,后面會具體講解:
port 6040
sentinel monitor mymaster 192.168.11.131 6379 1
sentinel down-after-milliseconds mymaster 5000 --表示如果5s內mymaster沒響應,就認為SDOWN
sentinel failover-timeout mymaster 15000 --表示如果15秒后,mysater仍沒活過來,則啟動failover,從剩下的slave中選一個升級為master
兩種方式啟動哨兵
redis-sentinel sentinel.conf
redis-server /path/to/sentinel.conf --sentinel
哨兵監控一個系統時,只需要配置監控master即可,哨兵會自動發現所有slave;
這時候,我們把master關閉,等待指定時間后(默認是30秒),會自動進行切換,會輸出如下消息:
+sdown表示哨兵主管認為master已經停止服務了,+odown表示哨兵客觀認為master停止服務了。關於主觀和客觀,后面會給大家講解。接着哨兵開始進行故障恢復,挑選一個slave升級為master
+try-failover表示哨兵開始進行故障恢復
+failover-end 表示哨兵完成故障恢復
+slave表示列出新的master和slave服務器,我們仍然可以看到已經停掉的master,哨兵並沒有清楚已停止的服務的實例,這是因為已經停止的服務器有可能會在某個時間進行恢復,恢復以后會以slave角色加入到整個集群中。
9.Redis-Cluster
即使是使用哨兵,此時的Redis集群的每個數據庫依然存有集群中的所有數據,從而導致集群的總數據存儲量受限於可用存儲內存最小的節點,形成了木桶效應。而因為Redis是基於內存存儲的,所以這一個問題在redis中就顯得尤為突出了
在redis3.0之前,我們是通過在客戶端去做的分片,通過hash環的方式對key進行分片存儲。分片雖然能夠解決各個節點的存儲壓力,但是導致維護成本高、增加、移除節點比較繁瑣。因此在redis3.0以后的版本最大的一個好處就是支持集群功能,集群的特點在於擁有和單機實例一樣的性能,同時在網絡分區以后能夠提供一定的可訪問性以及對主數據庫故障恢復的支持。
哨兵和集群是兩個獨立的功能,當不需要對數據進行分片使用哨兵就夠了,如果要進行水平擴容,集群是一個比較好的方式。
拓撲結構
一個Redis Cluster由多個Redis節點構成。不同節點組服務的數據沒有交集,也就是每個一節點組對應數據sharding的一個分片。節點組內部分為主備兩類節點,對應master和slave節點。兩者數據准實時一致,通過異步化的主備復制機制來保證。一個節點組有且只有一個master節點,同時可以有0到多個slave節點,在這個節點組中只有master節點對用戶提供些服務,讀服務可以由master或者slave提供。
redis-cluster是基於gossip協議實現的無中心化節點的集群,因為去中心化的架構不存在統一的配置中心,各個節點對整個集群狀態的認知來自於節點之間的信息交互。在Redis Cluster,這個信息交互是通過Redis Cluster Bus來完成的。
10.Redis的數據分區
分布式數據庫首要解決把整個數據集按照分區規則映射到多個節點的問題,即把數據集划分到多個節點上,每個節
點負責整個數據的一個子集, Redis Cluster采用哈希分區規則,采用虛擬槽分區。
虛擬槽分區巧妙地使用了哈希空間,使用分散度良好的哈希函數把所有的數據映射到一個固定范圍內的整數集合,整數定義為槽(slot)。比如Redis Cluster槽的范圍是0 ~ 16383。槽是集群內數據管理和遷移的基本單位。采用大范圍的槽的主要目的是為了方便數據的拆分和集群的擴展,每個節點負責一定數量的槽。計算公式:slot = CRC16(key)%16383。每一個節點負責維護一部分槽以及槽所映射的鍵值數據。
11.HashTags
通過分片手段,可以將數據合理的划分到不同的節點上,這本來是一件好事。但是有的時候,我們希望對相關聯的業務以原子方式進行操作。舉個簡單的例子。
我們在單節點上執行MSET , 它是一個原子性的操作,所有給定的key會在同一時間內被設置,不可能出現某些指定的key被更新另一些指定的key沒有改變的情況。但是在集群環境下,我們仍然可以執行MSET命令,但它的操作不在是原子操作,會存在某些指定的key被更新,而另外一些指定的key沒有改變,原因是多個key可能會被分配到不同的機器上。所以,這里就會存在一個矛盾點,及要求key盡可能的分散在不同機器,又要求某些相關聯的key分配到相同機器。這個也是在面試的時候會容易被問到的內容。怎么解決呢?
從前面的分析中我們了解到,分片其實就是一個hash的過程,對key做hash取模然后划分到不同的機器上。所以為了解決這個問題,我們需要考慮如何讓相關聯的key得到的hash值都相同呢?如果key全部相同是不現實的,所以怎么解決呢?在redis中引入了HashTag的概念,可以使得數據分布算法可以根據key的某一個部分進行計算,然后讓相關的key落到同一個數據分片舉個簡單的例子,加入對於用戶的信息進行存儲, user:user1:id、user:user1:name/ 那么通過hashtag的方式,user:{user1}:id、user:{user1}.name; 表示當一個key包含 {} 的時候,就不對整個key做hash,而僅對 {} 包括的字符串做hash。
12.重定向客戶端
Redis Cluster並不會代理查詢,那么如果客戶端訪問了一個key並不存在的節點,這個節點是怎么處理的呢?比如我想獲取key為msg的值,msg計算出來的槽編號為254,當前節點正好不負責編號為254的槽,那么就會返回客戶端下面信息:
-MOVED 254 127.0.0.1:6381
表示客戶端想要的254槽由運行在IP為127.0.0.1,端口為6381的Master實例服務。如果根據key計算得出的槽恰好由當前節點負責,則當期節點會立即返回結果。
13.分片遷移
在一個穩定的Redis cluster下,每一個slot對應的節點是確定的,但是在某些情況下,節點和分片對應的關系會發生變更
\1. 新加入master節點
\2. 某個節點宕機
也就是說當動態添加或減少node節點時,需要將16384個槽做個再分配,槽中的鍵值也要遷移。當然,這一過程,在目前實現中,還處於半自動狀態,需要人工介入。
新增一個主節點
新增一個節點D,redis cluster的這種做法是從各個節點的前面各拿取一部分slot到D上。大致就會變成這樣:
節點A覆蓋1365-5460
節點B覆蓋6827-10922
節點C覆蓋12288-16383
節點D覆蓋0-1364,5461-6826,10923-12287
刪除一個主節點
先將節點的數據移動到其他節點上,然后才能執行刪除。
14.槽遷移的過程
槽遷移的過程中有一個不穩定狀態,這個不穩定狀態會有一些規則,這些規則定義客戶端的行為,從而使得RedisCluster不必宕機的情況下可以執行槽的遷移。下面這張圖描述了我們遷移編號為1、2、3的槽的過程中,他們在MasterA節點和MasterB節點中的狀態。
簡單的工作流程
\1. 向MasterB發送狀態變更命令,吧Master B對應的slot狀態設置為IMPORTING
\2. 向MasterA發送狀態變更命令,將Master對應的slot狀態設置為MIGRATING
當MasterA的狀態設置為MIGRANTING后,表示對應的slot正在遷移,為了保證slot數據的一致性,MasterA此時對於slot內部數據提供讀寫服務的行為和通常狀態下是有區別的。
MIGRATING狀態
當MasterB的狀態設置為IMPORTING后,表示對應的slot正在向MasterB遷入,及時Master仍然能對外提供該slot的讀寫服務,但和通常狀態下也是有區別的
\1. 當來自客戶端的正常訪問不是從ASK跳轉過來的,說明客戶端還不知道遷移正在進行,很有可能操作了一個目前還沒遷移完成的並且還存在於MasterA上的key,如果此時這個key在A上已經被修改了,那么B和A的修改則會發生沖突。所以對於MasterB上的slot上的所有非ASK跳轉過來的操作,MasterB都不會uu出去護理,而是通過MOVED命令讓客戶端跳轉到MasterA上去執行這樣的狀態控制保證了同一個key在遷移之前總是在源節點上執行,遷移后總是在目標節點上執行,防止出現兩邊
同時寫導致的沖突問題。而且遷移過程中新增的key一定會在目標節點上執行,源節點也不會新增key,是的整個遷移過程既能對外正常提供服務,又能在一定的時間點完成slot的遷移。