MongoDB 副本集(Replica Set)は、自動障害回復機能を持つ主従クラスターで、1 つの Primary ノードと 1 つ以上の Secondary ノードで構成されています。副本集には固定の主ノードがなく、主ノードに障害が発生した場合、クラスター全体が新しい主ノードを選出してシステムにサービスを提供し、システムの高可用性を確保します。
副本集には 3 種類のノードがあります:主ノード、従ノード、仲裁ノード
1.主ノード はクライアントリクエストを処理し、データの読み書きを行います。
2.従ノード は主ノードからデータをコピーし、読み取りリクエストを受け付けることができます。主ノードに障害が発生した場合は、投票によって新しい主ノードを選出できます。
3.仲裁ノード はデータのコピーを保持せず、投票プロセスにのみ参加し、どのノードが主ノードになるかを決定します。
この記事では mongo5.0.8 を例に使用します
1.docker-compose.yaml#
ここでは、単一の mongo の docker-compose.yaml を直接示します。
version: '3.0'
services:
mongo1:
hostname: mongo1
image: mongo:5.0.8
restart: unless-stopped
container_name: mongodb1
command: --replSet app --bind_ip_all --keyFile /data/mongodb/keyFile
environment:
TZ: 'Asia/Shanghai'
MONGO_INITDB_ROOT_USERNAME: 'admin'
MONGO_INITDB_ROOT_PASSWORD: 'password'
ports:
- 27020:27017
volumes:
- ./data1:/data/db
- ./keyFile:/data/mongodb/keyFile
networks:
- bridge_network
mongo2:
hostname: mongo2
image: mongo:5.0.8
restart: unless-stopped
container_name: mongodb2
command: --replSet app --bind_ip_all --keyFile /data/mongodb/keyFile
environment:
TZ: 'Asia/Shanghai'
MONGO_INITDB_ROOT_USERNAME: 'admin'
MONGO_INITDB_ROOT_PASSWORD: 'password'
ports:
- 27018:27017
volumes:
- ./data2:/data/db
- ./keyFile:/data/mongodb/keyFile
networks:
- bridge_network
mongo3:
hostname: mongo3
image: mongo:5.0.8
restart: unless-stopped
container_name: mongodb3
command: --replSet app --bind_ip_all --keyFile /data/mongodb/keyFile
environment:
TZ: 'Asia/Shanghai'
MONGO_INITDB_ROOT_USERNAME: 'admin'
MONGO_INITDB_ROOT_PASSWORD: 'password'
ports:
- 27019:27017
volumes:
- ./data3:/data/db
- ./keyFile:/data/mongodb/keyFile
networks:
- bridge_network
networks:
bridge_network:
driver: bridge
ここでは、1 台のマシン上で 3 つのコンテナを起動することも、異なるマシン上で起動することもできます。本例では、上記のファイルを使用して、IP が 10.0.1.11(主)、10.0.1.31(従)、10.0.1.32(従)の 3 台のマシンで起動します。
10.0.1.11 でdocker compose up -d mongo1を実行します。
10.0.1.31 でdocker compose up -d mongo2を実行します。
10.0.1.32 でdocker compose up -d mongo3を実行します。
ここで docker compose up -d の後の mongo1、mongo2、mongo3 は、yaml ファイル内の 1 つのサービスを起動することを指します。yaml ファイルを異なるファイルに分割して、対応するマシンで起動する場合は、コマンドの後にサービス名を追加する必要はありません。
注意が必要です:
副本を構成する各マシンの keyFile ファイルは同じでなければなりません!!!mongodb インスタンスは正しい keyfile を持っている場合のみ副本集に参加できます。keyFile ファイルの具体的な操作方法については、【mongo トランザクション】docker-compose を使用して mongo を起動し、「単一副本モード」でトランザクションをサポートするを参照してください。
2. コンテナ起動後#
コンテナが起動した後、主ノードとしてのコンテナに入るために docker exec コマンドを使用し、mongo シェルに入ります。
mongo -u admin --authenticationDatabase admin
mongo 副本集を初期化します。
rs.initiate({
_id: "app",
members: [
{ _id: 0, host: "10.0.1.11:27020" },
{ _id: 1, host: "10.0.1.31:27018" },
{ _id: 2, host: "10.0.1.32:27019" }
]
})
注意:初期化時に、無引数の rs.initiate () を直接使用できると思い込み、次に rs.add (‘ip’) を使用して残りのノードを副本集に追加できると誤解していました。しかし、これは間違いです。初期化時に無引数の初期化方法を使用すると、mongo はホストマシン (この時、mongo にとってホストマシンは docker コンテナです) の hostname + ポートを取得して mongo の host を構成し、設定に書き込みます。しかし、mongo のホストマシンは実際にはコンテナであるため、コンテナの hostname と mongo がコンテナ内で使用するポートを取得します。
明らかに、異なるマシンの mongo 副本が相互に通信する際、この host は無効です。
したがって、初期化時には具体的なマシンを直接指定するのが最良です。
初期化が完了した後、rs.status()を使用して副本集の状態を確認できます。
一部の内容を抜粋します。
"members" : [
{
"_id" : 0,
"name" : "10.0.1.11:27020",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 2299,
"optime" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2024-06-05T04:01:57Z"),
"lastAppliedWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastDurableWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1717559887, 1),
"electionDate" : ISODate("2024-06-05T03:58:07Z"),
"configVersion" : 1,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "10.0.1.31:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 249,
"optime" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2024-06-05T04:01:57Z"),
"optimeDurableDate" : ISODate("2024-06-05T04:01:57Z"),
"lastAppliedWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastDurableWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastHeartbeat" : ISODate("2024-06-05T04:02:05.988Z"),
"lastHeartbeatRecv" : ISODate("2024-06-05T04:02:05.581Z"),
"pingMs" : NumberLong(1),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "10.0.1.11:27020",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
},
{
"_id" : 2,
"name" : "10.0.1.32:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 249,
"optime" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1717560117, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2024-06-05T04:01:57Z"),
"optimeDurableDate" : ISODate("2024-06-05T04:01:57Z"),
"lastAppliedWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastDurableWallTime" : ISODate("2024-06-05T04:01:57.808Z"),
"lastHeartbeat" : ISODate("2024-06-05T04:02:05.988Z"),
"lastHeartbeatRecv" : ISODate("2024-06-05T04:02:05.629Z"),
"pingMs" : NumberLong(1),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "10.0.1.11:27020",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1,
"configTerm" : 1
}
],
ここでは、初期化を実行したマシンが主ノードであり、残りが従ノードであることがわかります。
stateStr フィールドの各意味について:
STARTUP:メンバーが起動中です。
PRIMARY:メンバーは主ノードです。
SECONDARY:メンバーは従ノードです。
RECOVERING:メンバーが回復中です。
ARBITER:メンバーは仲裁ノードです。
DOWN:メンバーが大多数のノードと通信できません。
UNKNOWN:メンバーの状態が不明です。
ROLLBACK:メンバーがロールバック操作を行っています。
REMOVED:メンバーが副本集から削除されました。
最後に、後続の新しいノードを追加する必要がある場合は、rs.add (‘ip’) を使用して追加できます。*** 仲裁ノードを追加する必要がある場合(推奨しません)*** は、rs.add (‘ip’,true) を使用して追加できます。
ノードの数について
mongodb の公式では、ノードが 2 つを超える場合、推奨される副本集メンバーは奇数のメンバーであり、仲裁ノードを使用しないことを推奨しています。したがって、実際の運用では、可能な限り奇数のノードを確保し、仲裁ノードを使用しないようにします。やむを得ず、通常の主従ノードの合計数が偶数の場合、この時に仲裁ノードを副本集に追加することができます(仲裁ノードはストレージなどの業務に参加せず、投票にのみ参加するため、仲裁ノードに対する性能の要求は非常に低く、1 つの仲裁ノードを追加する難易度はそれほど高くありません)。これにより、投票権を持つノードの合計数が奇数になります。
具体的に奇数のノードが必要な理由は、投票選挙のアルゴリズムや実際の展開状況を説明する必要があります(脳裂問題)。