當redis 內存過大時, 必須使用集群。 而主從和sentinel 的方式都是一臺機器作為master,數據量過大時候, 還是撐不住。通常采取Cluster模式。下面文字參考了其他的一些文章。僅供自己總結。
redis集群的三種模式

通過持久化功能,Redis保證了即使在服務器重啟的情況下也不會丟失(或少量丟失)數據,因為持久化會把內存中數據保存到硬盤上,重啟會從硬盤上加載數據。但是由于數據是存儲在一臺服務器上的,如果這臺服務器出現硬盤故障等問題,也會導致數據都是。
為了避免單點故障,通常的做法是將數據庫復制多個副本以部署在不同的服務器上,這樣即使有一臺服務器出現故障,其他服務器已讓可以繼續(xù)提供服務。為此,**Redis提供了復制(replication)功能,可以實現當一臺數據庫中的數據更新后,自動將更新的數據同步到其他數據庫上。**
在復制的概念中,數據庫分為兩類,一類是主數據庫(master),另一類是從數據庫(slave)。主數據庫可以進行讀寫操作,當寫操作導致數據變化會自動將數據同步給從數據庫。而從數據庫一般是只讀的,并接受主數據庫同步過來的數據。一個主數據庫可以擁有多個從數據庫,而一個從數據庫只能擁有一個主數據庫。
主從數據庫的配置:
主數據庫不用配置,從數據庫的配置文件(redis.conf)中可以加載主數據庫的信息,也可以在啟動時,使用 redis-server --port 6380 --slaveof 127.0.0.1 6379 命令指明主數據庫的 IP 和端口。從數據庫一般是只讀,可以改為可寫,但寫入的數據很容易被主同步沒,所以還是只讀就可以。也可以在運行時使用 slaveof ip port 命令,停止原來的主,切換成剛剛設置的主 slaveof no one會把自己變成主。
主從復制原理:
- 從數據庫連接主數據庫,發(fā)送SYNC命令;
- 主數據庫接收到SYNC命令后,可以執(zhí)行BGSAVE命令生成RDB文件并使用緩沖區(qū)記錄此后執(zhí)行的所有寫命令;
- 主數據庫BGSAVE執(zhí)行完后,向所有從數據庫發(fā)送快照文件,并在發(fā)送期間繼續(xù)記錄被執(zhí)行的寫命令;
- 從數據庫收到快照文件后丟棄所有舊數據,載入收到的快照;
- 主數據庫快照發(fā)送完畢后開始向從數據庫發(fā)送緩沖區(qū)中的寫命令;
- 從數據庫完成對快照的載入,開始接受命令請求,并執(zhí)行來自主數據庫緩沖區(qū)的寫命令;(從數據庫初始化完成)
- 主數據庫每執(zhí)行一個寫命令就會向從數據庫發(fā)送相同的寫命令,從數據庫接收并執(zhí)行收到的寫命令(從數據庫初始化完成后的操作)
- 出現斷開重連后,2.8之后的版本會將斷線期間的命令傳給從數據庫,增量復制。
- 主從剛剛連接的時候,進行全量同步;全同步結束后,進行增量同步。當然,如果有需要,slave在任何時候都可以發(fā)起全量同步。Redis的策略是,無論如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步。
優(yōu)點:
- 支持主從復制,主機會自動將數據同步到從機,可以進行讀寫分離;
- 為了分載Master的讀操作壓力,Slave服務器可以為客戶端提供只讀操作的服務,寫服務依然必須由Master來完成;
- Slave同樣可以接受其他Slaves的連接和同步請求,這樣可以有效地分載Master的同步壓力;
- Master Server是以非阻塞的方式為Slaves提供服務。所以在Master-Slave同步期間,客戶端仍然可以提交查詢或修改請求;
- Slave Server同樣是以阻塞的方式完成數據同步。在同步期間,如果有客戶端提交查詢請求,Redis則范湖同步之前的數據。
缺點:
- Redis不具備自動容錯和恢復功能,主機從機的宕機都會導致前端部分讀寫請求失敗,需要等待機器重啟或者手動切換前端的IP才能恢復;
- 主機宕機,宕機前有部分數據未能及時同步到從機,切換IP后還會引入數據不一致的問題,降低了系統的可用性;
- 如果多個Slave斷線了,需要重啟的時候,盡量不要在同一時間段進行重啟。因為只要Slave啟動,就會發(fā)送sync請求和主機全量同步,當多個Slave重啟的時候,可能會導致Master IO劇增從而宕機。
- Redis較難支持在線擴容,在集群容量達到上限時在線擴容會變得很復雜;
二、哨兵模式
第一種主從同步/復制的模式,當主服務器宕機后,需要手動把一臺從服務器切換為主服務器,這就需要人工干預,費事費力,還會造成一段時間內服務不可用。這不是一種推薦的方式,更多時候,我們優(yōu)先考慮哨兵模式。
哨兵模式是一種特殊的模式,首先Redis提供了哨兵的命令,哨兵是一個獨立的進程,作為進程,它會獨立運行。其原理是哨兵通過發(fā)送命令,等待Redis服務器響應,從而監(jiān)控運行的多個Redis實例。

