天天看點

分布式專題-分布式緩存技術之Redis03-Redis的分布式

目錄導航

    • 前言
    • Redis叢集
    • Redis主從複制
      • 配置
      • 原理
        • 全量複制
        • 增量複制
        • 無硬碟複制
    • 哨兵機制
      • 什麼是哨兵
      • master的故障發現
      • 配置實作
    • Redis-Cluster
      • 拓撲結構
      • Redis的資料分區
      • HashTags
      • 重定向用戶端
      • 分片遷移
      • 槽遷移的過程
        • MIGRATING狀态
        • IMPORTING狀态
    • 後記

關于Redis,一共分為4節,本節了解一下分布式Redis:

  1. 初步了解分布式緩存技術Redis的使用

    主要會以資料結構為導向去了解Redis

  2. Redis内部的原理揭秘

    Redis的内部原理、Lua腳本的結合使用、Redis的回收政策、Redis持久化原理

  3. 了解分布式Redis

    分布式Redis、主從、哨兵、分片、企業級叢集方案

  4. Redis的應用實戰

    Redis的應用實戰,如何在實際應用中去使用redis

先來簡單了解下redis中提供的叢集政策, 雖然redis有持久化功能能夠保障redis伺服器當機也能恢複并且隻有少量的資料損失,但是由于所有資料在一台伺服器上,如果這台伺服器出現硬碟故障,那就算是有備份也仍然不可避免資料丢失的問題。

在實際生産環境中,我們不可能隻使用一台redis伺服器作為我們的緩存伺服器,必須要多台實作叢集,避免出現單點故障;

分布式專題-分布式緩存技術之Redis03-Redis的分布式

複制的作用是把redis的資料庫複制多個副本部署在不同的伺服器上,如果其中一台伺服器出現故障,也能快速遷移到其他伺服器上提供服務。 複制功能可以實作當一台redis伺服器的資料更新後,自動将新的資料同步到其他伺服器上

主從複制就是我們常見的master/slave模式, 主資料庫可以進行讀寫操作,當寫操作導緻資料發生變化時會自動将資料同步給從資料庫。而一般情況下,從資料庫是隻讀的,并接收主資料庫同步過來的資料。 一個主資料庫可以有多個從資料庫

分布式專題-分布式緩存技術之Redis03-Redis的分布式

在redis中配置master/slave是非常容易的,隻需要在從資料庫的配置檔案中加入slaveof 主資料庫位址 端口。 而 master 資料庫不需要做任何改變

配置之前,準備三台伺服器

  • linux1 (master) 192.168.200.111
  • linux2 (slave) 192.168.200.112
  • linux3 (slave) 192.168.200.113

分别安裝redis,具體安裝redis的細則可以參考初步了解分布式緩存技術Redis的使用

分布式專題-分布式緩存技術之Redis03-Redis的分布式
  1. 在slave的/redis.conf檔案中增加 slaveof {master.ip} {master.port} 、 同時将bind注釋掉,代表允許所有ip通路,并檢查主節點linux1上/redis.conf的保護模式是否關閉
    分布式專題-分布式緩存技術之Redis03-Redis的分布式
将linux2、linux3上redis的配置檔案增加slaveof配置:
分布式專題-分布式緩存技術之Redis03-Redis的分布式
将linux1、linux2、linux3上redis的配置檔案bind注釋掉:
分布式專題-分布式緩存技術之Redis03-Redis的分布式

redis預設配置是隻能接受本機ip,這裡我們分别将linux1、linux2、linux3的bind 注釋掉,代表所有ip都可以通路

關閉主節點的保護模式,設定為no即可:
分布式專題-分布式緩存技術之Redis03-Redis的分布式

2. 啟動所有機器的redis

分布式專題-分布式緩存技術之Redis03-Redis的分布式

Tips:

将redis.conf檔案的daemonize屬性改為yes ,表示背景靜默啟動,即沒有啟動視窗

分布式專題-分布式緩存技術之Redis03-Redis的分布式

最後的效果是:

分布式專題-分布式緩存技術之Redis03-Redis的分布式
  1. 分别通路linux1、linux2、linux3的redis用戶端,輸入 info replication
分布式專題-分布式緩存技術之Redis03-Redis的分布式

我們可以看到,linux3上的redis角色為:slave,master主節點位址正是我們之前在配置檔案裡配的,并且已經與master連接配接成功。我們再看master的狀态:

分布式專題-分布式緩存技術之Redis03-Redis的分布式

可以看到兩台slave的redis已經成功連接配接到master了~

  1. 通過在master(linux1)機器上輸入指令,比如set foo bar 、 在slave(linux2、linux3)伺服器就能看到該值已經同步過來了
