この記事では、Redis に関する関連知識を提供します。主にクラスター関連の問題を紹介します。Redis クラスターは分散データベース ソリューションです。クラスターはシャーディングを通じてデータを共有し、レプリケーションとフェイルオーバーの機能を提供します。皆さんのお役に立てば幸いです。
推奨学習: Redis 学習チュートリアル
いくつかの Redis 高可用性ソリューション。 「マスタースレーブモード」、「センチネルメカニズム」、「センチネルクラスター」が含まれます。
Redis は、最も単純なスタンドアロン バージョンから、データ永続化、マスター/スレーブの複数コピー、センチネル クラスターへと移行し、このような最適化により、パフォーマンスと安定性の両方がますます高くなりました。
しかし、時間の経過とともに、会社のビジネス量は爆発的に増加しました。この時点のアーキテクチャ モデルは依然としてこれほど大量のトラフィックに耐えることができるでしょうか?
たとえば、次のような要件があります。Redis を使用して 5,000 万
のキーと値のペアを保存します。各キーと値のペアは約 512B
です。サービスを提供するには、クラウド ホストを使用して Redis インスタンスを実行しますが、クラウド ホストのメモリ容量はどのように選択すればよいでしょうか?
計算によると、これらのキーと値のペアが占めるメモリ空間は約 25GB (5,000 万 *512B) になります。
最初に思い浮かぶ解決策は、Redis をデプロイするために 32GB メモリを備えたクラウド ホストを選択することです。 32 GB のメモリにすべてのデータを保存でき、システムの通常の動作を確保するにはまだ 7 GB が残っているためです。
同時に、RDB はデータの永続化にも使用され、Redis インスタンスの障害後に RDB からデータを確実に回復できるようにします。
ただし、使用していると、Redis の応答が非常に遅い場合があります。 INFO コマンド
(最新のフォークにかかった時間を示す) を通じて Redis の latest_fork_usec
インジケーター値を確認すると、このインジケーター値が特に高いことがわかります。
これは、Redis の永続化メカニズムに関連しています。
RDB を永続化に使用する場合、Redis は fork
サブプロセスを実行して完了します。fork
操作時間はデータ量に比例します。 Redis 関連の では、fork
は実行時にメインスレッドをブロックします。 データ量が大きくなるほど、フォーク操作によりメインスレッドがブロックされる時間が長くなります。
そのため、RDB
を使用して 25GB のデータを永続化すると、データ量が多くなり、バックグラウンドで実行されている子プロセスが fork## 時にブロックされてしまいます。 # が作成され、メインスレッドがブロックされるため、Redis の応答が遅くなります。
垂直拡張」と「水平拡張」の2つの方法があります。 垂直方向の拡張: 単一 Redis インスタンスのリソース構成をアップグレードします (メモリ容量の増加、ディスク容量の増加、より高構成の CPU の使用など)。
最初の問題は、RDB を使用してデータを永続化する場合、データ量が増加すると必要なメモリも増加し、子スレッドの実行時にメインスレッドが
fork2 番目の質問:
垂直方向の拡張はハードウェアとコストによって制限されます。 sharding、
スライシング とも呼ばれます) です。 ) データを共有し、レプリケーションおよびフェイルオーバー機能を提供します。
先ほどのシナリオに戻ります。25GB のデータを 5 つの部分に均等に分割し (もちろん、均等に分割する必要はありません)、それを保存するために 5 つのインスタンスを使用すると、各インスタンスは5GBのデータを保存するだけで済みます。以下に示すように:
次に、スライシング クラスターでは、インスタンスが 5GB データの RDB を生成すると、データ量ははるかに少なくなります。メインスレッドへのオーバーヘッド、長期にわたるブロック。 複数のインスタンスを使用してデータ スライスを保存すると、25 GB のデータを保存できるだけでなく、子プロセスがメイン スレッドをブロックするときに
によって引き起こされる応答の突然の低下を回避することもできます。 実際にRedisを利用する場合、ビジネス規模の拡大に伴い、通常は大量のデータの保存が避けられません。 Redis クラスターは非常に優れたソリューションです。
Redis クラスターの構築方法の勉強を始めましょう?
Redis クラスターの構築
して、複数のノードを含む クラスターを形成する必要があります。 各ノードは
CLUSTER MEET コマンドを通じて接続できます: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">CLUSTER MEET <ip> <port></port></ip></pre><div class="contentsignin">ログイン後にコピー</div></div>
コマンドをノード A に送信することで、コマンドを受け取ったノード A がクラスターを追加することができます。別のノード B からノード A が配置されているクラスター内にあります。 これは少し抽象的なので、例を見てみましょう。
現在、3 つの独立したノード
127.0.0.1:7001、127.0.0.1:7002
、127.0.0.1:7003
があるとします。
最初にクライアントを使用してノード
7001: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">$ redis-cli -c -p 7001</pre><div class="contentsignin">ログイン後にコピー</div></div>
に接続し、次にコマンドをノード
7001 が存在するクラスターにノード
7002 を追加します。
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 の 3 つのノードが含まれています。ただし、単一インスタンスを使用すると、データがどこに存在し、クライアントがどこにアクセスするかが非常に明確になります。ただし、クラスターをスライスすると、複数のインスタンスの
分散管理の問題 が必然的に発生します。
スライス クラスターを使用したい場合は、2 つの大きな問題を解決する必要があります:
Redis クラスター
ソリューションに関連しています。ただし、最初にスライシング クラスターとRedis クラスター の関係と違いを理解する必要があります。
Redis 3.0 より前は、公式はクラスターをスライスするための具体的なソリューションを提供していませんでした。 3.0 以降、公式はスライシング クラスターを実装するための
と呼ばれるソリューションを提供します。このスキームは、データとインスタンスに対応するルールを規定します。Redis Cluster
実際、クラスタのスライスは大量のデータを保存するための一般的なメカニズムであり、このメカニズムにはさまざまな実装ソリューションがあります。
具体的には、
Redis Cluster
Hash Slot (ハッシュ スロット) を使用して、データとインスタンス間のマッピング関係を処理します。
ハッシュ スロットと Redis インスタンスのマッピング
16384 のハッシュ スロット (2^14) があります。 )、これらのハッシュ スロットはデータ パーティションに似ており、各キーと値のペアはそのキーに従ってハッシュ スロットにマッピングされます。
上記の分析では、3 つのノード
7001
7002、および
7003 が
CLUSTER MEET を通じて接続されています。同じクラスターに移動しますが、クラスター内の 3 つのノードにはスロットが割り当てられていないため、
このクラスターは現在 オフライン状態
になっています。 では、これらのハッシュ スロットは特定の Redis インスタンスにどのようにマッピングされるのでしょうか? 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;
通过
CLUSTER KEYSLOT <key></key>
命令可以查看 key 属于哪个槽。
当节点计算出 key 所属的 槽 i
之后,节点会判断 槽 i
是否被指派了自己。那么如何判断呢?
每个节点会维护一个 「slots数组」,节点通过检查 slots[i]
,判断 槽 i
是否由自己负责:
slots[i]
对应的节点是当前节点的话,那么说明 槽 i
由当前节点负责,节点可以执行客户端发送的命令;slots[i]
对应的不是当前节点,节点会根据 slots[i]
所指向的节点向客户端返回 MOVED
错误,指引客户端转到正确的节点。格式:
MOVED <slot> <ip>:<port></port></ip></slot>
比如: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
重定向命令的使用方法。
在集群中,实例和哈希槽的对应关系并不是一成不变的,最常见的变化有两个:
重新分片可以在线进行,也就是说,重新分片的过程中,集群不需要下线。
举个例子,上面提到,我们组成了 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
错误,指引客户端转向目标节点,并再次发送之前要执行的命令。看起来好像有点复杂,我们举个例子来解释一下。
如上图所示,节点 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
错误都会导致客户端重定向,它们的区别在于:
槽 i
的 MOVED
错误之后,客户端每次遇到关于 槽 i
的命令请求时,都可以直接将命令请求发送至 MOVED
错误指向的节点,因为该节点就是目前负责 槽 i
的节点。槽 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
消息,那么接收消息的一方就会被发送方标记为「疑似下线」。
集群中的各个节点会通过互相发消息的方式来交换各节点的状态信息。
节点的三种状态:
一个节点认为某个节点失联了并不代表所有的节点都认为它失联了。在一个集群中,半数以上负责处理槽的主节点都认定了某个主节点下线了,集群才认为该节点需要进行主从切换。
Redis 集群节点采用 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。
我们都知道,哨兵机制可以通过监控、自动切换主库、通知客户端实现故障自动切换。那么 Redis Cluster
又是如何实现故障自动转移呢?
当一个从节点发现自己正在复制的主节点进入了「已下线」状态时,从节点将开始对下线主节点进行故障切换。
故障转移的执行步骤:
SLAVEOF no one
命令,成为主节点PONG
消息,让集群中其他节点知道,该节点已经由从节点变为主节点,且已经接管了原主节点负责的槽这个选主方法和哨兵的很相似,两者都是基于 Raft算法
的领头算法实现的。流程如下:
大于等于 N/2 + 1
时,该从节点就会当选为新的主节点;集群中的各个节点通过发送和接收消息来进行通信,我们把发送消息的节点称为发送者,接收消息的称为接收者。
节点发送的消息主要有五种:
クラスターの各ノードは、Gossip
プロトコルを通じてさまざまなノードのステータス情報を交換します。Gossip
は、MEET
、PING
、## で構成されます。 #PONG 3種類のメッセージで構成されています。
MEET、
PING、
PONG メッセージを送信するたびに、既知のノード リストから 2 つのノードをランダムに選択します。 (マスターノードまたはスレーブノードのいずれかになります) をまとめて受信者に送信します。
MEET、
PING、
PONG メッセージを受信すると、これら 2 つのノードを認識しているかどうかに基づいて、異なる処理を実行します。
以上がRedis 高可用性クラスターを段階的に理解します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。