redis(二)集群 redis-cluster & redis主從同步


參考文檔:

http://geek.csdn.net/news/detail/200023

redis主從復制:https://blog.csdn.net/imxiangzi/article/details/52400877

設計原則和初衷

  • 性能:這是Redis賴以生存的看家本領,增加集群功能后當然不能對性能產生太大影響,所以Redis采取了P2P而非Proxy方式、異步復制、客戶端重定向等設計,而犧牲了部分的一致性、使用性。
  • 水平擴展:集群的最重要能力當然是擴展,文檔中稱可以線性擴展到1000結點。
  • 可用性:在Cluster推出之前,可用性要靠Sentinel保證。有了集群之后也自動具有了Sentinel的監控和自動Failover能力。

redis-cluster設計

Redis-Cluster采用無中心結構,每個節點保存數據和整個集群狀態,每個節點都和其他所有節點連接。

其結構設計:

  • Redis Cluster中節點負責存儲數據,記錄集群狀態,集群節點能自動發現其他節點,檢測出節點的狀態,並在需要時剔除故障節點,提升新的主節點
  • Redis Cluster中所有節點通過PING-PONG機制彼此互聯,使用一個二級制協議(Cluster Bus) 進行通信,優化傳輸速度和帶寬。發現新的節點、發送PING包、特定情況下發送集群消息,集群連接能夠發布與訂閱消息。
  • 客戶端和集群中的節點直連,不需要中間的Proxy層。理論上而言,客戶端可以自由地向集群中的所有節點發送請求,但是每次不需要連接集群中的所有節點,只需要連接集群中任何一個可用節點即可。當客戶端發起請求后,接收到重定向(MOVED\ASK)錯誤,會自動重定向到其他節點,所以客戶端無需保存集群狀態。不過客戶端可以緩存鍵值和節點之間的映射關系,這樣能明顯提高命令執行的效率。
  • Redis Cluster中節點之間使用異步復制,在分區過程中存在窗口,容易導致丟失寫入的數據,集群即使努力嘗試所有寫入,但是以下兩種情況可能丟失數據:
    • 命令操作已經到達主節點,但在主節點回復的時候,寫入可能還沒有通過主節點復制到從節點那里。如果這時主節點宕機了,這條命令將永久丟失。以防主節點長時間不可達而它的一個從節點已經被提升為主節點。
    • 分區導致一個主節點不可達,然而集群發送故障轉移(failover),提升從節點為主節點,原來的主節點再次恢復。一個沒有更新路由表(routing table)的客戶端或許會在集群把這個主節點變成一個從節點(新主節點的從節點)之前對它進行寫入操作,導致數據徹底丟失。
  • Redis集群的節點不可用后,在經過集群半數以上Master節點與故障節點通信超過cluster-node-timeout時間后,認為該節點故障,從而集群根據自動故障機制,將從節點提升為主節點。這時集群恢復可用。

redis cluster 數據分片

Redis Cluster在設計中沒有使用一致性哈希(Consistency Hashing),而是使用數據分片(Sharding)引入哈希槽(hash slot)來實現;一個 Redis Cluster包含16384(0~16383)個哈希槽,存儲在Redis Cluster中的所有鍵都會被映射到這些slot中,集群中的每個鍵都屬於這16384個哈希槽中的一個,集群使用公式slot=CRC16(key)/16384來計算key屬於哪個槽,其中CRC16(key)語句用於計算key的CRC16 校驗和。

集群中的每個主節點(Master)都負責處理16384個哈希槽中的一部分,當集群處於穩定狀態時,每個哈希槽都只由一個主節點進行處理,每個主節點可以有一個到N個從節點(Slave),當主節點出現宕機或網絡斷線等不可用時,從節點能自動提升為主節點進行處理。  

現在我們是三個主節點分別是:A, B, C 三個節點,它們可以是一台機器上的三個端口,也可以是三台不同的服務器。那么,采用哈希槽 (hash slot)的方式來分配16384個slot 的話,它們三個節點分別承擔的slot 區間是:
      節點A覆蓋0-5460;
      節點B覆蓋5461-10922;
      節點C覆蓋10923-16383.
     獲取數據:
      如果存入一個值,按照redis cluster哈希槽的算法: CRC16('key')384 = 6782。 那么就會把這個key 的存儲分配到 B 上了。同樣,當我連接(A,B,C)任何一個節點想獲取'key'這個key時,也會這樣的算法,然后內部跳轉到B節點上獲取數據 ,如圖:


     新增一個主節點:
    新增一個節點D,redis cluster的這種做法是從各個節點的前面各拿取一部分slot到D上,我會在接下來的實踐中實驗。大致就會變成這樣:
    節點A覆蓋1365-5460
    節點B覆蓋6827-10922
    節點C覆蓋12288-16383
    節點D覆蓋0-1364,5461-6826,10923-12287
     同樣刪除一個節點也是類似,移動完成后就可以刪除這個節點了。