分布式專題-分布式緩存技術之Redis03-Redis的分布式

再看子節點:

分布式專題-分布式緩存技術之Redis03-Redis的分布式

這就是所謂的主從複制

到此為止,linux主從叢集就配置完畢~

Redis全量複制一般發生在Slave初始化階段,這時Slave需要将Master上的所有資料都複制一份。具體步驟

分布式專題-分布式緩存技術之Redis03-Redis的分布式

完成上面幾個步驟後就完成了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以斷開

從redis 2.8開始,就支援主從複制的斷點續傳,如果主從複制過程中,網絡連接配接斷掉了,那麼可以接着上次複制的地方,繼續複制下去,而不是從頭開始複制一份

master node會在記憶體中建立一個backlog,master和slave都會儲存一個replica offset還有一個master id,offset就是儲存在backlog中的。如果master和slave網絡連接配接斷掉了,slave會讓master從上次的replica offset開始繼續複制

但是如果沒有找到對應的offset,那麼就會執行一次全量同步

監控示範

這裡給出一個指令,在slave節點的cli上可以監控master的變化:

replconf listening-port {master.port}

sync

在master上執行set操作

分布式專題-分布式緩存技術之Redis03-Redis的分布式

在slave監控到資料響應(ping是心跳機制)

分布式專題-分布式緩存技術之Redis03-Redis的分布式

前面我們說過,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,不會在自己本地落地磁盤了

在前面講的master/slave模式,在一個典型的一主多從的系統中,slave在整個體系中起到了資料備援備份和讀寫分離的作用。當master遇到異常終端後,需要從slave中選舉一個新的master繼續對外提供服務,這種機制在前面提到過N次,比如在zk中通過leader選舉、kafka中可以基于zk的節點實作master選舉。是以在redis中也需要一種機制去實作master的決策,redis并沒有提供自動master選舉功能,而是需要借助一個哨兵來進行監控

顧名思義,哨兵的作用就是監控Redis系統的運作狀況,它的功能包括兩個

  1. 監控master和slave是否正常運作
  2. master出現故障時自動将slave資料庫更新為master

哨兵是一個獨立的程序,使用哨兵後的架構圖

分布式專題-分布式緩存技術之Redis03-Redis的分布式

為了解決master選舉問題,又引出了一個單點問題,也就是哨兵的可用性如何解決,在一個一主多從的Redis系統中,可以使用多個哨兵進行監控任務以保證系統足夠穩定。此時哨兵不僅會監控master和slave,同時還會互相監控;這種方式稱為哨兵叢集,哨兵叢集需要解決故障發現、和master決策的協商機制問題

分布式專題-分布式緩存技術之Redis03-Redis的分布式

sentinel之間的互相感覺

sentinel節點之間會因為共同監視同一個master進而産生了關聯,一個新加入的sentinel節點需要和其他監視相同master節點的sentinel互相感覺,首先

  1. 需要互相感覺的sentinel都向他們共同監視的master節點訂閱channel:sentinel:hello
  2. 新加入的sentinel節點向這個channel釋出一條消息,包含自己本身的資訊,這樣訂閱了這個channel的sentinel就可以發現這個新的sentinel
  3. 新加入得sentinel和其他sentinel節點建立長連接配接
分布式專題-分布式緩存技術之Redis03-Redis的分布式

sentinel節點會定期向master節點發送心跳包來判斷存活狀态,一旦master節點沒有正确響應,sentinel會把master設定為“主觀不可用狀态”,然後它會把“主觀不可用”發送給其他所有的sentinel節點去确認,當确認的sentinel節點數大于>quorum時,則會認為master是“客觀不可用”,接着就開始進入選舉新的master流程;但是這裡又會遇到一個問題,就是sentinel中,本身是一個叢集,如果多個節點同時發現master節點達到客觀不可用狀态,那誰來決策選擇哪個節點作為maste呢?這個時候就需要從sentinel叢集中選擇一個leader來做決策。而這裡用到了一緻性算法Raft算法、它和Paxos算法類似,都是分布式一緻性算法。但是它比Paxos算法要更容易了解;Raft和Paxos算法一樣,也是基于投票算法,隻要保證過半數節點通過提議即可;

動畫示範位址:http://thesecretlivesofdata.com/raft/

通過在這個配置的基礎上增加哨兵機制。在其中任意一台伺服器上建立一個sentinel.conf檔案,檔案内容 sentinel monitor {name } {ip} {port } {quorum}

其中name表示要監控的master的名字,這個名字是自己定義。 ip和port表示master的ip和端口号。 最後一個1表示最低通過票數,也就是說至少需要幾個哨兵節點統一才可以,後面會具體講解

