ホームページ  >  記事  >  データベース  >  Redis 高可用性クラスターを段階的に理解します。

Redis 高可用性クラスターを段階的に理解します。

WBOY
WBOY転載
2022-03-04 17:03:452577ブラウズ

この記事では、Redis に関する関連知識を提供します。主にクラスター関連の問題を紹介します。Redis クラスターは分散データベース ソリューションです。クラスターはシャーディングを通じてデータを共有し、レプリケーションとフェイルオーバーの機能を提供します。皆さんのお役に立てば幸いです。

Redis 高可用性クラスターを段階的に理解します。

推奨学習: Redis 学習チュートリアル

いくつかの Redis 高可用性ソリューション。 「マスタースレーブモード」、「センチネルメカニズム」、「センチネルクラスター」が含まれます。

  • 「マスタースレーブモード」には、読み取りと書き込みの分離、読み取り圧力の共有、データのバックアップ、複数のコピーの提供などの利点があります。
  • 「センチネル メカニズム」は、マスター ノードに障害が発生した後、スレーブ ノードを自動的にマスター ノードに昇格させることができ、手動介入なしでサービスを復元できます。
  • 「Sentinel Cluster」は、単一マシンの Sentinel によって引き起こされる単一障害点と「誤判断」の問題を解決します。

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 の使用など)。

    水平方向の拡張: 現在の Redis インスタンスの数を水平方向に増加します。 。
  • まず、「垂直拡張」の利点は、実装が簡単かつ直接的なことです。ただし、このソリューションには 2 つの潜在的な問題もあります。

最初の問題は、RDB を使用してデータを永続化する場合、データ量が増加すると必要なメモリも増加し、子スレッドの実行時にメインスレッドが

fork
    してしまうことです。プロセスがブロックされる可能性があります。
  • 2 番目の質問: 垂直方向の拡張はハードウェアとコストによって制限されます。
  • これはわかりやすいのですが、結局のところ、メモリを 32GB から 64GB に拡張するのは簡単ですが、1TB に拡張しようとすると、ハードウェアの容量とコストの制限に直面します。
  • 「垂直拡張」と比較すると、「水平拡張」はスケーラビリティに優れたソリューションです。これは、より多くのデータを保存したい場合、このソリューションを採用すると、Redis インスタンスの数を増やすだけで済み、単一インスタンスのハードウェアとコストの制限を気にする必要がないためです。
Redis クラスターは、複数の Redis インスタンスを起動してクラスターを形成し、受信したデータを一定の規則に従って複数の部分に分割し、各部分に 1 つのインスタンスを使用する「水平拡張」に基づいて実装されています。 。

Redis クラスター

Redis クラスターは分散データベース ソリューションです。クラスターは

sharded

(

shardingスライシング とも呼ばれます) です。 ) データを共有し、レプリケーションおよびフェイルオーバー機能を提供します。 先ほどのシナリオに戻ります。25GB のデータを 5 つの部分に均等に分割し (もちろん、均等に分割する必要はありません)、それを保存するために 5 つのインスタンスを使用すると、各インスタンスは5GBのデータを保存するだけで済みます。以下に示すように:

Redis 高可用性クラスターを段階的に理解します。
次に、スライシング クラスターでは、インスタンスが 5GB データの RDB を生成すると、データ量ははるかに少なくなります。メインスレッドへのオーバーヘッド、長期にわたるブロック。 複数のインスタンスを使用してデータ スライスを保存すると、25 GB のデータを保存できるだけでなく、子プロセスがメイン スレッドをブロックするときに

fork

によって引き起こされる応答の突然の低下を回避することもできます。 実際にRedisを利用する場合、ビジネス規模の拡大に伴い、通常は大量のデータの保存が避けられません。 Redis クラスターは非常に優れたソリューションです。

Redis クラスターの構築方法の勉強を始めましょう?

Redis クラスターの構築

Redis クラスターは通常複数のノードで構成されますが、最初は各ノードは互いに独立しており、ノード間には関係がありません。動作するクラスターを形成するには、

独立したノードを接続

して、複数のノードを含む クラスターを形成する必要があります。 各ノードは

CLUSTER MEET