優勢

  • 無中心架構
  • 數據按照slot存儲分布在多個節點,節點間數據共享,可動態調整數據分布
  • 可擴展性,可線性擴展到1000個節點,節點可動態添加或刪除
  • 高可用性,部分節點不可用時,集群仍可用。通過增加Slave做standby數據副本,能夠實現故障自動failover,節點之間通過gossip協議交換狀態信息,用投票機制完成Slave到Master的角色提升
  • 降低運維成本,提高系統的擴展性和可用性

不足

  • Client實現復雜,驅動要求實現Smart Client,緩存slots mapping信息並及時更新,提高了開發難度,客戶端的不成熟影響業務的穩定性。目前僅JedisCluster相對成熟,異常處理部分還不完善,比如常見的“max redirect exception”
  • 節點會因為某些原因發生阻塞(阻塞時間大於clutser-node-timeout),被判斷下線,這種failover是沒有必要的
  • 數據通過異步復制,不保證數據的強一致性
  • 多個業務使用同一套集群時,無法根據統計區分冷熱數據,資源隔離性較差,容易出現相互影響的情況
  • Slave在集群中充當“冷備”,不能緩解讀壓力,當然可以通過SDK的合理設計來提高Slave資源的利用率

Redis 主從模式
          redis cluster 為了保證數據的高可用性,加入了主從模式,一個主節點對應一個或多個從節點,主節點提供數據存取,從節點則是從主節點拉取數據備份,當這個主節點掛掉后,就會有這個從節點選取一個來充當主節點,從而保證集群不會掛掉。
         上面那個例子里, 集群有ABC三個主節點, 如果這3個節點都沒有加入從節點,如果B掛掉了,我們就無法訪問整個集群了。A和C的slot也無法訪問。所以我們在集群建立的時候,一定要為每個主節點都添加了從節點, 比如像這樣, 集群包含主節點A、B、C, 以及從節點A1、B1、C1, 那么即使B掛掉系統也可以繼續正確工作。B1節點替代了B節點,所以Redis集群將會選擇B1節點作為新的主節點,集群將會繼續正確地提供服務。 當B重新開啟后,它就會變成B1的從節點。不過需要注意,如果節點B和B1同時掛了,Redis集群就無法繼續正確地提供服務了

redis主從復制的一些特點:

1)master可以有多個slave
2)除了多個slave連到相同的master外,slave也可以連接其他slave形成圖狀結構
3)主從復制不會阻塞master。也就是說當一個或多個slave與master進行初次同步數據時,master可以繼續處理client發來的請求。相反slave在初次同步數據時則會阻塞不能處理client的請求
4)主從復制可以用來提高系統的可伸縮性,我們可以用多個slave專門用於client的讀請求,比如sort操作可以使用slave來處理。也可以用來做簡單的數據冗余
5)可以在master禁用數據持久化,只需要注釋掉master配置文件中的所有save配置,然后只在slave上配置數據持久化

redis的主從復制分為兩個階段:

1)同步操作:將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態
2)命令傳播:在主服務器的數據庫狀態被修改,導致主從服務器的數據庫狀態出現不一致時,主服務器會將自己執行的寫命令送給從服務器執行

同步操作的過程(2.8版本以后):

1)設置主服務器地址和端口,通過調用SAVEOF <master_ip> <master_port>命令
2)建立套接字連接
3)發送PING命令,檢查主從服務器是否能夠正常處理命令
4)身份驗證,從服務器設置了masterauth並且主服務器設置了requirepass是需要進行身份驗證。這兩個選項要么都設置要么都不設置,如果只設置了一個從服務器向主服務器發送命令時會報錯
5)發送端口信息,通過執行命令REPLCONF listening-port <port-number>,向主服務器發送從服務器的監聽端口號
6)同步,從服務器向主服務器發送PSYNC命令
7)命令傳播,完成同步之后主服務器會把之后執行的寫命令傳播到從服務器保證主從服務器的狀態一致

2.8版本之前 同步操作SYNC。只有全量同步,效率比較低

