手把手帶你搞懂Redis高可用集群
這篇文章為大家帶來了關於Redis的相關知識,其中主要介紹了叢集的相關問題,Redis叢集是一種分散式資料庫方案,叢集透過分片來進行資料共享,並提供複製和故障轉移功能,希望對大家有幫助。
推薦學習:Redis學習教學
幾種 Redis 高可用性的解決方案。包括:「主從模式」、「哨兵機制」以及「哨兵集群」。
- 「主從模式」具有讀寫分離,分擔讀取壓力、資料備份,提供多個副本等優點。
- 「哨兵機制」在主節點故障後能自動將從節點提升成主節點,不需要人工幹預操作就能恢復服務可用。
- 「哨兵叢集」解決單點故障以及單機哨兵產生「誤判」問題。
Redis 從最簡單的單機版,經過資料持久化、主從多副本、哨兵集群,透過這麼一番的優化,不管是性能還是穩定性,都越來越高。
但是隨著時間的發展,公司業務量迎來了爆炸性成長,此時的架構模型,還能夠承擔這麼大的流量嗎?
例如有這麼一個需求:要用Redis 保存5000 萬
個鍵值對,每個鍵值對大約是512B
,為了能快速部署並對外提供服務,我們採用雲端主機來運行Redis 實例,那麼,該如何選擇雲端主機的記憶體容量呢?
透過計算,這些鍵值對所佔的記憶體空間大約是 25GB(5000 萬 *512B)。
想到的第一個方案就是:選擇一台 32GB 記憶體的雲端主機來部署 Redis。因為 32GB 的記憶體能保存所有數據,而且還留有 7GB,可以確保系統的正常運作。
同時,也採用 RDB 對資料做持久化,以確保 Redis 實例故障後,還能從 RDB 恢復資料。
但是,在使用的過程中會發現,Redis 的回應有時會非常慢。透過 INFO指令
查看 Redis 的latest_fork_usec
指標值(表示最近一次 fork 的耗時),結果發現這個指標值特別高。
這跟 Redis 的持久化機制有關係。
在使用RDB 進行持久化時,Redis 會fork
子程序來完成,fork
運算的用時和Redis 的資料量是正相關的,而fork
在執行時會阻塞主執行緒。 資料量越大,fork 操作造成的主執行緒阻塞的時間越長。
所以,在使用RDB
對25GB 的資料進行持久化時,資料量較大,後台運行的子程序在fork
建立時阻塞了主線程,於是就導致Redis 回應變慢了。
顯然這個方案是不可行的,我們必須要尋找其他的方案。
如何保存更多資料?
為了保存大量數據,我們一般有兩種方法:「縱向擴展」和「橫向擴展」:
- 縱向擴充:升級單一Redis 執行個體的資源配置,包括增加記憶體容量、增加磁碟容量、使用更高配置的CPU;
- 橫向擴充:橫向增加目前Redis 執行個體的個數。
首先,「縱向擴展」的好處是,實作起來簡單、直接。不過,這個方案也面臨兩個潛在的問題。
- 第一個問題是,當使用RDB 對資料進行持久化時,如果資料量增加,需要的記憶體也會增加,主執行緒
fork
子程序時就可能會阻塞。 - 第二個問題:縱向擴充會受到硬體和成本的限制。 這很容易理解,畢竟,把記憶體從 32GB 擴展到 64GB 還算容易,但是,要擴充到 1TB,就會面臨硬體容量和成本上的限制了。
與「縱向擴展」相比,「橫向擴展」是擴展性較好的方案。這是因為,要保存更多的數據,採用這種方案的話,只用增加 Redis 的實例個數就行了,不用擔心單一實例的硬體和成本限制。
Redis 集群就是基於「橫向擴展」實現的,透過啟動多個Redis 實例組成一個集群,然後按照一定的規則,把收到的資料劃分成多份,每一份都用一個實例來保存。
Redis 叢集
Redis 叢集是一種分散式資料庫方案,叢集透過分片
(sharding
,也可以叫切片
)來進行資料共享,並提供複製和故障轉移功能。
回到我們剛剛的場景中,如果把 25GB 的資料平均分成 5 份(當然,也可以不做均分),使用 5 個實例來保存,每個實例只需要保存 5GB 資料。如下圖所示:
那麼,在切片叢集中,實例在為5GB 資料產生RDB 時,資料量就小了很多,fork
子程序一般不會為主執行緒帶來較長時間的阻塞。
採用多個實例保存資料切片後,我們既能保存 25GB 數據,又避免了 fork
子進程阻塞主執行緒而導致的回應突然變慢。
在實際應用 Redis 時,隨著業務規模的擴展,保存大量資料的情況通常是無法避免的。而 Redis 集群,就是一個非常好的解決方案。
下面我們開始研究如何建立一個 Redis 叢集?
搭建 Redis 叢集
一個 Redis 叢集通常由多個節點組成,在剛開始的時候,每個節點都是相互獨立地,節點之間沒有任何關聯。要組成一個可以工作的集群,我們必須將各個獨立的節點連接起來,構成一個包含多節點的集群。
我們可以透過CLUSTER MEET
指令,將各個節點連接起來:
CLUSTER MEET <ip> <port></port></ip>
- ip:待加入叢集的節點ip ##port :待加入叢集的節點port
CLUSTER MEET 指令,可以讓接收指令的節點A 將另一個節點B 新增到節點A 所在的集群中。
127.0.0.1:7001、
127.0.0.1:7002、
127.0.0.1:7003。
7001:
$ redis-cli -c -p 7001
7001 發送指令,將節點
7002 加入到
7001 所在的叢集:
127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7002
7003 發送指令,也加入到
7001 和
7002 所在的叢集。
127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7003
透過CLUSTER NODES
指令可以查看叢集中的節點資訊。
現在叢集中已經包含
7001、
7002 和
7003 三個節點。不過,在使用單一實例的時候,資料存在哪兒,客戶端存取哪兒,都是非常明確的。但是,切片叢集不可避免地涉及到
多個實例的分散式管理問題。
- 資料切片後,在多個實例之間如何分佈?
- 客戶端怎麼決定想要存取的資料在哪個實例上?
Redis Cluster 方案有關了。不過,我們要先弄清楚切片群集和
Redis Cluster 的聯繫與差異。
在 Redis 3.0 之前,官方並沒有針對切片群集提供具體的方案。從 3.0 開始,官方提供了一個名為實際上,切片叢集是一種保存大量資料的通用機制,這個機制可以有不同的實作方案。Redis Cluster
的方案,用於實作切片叢集。
Redis Cluster 方案中就規定了資料和實例的對應規則。
Redis Cluster 方案採用
哈希槽(Hash Slot),來處理資料和實例之間的對應關係。
Redis Cluster 方案中,一個切片群集共有
16384 個哈希槽(2^14 ),這些哈希槽類似於資料分區,每個鍵值對都會根據它的key,被映射到一個哈希槽中。
CLUSTER MEET 指令將
7001、
7002、
7003 三個節點連接到同一個叢集裡面,但是這個叢集目前是處於
下線狀態的,因為叢集中的三個節點沒有分配任何槽。
CLUSTER MEET 指令手動建立實例間的連接,形成集群,再使用
CLUSTER ADDSLOTS 指令,指定每個實例上的雜湊槽個數。
CLUSTER ADDSLOTS <slot> [slot ...]</slot>
Redis5.0 提供CLUSTER CREATE
命令建立集群,使用該命令,Redis 會自動把這些槽平均分佈在集群實例上。
举个例子,我们通过以下命令,给 7001
、7002
、7003
三个节点分别指派槽。
将槽 0 ~ 槽5000 指派给 给 7001
:
127.0.0.1:7001> CLUSTER ADDSLOTS 0 1 2 3 4 ... 5000
将槽 5001 ~ 槽10000 指派给 给 7002
:
127.0.0.1:7002> CLUSTER ADDSLOTS 5001 5002 5003 5004 ... 10000
将槽 10001~ 槽 16383 指派给 给 7003
:
127.0.0.1:7003> CLUSTER ADDSLOTS 10001 10002 10003 10004 ... 16383
当三个 CLUSTER ADDSLOTS
命令都执行完毕之后,数据库中的 16384 个槽都已经被指派给了对应的节点,此时集群进入上线状态。
通过哈希槽,切片集群就实现了数据到哈希槽、哈希槽再到实例的分配。
但是,即使实例有了哈希槽的映射信息,客户端又是怎么知道要访问的数据在哪个实例上呢?
客户端如何定位数据?
一般来说,客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端。但是,在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的。
那么,客户端是如何可以在访问任何一个实例时,就能获得所有的哈希槽信息呢?
Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。
客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了。
当客户端向节点请求键值对时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:
- 如果键所在的槽刚好指派给了当前节点,那么节点会直接执行这个命令;
- 如果没有指派给当前节点,那么节点会向客户端返回一个
MOVED
错误,然后重定向(redirect)到正确的节点,并再次发送之前待执行的命令。
计算键属于哪个槽
节点通过以下算法来定义 key
属于哪个槽:
crc16(key,keylen) & 0x3FFF;
- crc16:用于计算 key 的 CRC-16 校验和
- 0x3FFF:换算成 10 进制是 16383
- & 0x3FFF:用于计算出一个介于 0~16383 之间的整数作为 key 的槽号。
通过
CLUSTER KEYSLOT <key></key>
命令可以查看 key 属于哪个槽。
判断槽是否由当前节点负责处理
当节点计算出 key 所属的 槽 i
之后,节点会判断 槽 i
是否被指派了自己。那么如何判断呢?
每个节点会维护一个 「slots数组」,节点通过检查 slots[i]
,判断 槽 i
是否由自己负责:
- 如果说
slots[i]
对应的节点是当前节点的话,那么说明槽 i
由当前节点负责,节点可以执行客户端发送的命令; - 如果说
slots[i]
对应的不是当前节点,节点会根据slots[i]
所指向的节点向客户端返回MOVED
错误,指引客户端转到正确的节点。
MOVED 错误
格式:
MOVED <slot> <ip>:<port></port></ip></slot>
- slot:键所在的槽
- ip:负责处理槽 slot 节点的 ip
- port:负责处理槽 slot 节点的 port
比如:MOVED 10086 127.0.0.1:7002
,表示,客户端请求的键值对所在的哈希槽 10086
,实际是在 127.0.0.1:7002
这个实例上。
通过返回的 MOVED
命令,就相当于把哈希槽所在的新实例的信息告诉给客户端了。
这样一来,客户端就可以直接和 7002
连接,并发送操作请求了。
同时,客户端还会更新本地缓存,将该槽与 Redis 实例对应关系更新正确。
集群模式的
redis-cli
客户端在接收到MOVED
错误时,并不会打印出MOVED
错误,而是根据MOVED
错误自动进行节点转向,并打印出转向信息,所以我们是看不见节点返回的MOVED
错误的。而使用单机模式的redis-cli
客户端可以打印MOVED
错误。
其实,Redis 告知客户端重定向访问新实例分两种情况:MOVED
和 ASK
。下面我们分析下 ASK
重定向命令的使用方法。
重新分片
在集群中,实例和哈希槽的对应关系并不是一成不变的,最常见的变化有两个:
- 在集群中,实例有新增或删除,Redis 需要重新分配哈希槽;
- 为了负载均衡,Redis 需要把哈希槽在所有实例上重新分布一遍。
重新分片可以在线进行,也就是说,重新分片的过程中,集群不需要下线。
举个例子,上面提到,我们组成了 7001
、7002
、7003
三个节点的集群,我们可以向这个集群添加一个新节点127.0.0.1:7004
。
$ redis-cli -c -p 7001 127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7004 OK
然后通过重新分片,将原本指派给节点 7003
的槽 15001 ~ 槽 16383 改为指派给 7004
。
在重新分片的期间,源节点向目标节点迁移槽的过程中,可能会出现这样一种情况:如果某个槽的数据比较多,部分迁移到新实例,还有一部分没有迁移咋办?
在这种迁移部分完成的情况下,客户端就会收到一条 ASK
报错信息。
ASK 错误
如果客户端向目标节点发送一个与数据库键有关的命令,并且这个命令要处理的键正好属于被迁移的槽时:
- 源节点会先在自己的数据库里查找指定的键,如果找到的话,直接执行命令;
- 相反,如果源节点没有找到,那么这个键就有可能已经迁移到了目标节点,源节点就会向客户端发送一个
ASK
错误,指引客户端转向目标节点,并再次发送之前要执行的命令。
看起来好像有点复杂,我们举个例子来解释一下。
如上图所示,节点 7003
正在向 7004
迁移 槽 16383
,这个槽包含 hello
和 world
,其中键 hello
还留在节点 7003
,而 world
已经迁移到 7004
。
我们向节点 7003
发送关于 hello
的命令 这个命令会直接执行:
127.0.0.1:7003> GET "hello" "you get the key 'hello'"
如果我们向节点 7003
发送 world
那么客户端就会被重定向到 7004
:
127.0.0.1:7003> GET "world" -> (error) ASK 16383 127.0.0.1:7004
客户端在接收到 ASK
错误之后,先发送一个 ASKING
命令,然后在发送 GET "world"
命令。
ASKING
命令用于打开节点的ASKING
标识,打开之后才可以执行命令。
ASK 和 MOVED 的区别
ASK
错误和 MOVED
错误都会导致客户端重定向,它们的区别在于:
- MOVED 错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到关于
槽 i
的MOVED
错误之后,客户端每次遇到关于槽 i
的命令请求时,都可以直接将命令请求发送至MOVED
错误指向的节点,因为该节点就是目前负责槽 i
的节点。 - 而 ASK 只是两个节点迁移槽的过程中的一种临时措施:在客户端收到关于
槽 i
的ASK
错误之后,客户端只会在接下来的一次命令请求中将关于槽 i
的命令请求发送到ASK
错误指向的节点,但是 ,如果客户端再次请求槽 i
中的数据,它还是会给原来负责槽 i
的节点发送请求。
这也就是说,ASK 命令的作用只是让客户端能给新实例发送一次请求,而且也不会更新客户端缓存的哈希槽分配信息。而不像 MOVED
命令那样,会更改本地缓存,让后续所有命令都发往新实例。
我们现在知道了 Redis 集群的实现原理。下面我们再来分析下,Redis 集群如何实现高可用的呢?
复制与故障转移
Redis 集群中的节点也是分为主节点和从节点。
- 主节点用于处理槽
- 从节点用于复制主节点,如果被复制的主节点下线,可以代替主节点继续提供服务。
举个例子,对于包含 7001
~ 7004
的四个主节点的集群,可以添加两个节点:7005
、7006
。并将这两个节点设置为 7001
的从节点。
设置从节点命令:
CLUSTER REPLICATE <node_id></node_id>
如图:
如果此时,主节点 7001
下线,那么集群中剩余正常工作的主节点将在 7001
的两个从节点中选出一个作为新的主节点。
例如,节点 7005
被选中,那么原来由节点 7001
负责处理的槽会交给节点 7005
处理。而节点 7006
会改为复制新主节点 7005
。如果后续 7001
重新上线,那么它将成为 7005
的从节点。如下图所示:
故障检测
集群中每个节点会定期向其他节点发送 PING
消息,来检测对方是否在线。如果接收消息的一方没有在规定时间内返回 PONG
消息,那么接收消息的一方就会被发送方标记为「疑似下线」。
集群中的各个节点会通过互相发消息的方式来交换各节点的状态信息。
节点的三种状态:
- 在线状态
- 疑似下线状态 PFAIL
- 已下线状态 FAIL
一个节点认为某个节点失联了并不代表所有的节点都认为它失联了。在一个集群中,半数以上负责处理槽的主节点都认定了某个主节点下线了,集群才认为该节点需要进行主从切换。
Redis 集群节点采用 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。
我们都知道,哨兵机制可以通过监控、自动切换主库、通知客户端实现故障自动切换。那么 Redis Cluster
又是如何实现故障自动转移呢?
故障转移
当一个从节点发现自己正在复制的主节点进入了「已下线」状态时,从节点将开始对下线主节点进行故障切换。
故障转移的执行步骤:
- 在复制下线主节点的所有从节点里,选中一个从节点
- 被选中的从节点执行
SLAVEOF no one
命令,成为主节点 - 新的主节点会撤销所有对已下线主节点的槽指派,将这些槽全部指派给自己
- 新的主节点向集群广播一条
PONG
消息,让集群中其他节点知道,该节点已经由从节点变为主节点,且已经接管了原主节点负责的槽 - 新的主节点开始接收自己负责处理槽有关的命令请求,故障转移完成。
选主
这个选主方法和哨兵的很相似,两者都是基于 Raft算法
的领头算法实现的。流程如下:
- 集群的配置纪元是一个自增计数器,初始值为0;
- 当集群里的某个节点开始一次故障转移操作时,集群配置纪元加 1;
- 对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,第一个向主节点要求投票的从节点将获得主节点的投票;
- 当从节点发现自己复制的主节点进入「已下线」状态时,会向集群广播一条消息,要求收到这条消息,并且具有投票权的主节点为自己投票;
- 如果一个主节点具有投票权,且尚未投票给其他从节点,那么该主节点会返回一条消息给要求投票的从节点,表示支持从节点成为新的主节点;
- 每个参与选举的从节点会计算获得了多少主节点的支持;
- 如果集群中有 N 个具有投票权的主节点,当一个从节点收到的支持票
大于等于 N/2 + 1
时,该从节点就会当选为新的主节点; - 如果在一个配置纪元里没有从节点收集到足够多的票数,那么集群会进入一个新的配置纪元,并再次进行选主。
消息
集群中的各个节点通过发送和接收消息来进行通信,我们把发送消息的节点称为发送者,接收消息的称为接收者。
节点发送的消息主要有五种:
- MEET 訊息
- PING 訊息
- PONG 訊息
- FAIL 訊息
- PUBLISH 訊息
#集群中的各個節點透過Gossip
協定交換不同節點的狀態訊息, Gossip
是由MEET
、PING
、PONG
三種訊息組成。
發送者每次發送MEET
、PING
、PONG
訊息時,都會從自己已知的節點清單中隨機選出兩個節點(可以是主節點或從節點)一併發送給接收者。
接收者收到MEET
、PING
、PONG
訊息時,根據自身是否認識這兩個節點來進行不同的處理:
- 如果被選取的節點不存在接收已知的節點列表,說明是第一次接觸,接收者會根據選擇節點的ip和連接埠號碼進行通訊;
- 如果已經存在,表示之前已經完成了通信,然後會更新原有選取節點的資訊。
推薦學習:Redis教學
#以上是手把手帶你搞懂Redis高可用集群的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