コマンドを通じて接続できます: <pre class="brush:php;toolbar:false">CLUSTER MEET &lt;ip&gt; &lt;port&gt;&lt;/port&gt;&lt;/ip&gt;</pre>

ip: クラスターに追加されるノードの IP
  • port : クラスターに追加するノードのポート
  • コマンド説明 :
CLUSTER MEET

コマンドをノード A に送信することで、コマンドを受け取ったノード A がクラスターを追加することができます。別のノード B からノード A が配置されているクラスター内にあります。 これは少し抽象的なので、例を見てみましょう。

現在、3 つの独立したノード

127.0.0.1:7001

127.0.0.1:7002127.0.0.1:7003 があるとします。

Redis 高可用性クラスターを段階的に理解します。最初にクライアントを使用してノード

7001

: <pre class="brush:php;toolbar:false">$ redis-cli -c -p 7001</pre> に接続し、次にコマンドをノード

7001# に送信します。 ## 、

7001 が存在するクラスターにノード 7002 を追加します。

127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7002
同様に、コマンドを 7003 に送信し、次のコマンドも追加します。

70017002 が配置されているクラスター。

127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7003
CLUSTER NODES
コマンドを使用して、クラスター内のノード情報を表示できます。

クラスターには、

7001Redis 高可用性クラスターを段階的に理解します。
70027003 の 3 つのノードが含まれています。ただし、単一インスタンスを使用すると、データがどこに存在し、クライアントがどこにアクセスするかが非常に明確になります。ただし、クラスターをスライスすると、複数のインスタンスの 分散管理の問題 が必然的に発生します。 スライス クラスターを使用したい場合は、2 つの大きな問題を解決する必要があります:

スライス後に複数のインスタンスにデータを分散するにはどうすればよいですか?
  • クライアントは、アクセスしたいデータがどのインスタンスにあるかをどのように判断するのでしょうか?
  • 次に、それらを 1 つずつ解決していきます。
データスライスとインスタンスの分散関係

スライスクラスタでは、データを異なるインスタンスに分散させる必要がありますが、データとインスタンスの対応はどうなっているのでしょうか?

これは、次に説明する

Redis クラスター

ソリューションに関連しています。ただし、最初にスライシング クラスターと

Redis クラスター の関係と違いを理解する必要があります。 Redis 3.0 より前は、公式はクラスターをスライスするための具体的なソリューションを提供していませんでした。 3.0 以降、公式はスライシング クラスターを実装するための

Redis Cluster
と呼ばれるソリューションを提供します。

実際、クラスタのスライスは大量のデータを保存するための一般的なメカニズムであり、このメカニズムにはさまざまな実装ソリューションがあります。

Redis Cluster
このスキームは、データとインスタンスに対応するルールを規定します。

具体的には、Redis Cluster

ソリューションは

Hash Slot (ハッシュ スロット) を使用して、データとインスタンス間のマッピング関係を処理します。 ハッシュ スロットと Redis インスタンスのマッピング

Redis クラスター

ソリューションでは、スライス クラスターには合計

16384 のハッシュ スロット (2^14) があります。 )、これらのハッシュ スロットはデータ パーティションに似ており、各キーと値のペアはそのキーに従ってハッシュ スロットにマッピングされます。 上記の分析では、3 つのノード 7001

7002、および 7003CLUSTER MEET を通じて接続されています。同じクラスターに移動しますが、クラスター内の 3 つのノードにはスロットが割り当てられていないため、このクラスターは現在 オフライン状態になっています。 では、これらのハッシュ スロットは特定の Redis インスタンスにどのようにマッピングされるのでしょうか? CLUSTER MEET

コマンドを使用してインスタンス間の接続を手動で確立してクラスターを形成し、その後

CLUSTER ADDSLOTS

コマンドを使用してハッシュ スロットの数を指定できます。各インスタンスの番号。

CLUSTER ADDSLOTS <slot> [slot ...]</slot>
Redis5.0 には、クラスターを作成するための CLUSTER CREATE コマンドが用意されており、このコマンドを使用すると、Redis はこれらのスロットをクラスター インスタンス間で自動的に均等に分配します。

举个例子,我们通过以下命令,给 700170027003 三个节点分别指派槽。

将槽 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

Redis 高可用性クラスターを段階的に理解します。