哨兵模式的作用:
- 通過發(fā)送命令,讓Redis服務器返回監(jiān)控其運行狀態(tài),包括主服務器和從服務器;
- 當哨兵監(jiān)測到master宕機,會自動將slave切換到master,然后通過發(fā)布訂閱模式通過其他的從服務器,修改配置文件,讓它們切換主機;
-
然而一個哨兵進程對Redis服務器進行監(jiān)控,也可能會出現問題,為此,我們可以使用多個哨兵進行監(jiān)控。各個哨兵之間還會進行監(jiān)控,這樣就形成了多哨兵模式。
image.png
故障切換的過程:
假設主服務器宕機,哨兵1先檢測到這個結果,系統并不會馬上進行 failover 過程,僅僅是哨兵1主觀的認為主服務器不可用,這個現象成為主觀下線。當后面的哨兵也檢測到主服務器不可用,并且數量達到一定值時,那么哨兵之間就會進行一次投票,投票的結果由一個哨兵發(fā)起,進行 failover 操作。切換成功后,就會通過發(fā)布訂閱模式,讓各個哨兵把自己監(jiān)控的從服務器實現切換主機,這個過程稱為客觀下線。這樣對于客戶端而言,一切都是透明的
哨兵模式的工作方式:
每個Sentinel(哨兵)進程以每秒鐘一次的頻率向整個集群中的Master主服務器,Slave從服務器以及其他Sentinel(哨兵)進程發(fā)送一個 PING 命令。
如果一個實例(instance)距離最后一次有效回復 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個實例會被 Sentinel(哨兵)進程標記為主觀下線(SDOWN)
如果一個Master主服務器被標記為主觀下線(SDOWN),則正在監(jiān)視這個Master主服務器的所有 Sentinel(哨兵)進程要以每秒一次的頻率確認Master主服務器的確進入了主觀下線狀態(tài)
當有足夠數量的 Sentinel(哨兵)進程(大于等于配置文件指定的值)在指定的時間范圍內確認Master主服務器進入了主觀下線狀態(tài)(SDOWN), 則Master主服務器會被標記為客觀下線(ODOWN)
在一般情況下, 每個 Sentinel(哨兵)進程會以每 10 秒一次的頻率向集群中的所有Master主服務器、Slave從服務器發(fā)送 INFO 命令。
當Master主服務器被 Sentinel(哨兵)進程標記為客觀下線(ODOWN)時,Sentinel(哨兵)進程向下線的 Master主服務器的所有 Slave從服務器發(fā)送 INFO 命令的頻率會從 10 秒一次改為每秒一次。
若沒有足夠數量的 Sentinel(哨兵)進程同意 Master主服務器下線, Master主服務器的客觀下線狀態(tài)就會被移除。若 Master主服務器重新向 Sentinel(哨兵)進程發(fā)送 PING 命令返回有效回復,Master主服務器的主觀下線狀態(tài)就會被移除。
優(yōu)點:
哨兵模式是基于主從模式的,所有主從的優(yōu)點,哨兵模式都具有。
主從可以自動切換,系統更健壯,可用性更高。
缺點:
Redis較難支持在線擴容,在集群容量達到上限時在線擴容會變得很復雜。
三、Cluster 集群
Redis 的哨兵模式基本已經可以實現高可用,讀寫分離 ,但是在這種模式下每臺 Redis 服務器都存儲相同的數據,很浪費內存,所以在redis3.0上加入了 Cluster 集群模式,實現了 Redis 的分布式存儲,也就是說每臺 Redis 節(jié)點上存儲不同的內容。

