Redis Cluster write safety分析
9481 ワード
redis clusterはredisの分散実装である.公式文書cluster-specが強調したように、その設計は高性能と線形拡張能力を優先し、write safetyを保証するために最善を尽くしている.
ここでwriteロスとは、クライアントackに返信した後、後続のリクエストでデータが変更されていない場合やロスしている場合を指し、主に切替、インスタンス再起動、脳裂の3つのケースがこの問題を引き起こす可能性があり、以下、順次分析する.
failoverはルーティングの変更をもたらし,アクティブ/パッシブの場合は別々に議論する必要がある.
表現の便宜上、cluster状態は正常で、node Cはmasterで、slot 1-100を担当し、slaveはC'に対応すると仮定している.
マスターCを切ると、slave C'は最大2倍cluster_node_timeoutの時間内にCをFAILとしてマークし,failoverロジックをトリガする.
slave C'がmasterに正常に切り替わるまで、1-100 slotはCが担当し、アクセスが間違っています.C'がmasterに切断された後、gossipはルーティングの変更を放送し、この過程でclientはC'にアクセスし、正常な応答を得ることができ、他の古いルーティングを持つnodeにアクセスし、要求はMOVEDに切られたCにアクセスされ、アクセスはエラーを報告する.
write損失が発生する可能性のある唯一のcaseは、プライマリスレーブ非同期レプリケーションメカニズムによって発生します.マスターに書き込まれたデータがslaveに同期できずに切られた場合、このデータは失われます(再起動後はmerge操作は存在しません).master返信client ackは同期slaveとほぼ同時に行われ、このような状況はめったに発生しないが、これはリスクであり、時間ウィンドウが小さい.
アクティブfailoverはsysadminによってslave node上で
完全manual failoverプロセスは、前のブログで詳細に議論され、以下の6つのステップに要約される. slaveは要求を開始し、gossipメッセージはCLUSTERMSMGを携帯する.TYPE_MFSTARTマーク. masterはclientをブロックし、停止時間は2倍CLUSTER_MF_TIMEOUT、現在のバージョンは10 sです. slaveは、プライマリからoffsetデータのコピーを追跡する. slaveが選挙を開始し、当選した. slaveは、自身のroleを切り替え、slotsを引き継ぎ、新しいルーティング情報をブロードキャストする. 他のノードはルーティングを変更し、clusterルーティングは平らになります.
3つのオプションにはそれぞれ異なる動作があり、以下のように分析され、(1)デフォルトのオプションです.完全なmfプロセスを実行すると、masterは停止動作をするため、writeが失われる問題はありません.
(2)FORCEオプション.手順4から実行します.slave C'統計票フェーズでは、master Cは依然としてユーザー要求を正常に受信することができ、writeの損失を招く可能性があります.mfは将来のある時点で実行を開始し、timeout時間はCLUSTER_MF_TIMEOUT(現バージョン5 s)は、
(3)TAKEOVERオプション.手順5から実行します.slaveは自分のconfigEpoch(他のnodeの同意を必要としない)を直接増やし、slotsを引き継ぐ.slave C'からmasterに切り替え、元のmasterノードCにルーティングを更新し、Cに送信する要求に至るまで、writeが失われる可能性があり、一般的にはpingの時間内に完了し、タイムウィンドウが小さい.CとC'以外のノードがルーティングヒステリシスを更新すると、一度だけMOVEDエラーが発生し、writeが失われることはありません.
clusterState構造体には、clusterのグローバル状態を表すstateメンバー変数があり、現在のclusterがサービスを提供できるかどうかを制御します.次の2つの値があります.
サーバが再起動すると、stateはCLUSTER_に初期化されます.FAIL,コードロジックは
CLUSTER_についてFAIL状態のclusterはアクセスを拒否しており、コードは以下のように参照されます.
ポイントは
注意:redis clusterは、各nodeに直接アクセスできる非中心化されたルーティング管理ポリシーを採用しています.commandを実行するnodeが現在の接続ではない場合、本当にcommandを実行するnodeを指す-MOVEのリダイレクトエラーが返されます.
CLUSTERでなければなりませんOK状態のclusterは正常にアクセスできます.
私たちは、このような制限はWrite safetyを保証するために非常に必要だと言っています.マスターAが切れると、対応するslave A'が選挙で新マスターに当選することが想像できる.このとき、Aは再起動され、ちょうどclientが見たルートが更新されていないので、Aにデータを書きます.これらのwriteを受け入れると、データが失われます.A'こそこのshardingみんなが公認しているマスターです.したがって、A'が再起動されると、ルーティング変更が完了するまでサービスを無効にする必要があります.
では、いつclusterがCLUSTERに現れるのかFAIL -> CLUSTER_OKの状態変更は?答えは
キーロジックは
以上の論理ではcluster状態変更はCLUSTER_を遅延させることが分かる.WRITABLE_DELAYミリ秒、現在のバージョンは2 sです.
アクセス遅延はルーティング変更を待つためですが、ルーティング変更はいつトリガーされますか?新しいserverが起動したばかりで、他のnodeとgossip通信を行うlinkはnullであり、
CLUSTER_WRITABLE_DELAYミリ秒後、Aノードはアクセスを再開し、CLUSTER_WRITABLE_DELAYのタイムウィンドウはルーティングを更新するのに十分です.
ネットワークの信頼性が低いため,ネットワークパーティションはCAP理論におけるPという考慮すべき問題である.
partitionが発生するとclusterはmajorityとminorityの2つの部分に分割され,ここではパーティション内のmasterノードの数で区別される.
(1)minority部分ではslaveが選挙を開始するが、多くのmasterの票を受け取ることができず、正常なfailoverプロセスを完了することができない.また、
上のコードから分かるように、minorityではcluster状態がしばらくするとCLUSTER_に変更されます.FAIL.ただし、minorityに区切られたマスターBについては、ステータスが変更されるまでアクセスできます.これにはタイムウィンドウがあり、writeが失われます!!
このタイムウィンドウのサイズは、
また、サービスが無効になった時間、すなわちamong_が記録されます.minority_time.
(2)majority部分ではslaveが選挙を開始し,Bのslave B'を例にfailoverが新しいmasterにカットされ,サービスを提供する.partition時間がclusterより小さい場合node_timeoutは、PFIEL識別子が現れなければwriteが失われないほどです.
partitionが回復すると、minorityの古いマスターBがclusterに再追加され、Bがサービスを提供するには、まずcluster状態をCLUSTER_FAILをCLUSTERに変更OKです.では、いつ変更すればいいですか.
Bでは古いルーティングであることを知っていますが、この場合slaveに変更する必要があります.そのため、ルーティングの変更を待つ必要があります.そうしないと、writeが失われる可能性があります(前に分析しました)、同じ
タイムウィンドウがcluster_であることがわかります.node_timeout、最大5 s、最低500 ms.
failoverは、選挙とプライマリの非同期レプリケーションデータの偏差によってwriteが失われる可能性があります.マスター再起動CLUSTER経由WRITABLE_DELAY遅延、cluster状態がCLUSTER_に変更されるのを待つOK、再アクセス可能、writeロスは存在しません.partitionのminority部分、cluster状態でCLUSTER_に変更FAILの前に、writeの紛失がある可能性があります.partitionが回復した後、rejoin_を通過delay遅延、cluster状態がCLUSTERに変更されるのを待つOK、再アクセス可能、writeロスは存在しません.
ここでwriteロスとは、クライアントackに返信した後、後続のリクエストでデータが変更されていない場合やロスしている場合を指し、主に切替、インスタンス再起動、脳裂の3つのケースがこの問題を引き起こす可能性があり、以下、順次分析する.
ケース1主従切替
failoverはルーティングの変更をもたらし,アクティブ/パッシブの場合は別々に議論する必要がある.
受動failover
表現の便宜上、cluster状態は正常で、node Cはmasterで、slot 1-100を担当し、slaveはC'に対応すると仮定している.
マスターCを切ると、slave C'は最大2倍cluster_node_timeoutの時間内にCをFAILとしてマークし,failoverロジックをトリガする.
slave C'がmasterに正常に切り替わるまで、1-100 slotはCが担当し、アクセスが間違っています.C'がmasterに切断された後、gossipはルーティングの変更を放送し、この過程でclientはC'にアクセスし、正常な応答を得ることができ、他の古いルーティングを持つnodeにアクセスし、要求はMOVEDに切られたCにアクセスされ、アクセスはエラーを報告する.
write損失が発生する可能性のある唯一のcaseは、プライマリスレーブ非同期レプリケーションメカニズムによって発生します.マスターに書き込まれたデータがslaveに同期できずに切られた場合、このデータは失われます(再起動後はmerge操作は存在しません).master返信client ackは同期slaveとほぼ同時に行われ、このような状況はめったに発生しないが、これはリスクであり、時間ウィンドウが小さい.
アクティブfailover
アクティブfailoverはsysadminによってslave node上で
CLUSTER FAILOVER [FORCE|TAKEOVER]
コマンドを実行してトリガーされます.完全manual failoverプロセスは、前のブログで詳細に議論され、以下の6つのステップに要約される.
3つのオプションにはそれぞれ異なる動作があり、以下のように分析され、(1)デフォルトのオプションです.完全なmfプロセスを実行すると、masterは停止動作をするため、writeが失われる問題はありません.
(2)FORCEオプション.手順4から実行します.slave C'統計票フェーズでは、master Cは依然としてユーザー要求を正常に受信することができ、writeの損失を招く可能性があります.mfは将来のある時点で実行を開始し、timeout時間はCLUSTER_MF_TIMEOUT(現バージョン5 s)は、
clusterCron
毎にチェックされます.(3)TAKEOVERオプション.手順5から実行します.slaveは自分のconfigEpoch(他のnodeの同意を必要としない)を直接増やし、slotsを引き継ぐ.slave C'からmasterに切り替え、元のmasterノードCにルーティングを更新し、Cに送信する要求に至るまで、writeが失われる可能性があり、一般的にはpingの時間内に完了し、タイムウィンドウが小さい.CとC'以外のノードがルーティングヒステリシスを更新すると、一度だけMOVEDエラーが発生し、writeが失われることはありません.
ケース2マスター再起動
cluster状態初期化
clusterState構造体には、clusterのグローバル状態を表すstateメンバー変数があり、現在のclusterがサービスを提供できるかどうかを制御します.次の2つの値があります.
#define CLUSTER_OK 0 /* Everything looks ok */
#define CLUSTER_FAIL 1 /* The cluster can't work */
サーバが再起動すると、stateはCLUSTER_に初期化されます.FAIL,コードロジックは
clusterInit
関数で見つけることができる.CLUSTER_についてFAIL状態のclusterはアクセスを拒否しており、コードは以下のように参照されます.
int processCommand(client *c) {
...
if (server.cluster_enabled &&
!(c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_LUA &&
server.lua_caller->flags & CLIENT_MASTER) &&
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0 &&
c->cmd->proc != execCommand))
{
int hashslot;
int error_code;
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,
&hashslot,&error_code);
...
}
...
}
ポイントは
getNodeByQuery
関数で、clusterモードがオンになった後、本当にcommandを実行するnodeを検索するために使用されます.注意:redis clusterは、各nodeに直接アクセスできる非中心化されたルーティング管理ポリシーを採用しています.commandを実行するnodeが現在の接続ではない場合、本当にcommandを実行するnodeを指す-MOVEのリダイレクトエラーが返されます.
getNodeByQuery
関数の一部の論理を見てみましょう clusterNode *getNodeByQuery(client *c,
struct redisCommand *cmd, robj **argv,
int argc, int *hashslot,
int *error_code) {
...
if (server.cluster->state != CLUSTER_OK) {
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
return NULL;
}
...
}
CLUSTERでなければなりませんOK状態のclusterは正常にアクセスできます.
私たちは、このような制限はWrite safetyを保証するために非常に必要だと言っています.マスターAが切れると、対応するslave A'が選挙で新マスターに当選することが想像できる.このとき、Aは再起動され、ちょうどclientが見たルートが更新されていないので、Aにデータを書きます.これらのwriteを受け入れると、データが失われます.A'こそこのshardingみんなが公認しているマスターです.したがって、A'が再起動されると、ルーティング変更が完了するまでサービスを無効にする必要があります.
cluster状態変更
では、いつclusterがCLUSTERに現れるのかFAIL -> CLUSTER_OKの状態変更は?答えは
clusterCron
のタイミングタスクで探します. void clusterCron(void) {
...
if (update_state || server.cluster->state == CLUSTER_FAIL)
clusterUpdateState();
}
キーロジックは
clusterUpdateState
関数にあります.#define CLUSTER_WRITABLE_DELAY 2000
void clusterUpdateState(void) {
static mstime_t first_call_time = 0;
...
if (first_call_time == 0) first_call_time = mstime();
if (nodeIsMaster(myself) &&
server.cluster->state == CLUSTER_FAIL &&
mstime() - first_call_time < CLUSTER_WRITABLE_DELAY) return;
new_state = CLUSTER_OK;
...
if (new_state != server.cluster->state) {
...
server.cluster->state = new_state;
}
}
以上の論理ではcluster状態変更はCLUSTER_を遅延させることが分かる.WRITABLE_DELAYミリ秒、現在のバージョンは2 sです.
アクセス遅延はルーティング変更を待つためですが、ルーティング変更はいつトリガーされますか?新しいserverが起動したばかりで、他のnodeとgossip通信を行うlinkはnullであり、
clusterCron
でチェックされた後、順次接続され、pingが送信されることを知っています.ルーティングが期限切れになった古いノードとして、他のノードからupdateメッセージを受信し、自身のルーティングを変更します.CLUSTER_WRITABLE_DELAYミリ秒後、Aノードはアクセスを再開し、CLUSTER_WRITABLE_DELAYのタイムウィンドウはルーティングを更新するのに十分です.
case 3 partition
partition発生
ネットワークの信頼性が低いため,ネットワークパーティションはCAP理論におけるPという考慮すべき問題である.
partitionが発生するとclusterはmajorityとminorityの2つの部分に分割され,ここではパーティション内のmasterノードの数で区別される.
(1)minority部分ではslaveが選挙を開始するが、多くのmasterの票を受け取ることができず、正常なfailoverプロセスを完了することができない.また、
clusterCron
では、ほとんどのノードがCLUSTERとしてマークされます.NODE_PFIAL状態、さらにclusterUpdateState
をトリガするロジックは、概ね以下のように、void clusterCron(void) {
...
di = dictGetSafeIterator(server.cluster->nodes);
while((de = dictNext(di)) != NULL) {
...
delay = now - node->ping_sent;
if (delay > server.cluster_node_timeout) {
if (!(node->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL))) {
serverLog(LL_DEBUG,"*** NODE %.40s possibly failing", node->name);
node->flags |= CLUSTER_NODE_PFAIL;
update_state = 1;
}
}
}
...
if (update_state || server.cluster->state == CLUSTER_FAIL)
clusterUpdateState();
}
clusterUpdateState
関数ではclusterの状態が変わります.void clusterUpdateState(void) {
static mstime_t among_minority_time;
...
{
dictIterator *di;
dictEntry *de;
server.cluster->size = 0;
di = dictGetSafeIterator(server.cluster->nodes);
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
if (nodeIsMaster(node) && node->numslots) {
server.cluster->size++;
if ((node->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) == 0)
reachable_masters++;
}
}
dictReleaseIterator(di);
}
{
int needed_quorum = (server.cluster->size / 2) + 1;
if (reachable_masters < needed_quorum) {
new_state = CLUSTER_FAIL;
among_minority_time = mstime();
}
}
...
}
上のコードから分かるように、minorityではcluster状態がしばらくするとCLUSTER_に変更されます.FAIL.ただし、minorityに区切られたマスターBについては、ステータスが変更されるまでアクセスできます.これにはタイムウィンドウがあり、writeが失われます!!
このタイムウィンドウのサイズは、
clusterCron
関数で計算できます.partition時間から計算するとcluster_node_timeout時間後にnodeがPFIALとマークされ、gossipメッセージ伝播がPFIALを携帯するノードに偏っているため、node Bはcluster_を待つ必要はありません.node_timeout/2 cluster nodes pingをパスすると、clusterをCLUSTERとしてマークできます.FAIL.タイムウィンドウはcluster_node_timeout. また、サービスが無効になった時間、すなわちamong_が記録されます.minority_time.
(2)majority部分ではslaveが選挙を開始し,Bのslave B'を例にfailoverが新しいmasterにカットされ,サービスを提供する.partition時間がclusterより小さい場合node_timeoutは、PFIEL識別子が現れなければwriteが失われないほどです.
partitionリカバリ
partitionが回復すると、minorityの古いマスターBがclusterに再追加され、Bがサービスを提供するには、まずcluster状態をCLUSTER_FAILをCLUSTERに変更OKです.では、いつ変更すればいいですか.
Bでは古いルーティングであることを知っていますが、この場合slaveに変更する必要があります.そのため、ルーティングの変更を待つ必要があります.そうしないと、writeが失われる可能性があります(前に分析しました)、同じ
clusterUpdateState
関数の論理にあります.#define CLUSTER_MAX_REJOIN_DELAY 5000
#define CLUSTER_MIN_REJOIN_DELAY 500
void clusterUpdateState(void) {
...
if (new_state != server.cluster->state) {
mstime_t rejoin_delay = server.cluster_node_timeout;
if (rejoin_delay > CLUSTER_MAX_REJOIN_DELAY)
rejoin_delay = CLUSTER_MAX_REJOIN_DELAY;
if (rejoin_delay < CLUSTER_MIN_REJOIN_DELAY)
rejoin_delay = CLUSTER_MIN_REJOIN_DELAY;
if (new_state == CLUSTER_OK &&
nodeIsMaster(myself) &&
mstime() - among_minority_time < rejoin_delay)
{
return;
}
}
}
タイムウィンドウがcluster_であることがわかります.node_timeout、最大5 s、最低500 ms.
小結
failoverは、選挙とプライマリの非同期レプリケーションデータの偏差によってwriteが失われる可能性があります.マスター再起動CLUSTER経由WRITABLE_DELAY遅延、cluster状態がCLUSTER_に変更されるのを待つOK、再アクセス可能、writeロスは存在しません.partitionのminority部分、cluster状態でCLUSTER_に変更FAILの前に、writeの紛失がある可能性があります.partitionが回復した後、rejoin_を通過delay遅延、cluster状態がCLUSTERに変更されるのを待つOK、再アクセス可能、writeロスは存在しません.