当三个 CLUSTER ADDSLOTS 命令都执行完毕之后,数据库中的 16384 个槽都已经被指派给了对应的节点,此时集群进入上线状态。

通过哈希槽,切片集群就实现了数据到哈希槽、哈希槽再到实例的分配。

但是,即使实例有了哈希槽的映射信息,客户端又是怎么知道要访问的数据在哪个实例上呢?

客户端如何定位数据?

一般来说,客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端。但是,在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的。

那么,客户端是如何可以在访问任何一个实例时,就能获得所有的哈希槽信息呢?

Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。

客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了。

当客户端向节点请求键值对时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:

  • 如果键所在的槽刚好指派给了当前节点,那么节点会直接执行这个命令;
  • 如果没有指派给当前节点,那么节点会向客户端返回一个 MOVED 错误,然后重定向(redirect)到正确的节点,并再次发送之前待执行的命令。

Redis 高可用性クラスターを段階的に理解します。

计算键属于哪个槽

节点通过以下算法来定义 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 告知客户端重定向访问新实例分两种情况:MOVEDASK 。下面我们分析下 ASK 重定向命令的使用方法。

重新分片

在集群中,实例和哈希槽的对应关系并不是一成不变的,最常见的变化有两个:

  • 在集群中,实例有新增或删除,Redis 需要重新分配哈希槽;
  • 为了负载均衡,Redis 需要把哈希槽在所有实例上重新分布一遍。

重新分片可以在线进行,也就是说,重新分片的过程中,集群不需要下线。

举个例子,上面提到,我们组成了 700170027003 三个节点的集群,我们可以向这个集群添加一个新节点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
Redis 高可用性クラスターを段階的に理解します。
在重新分片的期间,源节点向目标节点迁移槽的过程中,可能会出现这样一种情况:如果某个槽的数据比较多,部分迁移到新实例,还有一部分没有迁移咋办?

在这种迁移部分完成的情况下,客户端就会收到一条 ASK 报错信息。

ASK 错误

如果客户端向目标节点发送一个与数据库键有关的命令,并且这个命令要处理的键正好属于被迁移的槽时:

  • 源节点会先在自己的数据库里查找指定的键,如果找到的话,直接执行命令;
  • 相反,如果源节点没有找到,那么这个键就有可能已经迁移到了目标节点,源节点就会向客户端发送一个 ASK 错误,指引客户端转向目标节点,并再次发送之前要执行的命令。

看起来好像有点复杂,我们举个例子来解释一下。

Redis 高可用性クラスターを段階的に理解します。

如上图所示,节点 7003 正在向 7004 迁移 槽 16383,这个槽包含 helloworld,其中键 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 错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到关于 槽 iMOVED 错误之后,客户端每次遇到关于 槽 i 的命令请求时,都可以直接将命令请求发送至 MOVED 错误指向的节点,因为该节点就是目前负责 槽 i的节点。
  • 而 ASK 只是两个节点迁移槽的过程中的一种临时措施:在客户端收到关于 槽 iASK 错误之后,客户端只会在接下来的一次命令请求中将关于 槽 i 的命令请求发送到 ASK 错误指向的节点,但是 ,如果客户端再次请求 槽 i 中的数据,它还是会给原来负责 槽 i 的节点发送请求

这也就是说,ASK 命令的作用只是让客户端能给新实例发送一次请求,而且也不会更新客户端缓存的哈希槽分配信息。而不像 MOVED 命令那样,会更改本地缓存,让后续所有命令都发往新实例。

我们现在知道了 Redis 集群的实现原理。下面我们再来分析下,Redis 集群如何实现高可用的呢?

复制与故障转移

Redis 集群中的节点也是分为主节点和从节点。

  • 主节点用于处理槽
  • 从节点用于复制主节点,如果被复制的主节点下线,可以代替主节点继续提供服务。

举个例子,对于包含 7001 ~ 7004 的四个主节点的集群,可以添加两个节点:70057006。并将这两个节点设置为 7001 的从节点。

设置从节点命令:

CLUSTER REPLICATE <node_id></node_id>

如图:

Redis 高可用性クラスターを段階的に理解します。

如果此时,主节点 7001 下线,那么集群中剩余正常工作的主节点将在 7001 的两个从节点中选出一个作为新的主节点。

例如,节点 7005 被选中,那么原来由节点 7001 负责处理的槽会交给节点 7005 处理。而节点 7006 会改为复制新主节点 7005。如果后续 7001 重新上线,那么它将成为 7005 的从节点。如下图所示:

Redis 高可用性クラスターを段階的に理解します。

故障检测

集群中每个节点会定期向其他节点发送 PING 消息,来检测对方是否在线。如果接收消息的一方没有在规定时间内返回 PONG 消息,那么接收消息的一方就会被发送方标记为「疑似下线」。

集群中的各个节点会通过互相发消息的方式来交换各节点的状态信息。

节点的三种状态:

  • 在线状态
  • 疑似下线状态 PFAIL
  • 已下线状态 FAIL

一个节点认为某个节点失联了并不代表所有的节点都认为它失联了。在一个集群中,半数以上负责处理槽的主节点都认定了某个主节点下线了,集群才认为该节点需要进行主从切换。

Redis 集群节点采用 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。

我们都知道,哨兵机制可以通过监控、自动切换主库、通知客户端实现故障自动切换。那么 Redis Cluster 又是如何实现故障自动转移呢?

故障转移

当一个从节点发现自己正在复制的主节点进入了「已下线」状态时,从节点将开始对下线主节点进行故障切换。

故障转移的执行步骤:

  1. 在复制下线主节点的所有从节点里,选中一个从节点
  2. 被选中的从节点执行 SLAVEOF no one 命令,成为主节点
  3. 新的主节点会撤销所有对已下线主节点的槽指派,将这些槽全部指派给自己
  4. 新的主节点向集群广播一条 PONG 消息,让集群中其他节点知道,该节点已经由从节点变为主节点,且已经接管了原主节点负责的槽
  5. 新的主节点开始接收自己负责处理槽有关的命令请求,故障转移完成。

选主

这个选主方法和哨兵的很相似,两者都是基于 Raft算法 的领头算法实现的。流程如下:

  1. 集群的配置纪元是一个自增计数器,初始值为0;
  2. 当集群里的某个节点开始一次故障转移操作时,集群配置纪元加 1;
  3. 对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,第一个向主节点要求投票的从节点将获得主节点的投票;
  4. 当从节点发现自己复制的主节点进入「已下线」状态时,会向集群广播一条消息,要求收到这条消息,并且具有投票权的主节点为自己投票;
  5. 如果一个主节点具有投票权,且尚未投票给其他从节点,那么该主节点会返回一条消息给要求投票的从节点,表示支持从节点成为新的主节点;
  6. 每个参与选举的从节点会计算获得了多少主节点的支持;
  7. 如果集群中有 N 个具有投票权的主节点,当一个从节点收到的支持票 大于等于 N/2 + 1时,该从节点就会当选为新的主节点;
  8. 如果在一个配置纪元里没有从节点收集到足够多的票数,那么集群会进入一个新的配置纪元,并再次进行选主。

消息

集群中的各个节点通过发送和接收消息来进行通信,我们把发送消息的节点称为发送者,接收消息的称为接收者。

节点发送的消息主要有五种:

  • MEET メッセージ
  • PING メッセージ
  • PONG メッセージ
  • FAIL メッセージ
  • PUBLISH メッセージ

クラスターの各ノードは、Gossip プロトコルを通じてさまざまなノードのステータス情報を交換します。Gossip は、MEETPING、## で構成されます。 #PONG 3種類のメッセージで構成されています。

送信者は、

MEETPINGPONG メッセージを送信するたびに、既知のノード リストから 2 つのノードをランダムに選択します。 (マスターノードまたはスレーブノードのいずれかになります) をまとめて受信者に送信します。

受信者は、

MEETPINGPONG メッセージを受信すると、これら 2 つのノードを認識しているかどうかに基づいて、異なる処理を実行します。

##選択したノードが存在せず、既知のノード リストを受信して​​いる場合は、それが最初のコンタクトであることを意味し、受信者は選択したノードの IP とポート番号に基づいて通信します。
    # #すでに存在する場合は以前に通信が完了していることを意味し、元の選択ノードの情報が更新されます。
  • 推奨される学習:
  • Redis チュートリアル

以上がRedis 高可用性クラスターを段階的に理解します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。