集群的配置
根據官方推薦,集群部署至少要 3 臺以上的master節(jié)點,最好使用 3 主 3 從六個節(jié)點的模式。在測試環(huán)境中,只能在一臺機器上面開啟6個服務實例來模擬。
1、修改配置文件
將 redis.conf 的配置文件復制6份(文件名最好加上端口后綴),然后開始修改配置文件中的參數
# 開啟redis的集群模式
cluster-enabled yes
# 配置集群模式下的配置文件
cluster-config-file nodes-6379.conf
# 集群內幾點之間支持最長響應時間
cluster-node-timeout 15000
2、修改完畢之后啟動 6 個 Redis 服務
3、快速部署集群
6個 Redis 服務啟動成功之后,借助 redis-tri.rb 工具可以快速的部署集群,如果本機沒有該命令行需要自行安裝,只需要執(zhí)行/redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385 就可以成功創(chuàng)建集群。
集群的特點
- 所有的redis節(jié)點彼此互聯(PING-PONG機制),內部使用二進制協議優(yōu)化傳輸速度和帶寬。
- 節(jié)點的fail是通過集群中超過半數的節(jié)點檢測失效時才生效。
- 客戶端與Redis節(jié)點直連,不需要中間代理層,客戶端不需要連接集群所有節(jié)點,連接集群中任何一個可用節(jié)點即可。
集群的工作方式
在 Redis 的每一個節(jié)點上,都有這么兩個東西,一個是插槽(slot),它的的取值范圍是:0-16383。還有一個就是cluster,可以理解為是一個集群管理的插件。當我們的存取的 Key到達的時候,Redis 會根據 crc16的算法得出一個結果,然后把結果對 16384 求余數,這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,通過這個值,去找到對應的插槽所對應的節(jié)點,然后直接自動跳轉到這個對應的節(jié)點上進行存取操作。
我比較好奇的是在進群工作條件下,如果增加節(jié)點,這個需要數據重新調整。 所有的數據需要rehash,來進行分配。如果數據非常大, 這個是非常耗時的操作。如果進行這方面的操作。redis 也有很好的解決方案。
一、集群伸縮原理
Redis集群提供了靈活的節(jié)點擴容和收縮方案。在不影響集群對外服務的情況下,可以為集群添加節(jié)點進行擴容也可以下線部分節(jié)點進行縮容:

我們都指導,這樣的每一個節(jié)點上面都分配了我們的16384槽中的幾個,以及對應槽下面的數據。所以我們在伸縮節(jié)點的時候,實質上也是對于哈希槽和槽對應數據的一個調整。
首先我們先來看一下經典集群搭建的分布圖:

三個主節(jié)點分別維護自己負責的槽和對應的數據,如果希望加入1個節(jié)點實現集群擴容時,需要通過相關命令把一部分槽和數據遷移給新節(jié)點:

總結:集群伸縮就是槽和數據在節(jié)點之間移動。
二、集群擴容
我們的集群在擴容的時候,是進行的增加節(jié)點的操作,故需要把已有節(jié)點上的槽和數據分配到新的節(jié)點上,那么應該把已有節(jié)點上的哪些槽和節(jié)點放在新節(jié)點上呢?讓我們來探究。
擴容是分布式存儲最常見的需求,Redis集群擴容操作可分為如下步驟:
準備新節(jié)點。
加入集群。
遷移槽和數據。
1.準備新節(jié)點
首先我們需要運行命令來啟動兩個新的redis節(jié)點:
redis-server conf/redis-6385.conf
redis-server conf/redis-6386.conf
啟動之后,這兩個redis節(jié)點為孤立的節(jié)點
2、加入集群
127.0.0.1:6379> cluster meet 127.0.0.1 6385
127.0.0.1:6379> cluster meet 127.0.0.1 6386
執(zhí)行上述命令加入已有的集群。


集群內新舊節(jié)點經過一段時間的ping/pong消息通信之后,所有節(jié)點會發(fā) 現新節(jié)點并將它們的狀態(tài)保存到本地。這個時間按照我們集群的約定時間來確定。
新節(jié)點剛開始都是主節(jié)點狀態(tài),但是由于沒有負責的槽,所以不能接受任何讀寫操作。對于新節(jié)點的后續(xù)操作我們一般有兩種選擇:
為它遷移槽和數據實現擴容。
作為其他主節(jié)點的從節(jié)點負責故障轉移。
redis-trib.rb工具也實現了為現有集群添加新節(jié)點的命令,還實現了直接添加為從節(jié)點的支持,命令如下:
redis-trib.rb add-node new_host:new_port existing_host:existing_port --slave --master-id <arg>
使用命令直接添加從節(jié)點:
redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379
redis-trib.rb add-node 127.0.0.1:6386 127.0.0.1:6379
3、遷移槽和數據
槽是Redis集群管理數據的基本單位,首先需要為新節(jié)點制定槽的遷移計劃,確定原有節(jié)點的哪些槽需要遷移到新節(jié)點。遷移計劃需要確保每個節(jié)點負責相似數量的槽,從而保證各節(jié)點的數據均勻。例如,在集群中加入 6385節(jié)點,如圖所示。加入6385節(jié)點后,原有節(jié)點負責的槽數量從 6380變?yōu)?096個。

槽遷移計劃確定后開始逐個把槽內數據從源節(jié)點遷移到目標節(jié)點。
遷移數據:
數據遷移過程是逐個槽進行的,流程如下:
1)對目標節(jié)點發(fā)送cluster setslot{slot}importing{sourceNodeId}命令,讓目標節(jié)點準備導入槽的數據。
2)對源節(jié)點發(fā)送cluster setslot{slot}migrating{targetNodeId}命令,讓源節(jié)點準備遷出槽的數據。
3)源節(jié)點循環(huán)執(zhí)行cluster getkeysinslot{slot}{count}命令,獲取count個 屬于槽{slot}的鍵。