SYNC同步過程:
    1)從服務器向主服務器發送 SYNC 命令
    2)收到 SYNC 命令的主服務器執行 BGSAVE 命令, 在后台生成一個 RDB 文件, 並使用一個緩沖區記錄從現在開始執行的所有寫命令
    3)當主服務器的 BGSAVE 命令執行完畢時, 主服務器會將 BGSAVE 命令生成的 RDB 文件發送給從服務器, 從服務器接收並載入這個 RDB 文件, 將自己的數據庫狀態更新至主服務器執行 BGSAVE 命令時的數據庫狀態。
    4)主服務器將記錄在緩沖區里面的所有寫命令發送給從服務器, 從服務器執行這些寫命令, 將自己的數據庫狀態更新至主服務器數據庫當前所處的狀態

2.8版本之后 同步操作PSYNC。自行判斷 是全量同步 還是 增量同步 效率比較高

部分重同步功能由下面幾個部分構成:
主服務器的復制偏移量和從服務器的復制偏移量:當主服務器在向從服務器進行命令同步時,主服務器和從服務器會各自記錄一個復制偏移量,當主從服務器的數據庫狀態一致時這兩個復制偏移量是相同的,如果這兩個偏移量不一致說明當前主從服務器的狀態不一致
主服務器的復制積壓緩沖區:復制積壓緩沖區是一個固定大小的FIFO隊列,當隊列已滿時會彈出最早插入的數據,在主服務器進行命令傳播時會同時把命令放到緩沖區中,緩沖區包含兩部分數據,偏移量和字節。在進行復制時從服務器會將偏移量上報到主服務器,主服務檢查當前偏移量是否還存在緩沖區中,如果存在進行部分重同步,如果不存在進行完整重同步。因為這個積壓緩沖區是一個固定大小的隊列,所以當從服務器長時間斷線時,從服務器的復制偏移量很可能已不再緩沖區中,這時候只能進行完整重同步
服務器的運行ID:初次同步時主服務器會把ID發給從服務器,從服務器保存主服務器ID,當斷線重連后,會把之前保存的主服務器ID上報給主服務器,主服務器檢查從服務器之前復制的主服務器ID是否和自己的ID相同,如果相同,執行部分重同步,如果不同說明從服務器之前記錄的狀態不是當前主服務器,這時候需要執行完整重同步
PSYNC命令實現

1)初始復制或者之前執行過SLAVEOF no one命令,執行完整重同步:發送PSYNC ? -1命令到主服務器
2)如果從服務器已經復制過某個主服務器,在開始新復制時向主服務器發送PSYNC <runid> <offset>命令,runid是上次復制的主服務器id,offset是從服務器的復制偏移量
3)主服務器會根據這個兩個參數來決定做哪種同步,判斷服務器id是否和本機相同,復制偏移量是否在緩沖區中,主服務器有三種回復:

回復+FULLRESYNC <runid> <offset>執行完整重同步,從服務器把offset當做初始復制偏移量
回復+CONTINUE,表示執行部分重同步,從服務器等待主服務器發送缺少的數據
回復-ERR,表示主服務器版本低於2.8,不支持PSYNC命令

心跳檢測
在命令傳播階段,從服務器默認每秒一次的頻率向主服務器發送命令:REPLCONF ACK <replication_offset>,replication_offset是從服務器的復制偏移量,該命令有三個作用:

1)檢測從服務器的網絡連接狀態,檢測主從服務器連接是否正常,如果主服務器超過一定時間沒有收到從服務器的REPLCONF ACK 命令,那么它們的連接可能出了問題
2)輔助實現min-slaves選項,min-slaves-to-write和min-slaves-max-lag兩個選項可以防止主服務器在不安全的情況下執行寫命令,min-slaves-to-write 3 min-slaves-max-lag 10 表示如果從服務器少於3個,或者3個從服務器的延遲都大於10秒時,主服務器拒絕寫命令
3)檢測命令丟失,主服務器接收到從服務器的REPLCONF ACK 命令之后會檢查從服務器的偏移量是否和主服務器的一致,如果不一致會把積壓緩沖區中的從服務器偏移量后面的命令發送到從服務器

關閉主服務器持久化時,復制功能的數據安全

