Docker中搭建mongonDB副本集,实现事务处理

168 阅读3分钟

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

初始化

执行完成后,会有三个数据库实例

  1. 首先连接到任意一个 mongo 实例(通常选择 mongo1):
docker exec -it mongo1 mongosh
  1. 在 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();
  1. 等待几秒后,可以再次执行 rs.status() 确认配置是否生效。你会看到:

一个节点的 "stateStr" 显示为 "PRIMARY"

其他节点的 "stateStr" 显示为 "SECONDARY"

配置修改

如果副本集已经初始化过,但你想重新配置,可以按以下步骤操作:

  1. 首先连接到当前的主节点:
docker exec -it mongo1 mongosh
  1. 在 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;