4)在源節(jié)點上執(zhí)行migrate{targetIp}{targetPort}""0{timeout}keys{keys...} 命令,把獲取的鍵通過流水線(pipeline)機制批量遷移到目標節(jié)點,批量 遷移版本的migrate命令在Redis3.0.6以上版本提供,之前的migrate命令只能 單個鍵遷移。對于大量key的場景,批量鍵遷移將極大降低節(jié)點之間網絡IO次數。
5)重復執(zhí)行步驟3)和步驟4)直到槽下所有的鍵值數據遷移到目標節(jié)點。
6)向集群內所有主節(jié)點發(fā)送cluster setslot{slot}node{targetNodeId}命令,通知槽分配給目標節(jié)點。為了保證槽節(jié)點映射變更及時傳播,需要遍歷發(fā)送給所有主節(jié)點更新被遷移的槽指向新節(jié)點。
三、集群收縮
收縮集群意味著縮減規(guī)模,需要從現有集群中安全下線部分節(jié)點。


流程說明:
1)首先需要確定下線節(jié)點是否有負責的槽,如果是,需要把槽遷移到其他節(jié)點,保證節(jié)點下線后整個集群槽節(jié)點映射的完整性。
2)當下線節(jié)點不再負責槽或者本身是從節(jié)點時,就可以通知集群內其他節(jié)點忘記下線節(jié)點,當所有的節(jié)點忘記該節(jié)點后可以正常關閉。
1、下線遷移槽
下線節(jié)點需要把自己負責的槽遷移到其他節(jié)點,原理與之前節(jié)點擴容的 遷移槽過程一致。例如我們把6381和6384節(jié)點下線,節(jié)點信息如下:
127.0.0.1:6381> cluster nodes
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 myself,master - 0 0 2 connected
12288-16383 4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 slave
40b8d09d44294d2e2 3c7c768efc8fcd153446746 0 1469894180780 5 connected ...
6381是主節(jié)點,負責槽(12288-16383),6384是它的從節(jié)點,如圖所示。下線6381之前需要把負責的槽遷移到其他節(jié)點。

收縮正好和擴容遷移方向相反,6381變?yōu)樵垂?jié)點,其他主節(jié)點變?yōu)槟繕?節(jié)點,源節(jié)點需要把自身負責的4096個槽均勻地遷移到其他主節(jié)點上。
我們可以使用用redis-trib.rb reshard命令完成槽的遷移:
#redis-trib.rb reshard 127.0.0.1:6381
>>> Performing Cluster Check (using node 127.0.0.1:6381)
...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)1365
What is the receiving node ID cfb28ef1deee4e0fa78da86abe5d24566744411e /*輸入6379
節(jié)點id作為目標節(jié)點.*/
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1:40b8d09d44294d2e23c7c768efc8fcd153446746 /*源節(jié)點6381 id*/
Source node #2:done /* 輸入done確認 */
...
Do you want to proceed with the proposed reshard plan (yes/no) yes
...
這樣完成了1365個槽往6379節(jié)點上面遷移,同樣的工作做三遍,即可遷移所有節(jié)點到其他節(jié)點上。
2、忘記節(jié)點
由于集群內的節(jié)點不停地通過Gossip消息彼此交換節(jié)點狀態(tài),因此需要
通過一種健壯的機制讓集群內所有節(jié)點忘記下線的節(jié)點。也就是說讓其他節(jié) 點不再與要下線節(jié)點進行Gossip消息交換。Redis提供了cluster forget{downNodeId}命令實現該功能:
當節(jié)點接收到cluster forget{down NodeId}命令后,會把nodeId指定的節(jié) 點加入到禁用列表中,在禁用列表內的節(jié)點不再發(fā)送Gossip消息。禁用列表 有效期是60秒,超過60秒節(jié)點會再次參與消息交換。也就是說當第一次 forget命令發(fā)出后,我們有60秒的時間讓集群內的所有節(jié)點忘記下線節(jié)點。
線上操作不建議直接使用cluster forget命令下線節(jié)點,需要跟大量節(jié)點 命令交互,實際操作起來過于繁瑣并且容易遺漏forget節(jié)點。建議使用redistrib.rb del-node{host:port}{downNodeId}命令。
redis-trib.rb del-node 127.0.0.1:6379 4fa7eac4080f0b667ffeab9b87841da49b84a6e4 # 從節(jié)點6384 id
redis-trib.rb del-node 127.0.0.1:6379 40b8d09d44294d2e23c7c768efc8fcd153446746 # 主節(jié)點6381 id