當配置Redis復制功能時,強烈建議打開主服務器的持久化功能。 否則的話,由於延遲等問題,部署的服務應該要避免自動拉起。為了幫助理解主服務器關閉持久化時自動拉起的危險性,參考一下以下會導致主從服務器數據全部丟失的例子:
假設節點A為主服務器,並且關閉了持久化。 並且節點B和節點C從節點A復制數據
節點A崩潰,然后由自動拉起服務重啟了節點A. 由於節點A的持久化被關閉了,所以重啟之后沒有任何數據
節點B和節點C將從節點A復制數據,但是A的數據是空的, 於是就把自身保存的數據副本刪除。
在關閉主服務器上的持久化,並同時開啟自動拉起進程的情況下,即便使用Sentinel來實現Redis的高可用性,也是非常危險的。 因為主服務器可能拉起得非常快,以至於Sentinel在配置的心跳時間間隔內沒有檢測到主服務器已被重啟,然后還是會執行上面的數據丟失的流程。無論何時,數據安全都是極其重要的,所以應該禁止主服務器關閉持久化的同時自動拉起

主服務器只在有至少 N 個從服務器的情況下,才執行寫操作

從 Redis 2.8 開始, 為了保證數據的安全性, 可以通過配置, 讓主服務器只在有至少 N 個當前已連接從服務器的情況下, 才執行寫命令。不過, 因為 Redis 使用異步復制, 所以主服務器發送的寫數據並不一定會被從服務器接收到, 因此, 數據丟失的可能性仍然是存在的。以下是這個特性的運作原理:
    從服務器以每秒一次的頻率 PING 主服務器一次, 並報告復制流的處理情況。
    主服務器會記錄各個從服務器最后一次向它發送 PING 的時間。
    用戶可以通過配置, 指定網絡延遲的最大值 min-slaves-max-lag , 以及執行寫操作所需的至少從服務器數量 min-slaves-to-write
    如果至少有 min-slaves-to-write 個從服務器, 並且這些服務器的延遲值都少於 min-slaves-max-lag 秒, 那么主服務器就會執行客戶端請求的寫操作。你可以將這個特性看作 CAP 理論中的 C 的條件放寬版本: 盡管不能保證寫操作的持久性, 但起碼丟失數據的窗口會被嚴格限制在指定的秒數中。
    如果條件達不到 min-slaves-to-write 和 min-slaves-max-lag 所指定的條件, 那么寫操作就不會被執行, 主服務器會向請求執行寫操作的客戶端返回一個錯誤 

Redis可擴展集群搭建
1. 主動復制避開Redis復制缺陷

既然Redis的復制功能有缺陷,不妨放棄Redis本身提供的復制功能,我們可以采用主動復制的方式來搭建我們的集群環境。所謂主動復制是指由業務端或者通過代理中間件對Redis存儲的數據進行雙寫或多寫,通過數據的多份存儲來達到與復制相同的目的,主動復制不僅限於 用在Redis集群上,目前很多公司采用主動復制的技術來解決MySQL主從之間復制的延遲問題,比如Twitter還專門開發了用於復制和分區的中間件gizzard(https://github.com/twitter/gizzard) 。

主動復制雖然解決了被動復制的延遲問題,但也帶來了新的問題,就是數據的一致性問題,數據寫2次或多次,如何保證多份數據的一致性呢?如果你的應用 對數據一致性要求不高,允許最終一致性的話,那么通常簡單的解決方案是可以通過時間戳或者vector clock等方式,讓客戶端同時取到多份數據並進行校驗,如果你的應用對數據一致性要求非常高,那么就需要引入一些復雜的一致性算法比如Paxos來保證 數據的一致性,但是寫入性能也會相應下降很多。
通過主動復制,數據多份存儲我們也就不再擔心Redis單點故障的問題了,如果一組Redis集群掛掉,我們可以讓業務快速切換到另一組Redis上,降低業務風險。

2. 通過presharding進行Redis在線擴容

通過主動復制我們解決了Redis單點故障問題,那么還有一個重要的問題需要解決:容量規划與在線擴容問題。我們前面分析過Redis的適用場景是全部數據存儲在內存中,而內存容量有限,那么首先需要根據業務數據量進行初步的容量規划,比如你的業務數據需 要100G存儲空間,假設服務器內存是48G,至少需要3~4台服務器來存儲。這個實際是對現有 業務情況所做的一個容量規划,假如業務增長很快,很快就會發現當前的容量已經不夠了,Redis里面存儲的數據很快就會超過物理內存大小,如何進行 Redis的在線擴容呢?Redis的作者提出了一種叫做presharding的方案來解決動態擴容和數據分區的問題,實際就是在同一台機器上部署多個Redis實例的方式,當容量不夠時將多個實例拆分到不同的機器上,這樣實際就達到了擴容的效果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM