MongoDB 副本集介绍
MongoDB 副本集(Replica Set)包括主节点(primary)跟副本节点(Secondaries)。
主节点只能有一个,所有的写操作请求都在主节点上面处理。副本节点可以有多个,通过同步主节点的操作日志(oplog)来备份主节点数据。
具体更多的概念和细节可以阅读这个MongoDB 副本集之入门篇,本文只讲大概的搭建和使用
环境准备
1: 首先肯定需要有docker环境,自己百度一下吧
2: 为了方便起见,提供docker-compose.yml 直接运行,里面配置了mongonDB的版本,这个需要在4.0以上才可以支持事务处理,所以我们用了6.0的版本, 配置如下
services:
mongo1:
image: mongo:6.0.2
container_name: mongo1
networks:
- mongo-replica-set
ports:
- 27017:27017
command: ["mongod", "--replSet", "rs0"]
mongo2:
image: mongo:6.0.2
container_name: mongo2
networks:
- mongo-replica-set
ports:
- 27018:27017
command: ["mongod", "--replSet", "rs0"]
mongo3:
image: mongo:6.0.2
container_name: mongo3
networks:
- mongo-replica-set
ports:
- 27019:27017
command: ["mongod", "--replSet", "rs0"]
networks:
mongo-replica-set:
driver: bridge
初始化
执行完成后,会有三个数据库实例
- 首先连接到任意一个 mongo 实例(通常选择 mongo1):
docker exec -it mongo1 mongosh
- 在 mongosh 中执行以下命令初始化副本集:
// 初始化副本集配置
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo1:27017", priority: 2 }, // 优先级最高,会被选为主节点
{ _id: 1, host: "mongo2:27017", priority: 1 },
{ _id: 2, host: "mongo3:27017", priority: 1 },
],
});
// 查看副本集状态
rs.status();
- 等待几秒后,可以再次执行
rs.status()确认配置是否生效。你会看到:
一个节点的 "stateStr" 显示为 "PRIMARY"
其他节点的 "stateStr" 显示为 "SECONDARY"
配置修改
如果副本集已经初始化过,但你想重新配置,可以按以下步骤操作:
- 首先连接到当前的主节点:
docker exec -it mongo1 mongosh
- 在 mongosh 中执行以下命令重新配置:
// 强制重新配置副本集
rs.reconfig(
{
_id: "rs0",
members: [
{ _id: 0, host: "mongo1:27017", priority: 2 },
{ _id: 1, host: "mongo2:27017", priority: 1 },
{ _id: 2, host: "mongo3:27017", priority: 1 },
],
},
{ force: true }
);
// 查看副本集状态
rs.status();
最后
完成上述工作后,我们只要按照正常连接, 连接主节点,也就是mongo1就可以了, 就可以在写数据库操作的时候支持事务处理
如我们在eggjs框架中
const Service = require("egg").Service;
class RechargeService extends Service {
// 处理支付回调
async handlePayNotify(notifyData) {
const { ctx } = this;
const { transaction_id, out_trade_no, result_code } = notifyData;
const session = await ctx.model.RechargeOrder.startSession(); //创建事务
try {
await session.startTransaction();
// 查找并更新充值订单
const rechargeOrder = await ctx.model.RechargeOrder.findOneAndUpdate(
{ orderNo: out_trade_no, status: 1 }, // 状态为1表示待支付
{
status: 2, // 支付成功
transactionId: transaction_id,
payTime: new Date(),
updatedAt: new Date(),
},
{ new: true, session }
);
if (!rechargeOrder) {
ctx.throw(404, "订单不存在或已处理");
}
// 根据充值类型处理不同逻辑
if (rechargeOrder.rechargeType === 1) {
// 余额充值:更新用户余额
await ctx.model.User.updateOne(
{ openId: rechargeOrder.openId },
{ $inc: { balance: rechargeOrder.amount } },
{ session }
);
} else if (rechargeOrder.rechargeType === 2) {
// VIP购买:更新VIP订单状态和用户VIP信息
const vipOrder = await ctx.model.VipOrder.findOne({
orderNo: rechargeOrder.relatedOrderNo,
status: 0,
}).session(session);
if (!vipOrder) {
ctx.throw(404, "VIP订单不存在或已处理");
}
// 更新VIP订单状态
await ctx.model.VipOrder.updateOne(
{ orderNo: rechargeOrder.relatedOrderNo },
{
status: 1,
payTime: new Date(),
transactionId: notifyData.transaction_id,
},
{ session }
);
// 更新用户VIP状态
await ctx.model.User.updateOne(
{ openId: rechargeOrder.openId },
{
vipLevel: vipOrder.level,
vipExpireTime: vipOrder.endTime,
},
{ session }
);
}
await session.commitTransaction();
return true;
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
await session.endSession();
}
}
}
module.exports = RechargeService;