Redis集群模式通過分片將Redis實例部署到多個服務器,提高可擴展性和可用性。搭建步驟如下:創建奇數個Redis實例,端口不同;創建3個sentinel實例,監控Redis實例並進行故障轉移;配置sentinel配置文件,添加監控Redis實例信息和故障轉移設置;配置Redis實例配置文件,啟用集群模式並指定集群信息文件路徑;創建nodes.conf文件,包含各Redis實例的信息;啟動集群,執行create命令創建集群並指定副本數量;登錄集群執行CLUSTER INFO命令驗證集群狀態;使

Redis 使用哈希表存儲數據,支持字符串、列表、哈希表、集合和有序集合等數據結構。 Redis 通過快照 (RDB) 和追加只寫 (AOF) 機制持久化數據。 Redis 使用主從復制來提高數據可用性。 Redis 使用單線程事件循環處理連接和命令,保證數據原子性和一致性。 Redis 為鍵設置過期時間,並使用 lazy 刪除機制刪除過期鍵。

要查看 Redis 中的所有鍵,共有三種方法:使用 KEYS 命令返回所有匹配指定模式的鍵;使用 SCAN 命令迭代鍵並返回一組鍵;使用 INFO 命令獲取鍵的總數。

要查看 Redis 版本號,可以使用以下三種方法:(1) 輸入 INFO 命令,(2) 使用 --version 選項啟動服務器,(3) 查看配置文件。

解決redis-server找不到問題的步驟:檢查安裝,確保已正確安裝Redis;設置環境變量REDIS_HOST和REDIS_PORT;啟動Redis服務器redis-server;檢查服務器是否運行redis-cli ping。

Redis 有序集合(ZSet)用於存儲有序元素集合,並按關聯分數進行排序。 ZSet 的用法步驟包括:1. 創建 ZSet;2. 添加成員;3. 獲取成員分數;4. 獲取排名;5. 獲取排名範圍的成員;6. 刪除成員;7. 獲取元素個數;8. 獲取分數範圍內的成員個數。

理解 Redis 源碼的最佳方法是逐步進行:熟悉 Redis 基礎知識。選擇一個特定的模塊或功能作為起點。從模塊或功能的入口點開始,逐行查看代碼。通過函數調用鏈查看代碼。熟悉 Redis 使用的底層數據結構。識別 Redis 使用的算法。

使用 Redis 指令需要以下步驟:打開 Redis 客戶端。輸入指令(動詞 鍵 值)。提供所需參數(因指令而異)。按 Enter 執行指令。 Redis 返迴響應,指示操作結果(通常為 OK 或 -ERR)。