port 26379

sentinel monitor mymaster 192.168.200.111 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角色加入到整個叢集中

即使是使用哨兵,此時的Redis叢集的每個資料庫依然存有叢集中的所有資料,進而導緻叢集的總資料存儲量受限于可用存儲記憶體最小的節點,形成了木桶效應。而因為Redis是基于記憶體存儲的,是以這一個問題在redis中就顯得尤為突出了

在redis3.0之前,我們是通過在用戶端去做的分片,通過hash環的方式對key進行分片存儲。分片雖然能夠解決各個節點的存儲壓力,但是導緻維護成本高、增加、移除節點比較繁瑣。是以在redis3.0以後的版本最大的一個好處就是支援叢集功能,叢集的特點在于擁有和單機執行個體一樣的性能,同時在網絡分區以後能夠提供一定的可通路性以及對主資料庫故障恢複的支援。

哨兵和叢集是兩個獨立的功能,當不需要對資料進行分片使用哨兵就夠了,如果要進行水準擴容,叢集是一個比較好的方式

一個Redis Cluster由多個Redis節點構成。不同節點組服務的資料沒有交集,也就是每個一節點組對應資料sharding的一個分片。節點組内部分為主備兩類節點,對應master和slave節點。兩者資料準實時一緻,通過異步化的主備複制機制來保證。一個節點組有且隻有一個master節點,同時可以有0到多個slave節點,在這個節點組中隻有master節點對使用者提供些服務,讀服務可以由master或者slave提供

分布式專題-分布式緩存技術之Redis03-Redis的分布式

redis-cluster是基于gossip協定實作的無中心化節點的叢集,因為去中心化的架構不存在統一的配置中心,各個節點對整個叢集狀态的認知來自于節點之間的資訊互動。在Redis Cluster,這個資訊互動是通過Redis Cluster Bus來完成的

分布式資料庫首要解決把整個資料集按照分區規則映射到多個節點的問題,即把資料集劃分到多個節點上,每個節點負責整個資料的一個子集, Redis Cluster采用哈希分區規則,采用虛拟槽分區。

虛拟槽分區巧妙地使用了哈希空間,使用分散度良好的哈希函數把所有的資料映射到一個固定範圍内的整數集合,整數定義為槽(slot)。比如Redis Cluster槽的範圍是0 ~ 16383。槽是叢集内資料管理和遷移的基本機關。采用大範圍的槽的主要目的是為了友善資料的拆分和叢集的擴充,每個節點負責一定數量的槽。

計算公式:slot = CRC16(key)%16383。每一個節點負責維護一部分槽以及槽所映射的鍵值資料。

分布式專題-分布式緩存技術之Redis03-Redis的分布式

通過分片手段,可以将資料合理的劃分到不同的節點上,這本來是一件好事。但是有的時候,我們希望對相關聯的業務以原子方式進行操作。舉個簡單的例子

我們在單節點上執行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。

Redis Cluster并不會代理查詢,那麼如果用戶端通路了一個key并不存在的節點,這個節點是怎麼處理的呢?比如我想擷取key為msg的值,msg計算出來的槽編号為254,目前節點正好不負責編号為254的槽,那麼就會傳回用戶端下面資訊:

-MOVED 254 127.0.0.1:6381

表示用戶端想要的254槽由運作在IP為127.0.0.1,端口為6381的Master執行個體服務。如果根據key計算得出的槽恰好由目前節點負責,則當期節點會立即傳回結果

在一個穩定的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

删除一個主節點

先将節點的資料移動到其他節點上,然後才能執行删除

槽遷移的過程中有一個不穩定狀态,這個不穩定狀态會有一些規則,這些規則定義用戶端的行為,進而使得Redis Cluster不必當機的情況下可以執行槽的遷移。下面這張圖描述了我們遷移編号為1、2、3的槽的過程中,他們在MasterA節點和MasterB節點中的狀态。

分布式專題-分布式緩存技術之Redis03-Redis的分布式

簡單的工作流程

  1. 向MasterB發送狀态變更指令,把Master B對應的slot狀态設定為IMPORTING
  2. 向MasterA發送狀态變更指令,将Master對應的slot狀态設定為MIGRATING

當MasterA的狀态設定為MIGRANTING後,表示對應的slot正在遷移,為了保證slot資料的一緻性,MasterA此時對于slot内部資料提供讀寫服務的行為和通常狀态下是有差別的,

  1. 如果用戶端通路的Key還沒有遷移出去,則正常處理這個key
  2. 如果key已經遷移或者根本就不存在這個key,則回複用戶端ASK資訊讓它跳轉到MasterB去執行

當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的遷移。