MongoDB入门学习教程,从入门到精通,MongoDB从应用程序连接副本集(12)

从应用程序连接副本集

一、客户端到副本集的连接行为

1.1 连接字符串配置

案例1:标准副本集连接字符串
javascript 复制代码
// ==================== Node.js 应用程序连接示例 ====================

const { MongoClient } = require('mongodb');

// 副本集连接字符串格式
// mongodb://[username:password@]host1:port1,host2:port2,.../database?options

// 基础连接字符串
const standardUri = 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0';

// 带认证和选项的完整连接字符串
const advancedUri = 'mongodb://admin:password@localhost:27017,localhost:27018,localhost:27019/' +
    'testdb?' +
    'replicaSet=rs0&' +           // 指定副本集名称(必需)
    'readPreference=secondary&' + // 读偏好
    'connectTimeoutMS=10000&' +    // 连接超时(毫秒)
    'socketTimeoutMS=45000&' +     // Socket超时
    'maxPoolSize=50&' +           // 连接池最大连接数
    'minPoolSize=10&' +           // 连接池最小连接数
    'waitQueueTimeoutMS=5000';     // 等待队列超时

async function connectToReplicaSet() {
    const client = new MongoClient(advancedUri, {
        // 使用新的URL解析器
        useNewUrlParser: true,
        useUnifiedTopology: true
    });

    try {
        // 连接到副本集
        await client.connect();
        console.log('成功连接到副本集');
        
        // 获取当前连接的主节点信息
        const adminDb = client.db('admin');
        const isMaster = await adminDb.command({ isMaster: 1 });
        console.log('当前主节点:', isMaster.primary);
        console.log('所有节点:', isMaster.hosts);
        
        // 监听连接池事件
        client.on('connectionCreated', (event) => {
            console.log('新连接创建:', event);
        });
        
        client.on('connectionClosed', (event) => {
            console.log('连接关闭:', event);
        });
        
    } catch (error) {
        console.error('连接失败:', error);
    }
}

connectToReplicaSet();

1.2 连接行为特性

javascript 复制代码
// ==================== 连接行为演示 ====================

const { MongoClient } = require('mongodb');

// 连接字符串示例
const uri = 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0';

async function demonstrateConnectionBehavior() {
    const client = new MongoClient(uri, {
        useNewUrlParser: true,
        useUnifiedTopology: true,
        // 连接行为相关配置
        serverSelectionTimeoutMS: 30000,  // 服务器选择超时(30秒)
        heartbeatFrequencyMS: 10000,      // 心跳频率(10秒)
        retryWrites: true,                // 启用写重试
        retryReads: true                  // 启用读重试
    });
    
    await client.connect();
    
    // 1. 自动故障转移演示
    const db = client.db('test');
    const collection = db.collection('users');
    
    // 模拟主节点故障时的自动切换
    console.log('=== 自动故障转移测试 ===');
    
    // 持续执行写入操作,观察故障转移
    const intervalId = setInterval(async () => {
        try {
            const result = await collection.insertOne({
                timestamp: new Date(),
                message: '测试写入'
            });
            console.log(`写入成功,主节点: ${result.connection?.address || '未知'}`);
        } catch (error) {
            console.error('写入失败,正在重试:', error.message);
        }
    }, 2000);
    
    // 10秒后停止测试
    setTimeout(() => {
        clearInterval(intervalId);
        client.close();
    }, 10000);
}

demonstrateConnectionBehavior();

1.3 驱动程序连接池管理

java 复制代码
// ==================== Java 应用程序连接示例 ====================

import com.mongodb.*;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

public class ReplicaSetConnection {
    public static void main(String[] args) {
        // 构建副本集连接字符串
        ConnectionString connectionString = new ConnectionString(
            "mongodb://localhost:27017,localhost:27018,localhost:27019/" +
            "testdb?replicaSet=rs0"
        );
        
        // 配置连接池和连接行为
        MongoClientSettings settings = MongoClientSettings.builder()
            .applyConnectionString(connectionString)
            // 连接池配置
            .applyToConnectionPoolSettings(builder -> builder
                .maxSize(100)                    // 最大连接数
                .minSize(10)                     // 最小连接数
                .maxWaitTime(5000, TimeUnit.MILLISECONDS)  // 等待连接超时
                .maxConnectionIdleTime(60000, TimeUnit.MILLISECONDS) // 连接空闲时间
            )
            // 服务器选择配置
            .applyToServerSettings(builder -> builder
                .heartbeatFrequency(10000, TimeUnit.MILLISECONDS)  // 心跳频率
                .serverSelectionTimeout(30000, TimeUnit.MILLISECONDS) // 服务器选择超时
            )
            // 重试配置
            .retryWrites(true)      // 启用写重试
            .retryReads(true)       // 启用读重试
            .build();
        
        // 创建MongoDB客户端
        try (MongoClient client = MongoClients.create(settings)) {
            MongoDatabase database = client.getDatabase("testdb");
            MongoCollection<Document> collection = database.getCollection("users");
            
            // 执行操作
            Document doc = new Document("name", "张三")
                .append("age", 25)
                .append("timestamp", new java.util.Date());
            
            collection.insertOne(doc);
            System.out.println("文档插入成功");
            
            // 查询所有节点信息
            Document result = database.runCommand(new Document("isMaster", 1));
            System.out.println("主节点: " + result.getString("primary"));
            System.out.println("所有节点: " + result.get("hosts"));
        }
    }
}

二、在写入时等待复制(Write Concern)

2.1 写关注级别详解

javascript 复制代码
// ==================== 写关注配置示例 ====================

const { MongoClient } = require('mongodb');

const uri = 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0';

async function demonstrateWriteConcern() {
    const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
    await client.connect();
    
    const db = client.db('test');
    const collection = db.collection('orders');
    
    // 1. 默认写关注(w:1)- 仅主节点确认
    console.log('=== 默认写关注(w:1)===');
    const result1 = await collection.insertOne(
        { orderId: 1, amount: 100, status: 'pending' },
        { writeConcern: { w: 1 } }  // 主节点确认即可
    );
    console.log('插入成功,写入时间:', result1.insertedId);
    
    // 2. 多数派写关注(w: "majority")- 等待多数节点确认
    console.log('\n=== 多数派写关注 ===');
    const result2 = await collection.insertOne(
        { orderId: 2, amount: 200, status: 'confirmed' },
        { writeConcern: { w: "majority", wtimeout: 5000 } }  // 5秒超时
    );
    console.log('插入成功,已复制到多数节点');
    
    // 3. 指定节点数量写关注(w: 3)- 等待3个节点确认
    console.log('\n=== 指定节点数量写关注 ===');
    try {
        const result3 = await collection.insertOne(
            { orderId: 3, amount: 300, status: 'processing' },
            { writeConcern: { w: 3, wtimeout: 3000 } }  // 等待3个节点,3秒超时
        );
        console.log('插入成功,3个节点已确认');
    } catch (error) {
        console.error('写关注超时:', error.message);
    }
    
    // 4. 自定义标签写关注(需要配置members的tags)
    console.log('\n=== 自定义标签写关注 ===');
    // 需要先在副本集配置中添加tags
    // rs0:PRIMARY> conf = rs.conf()
    // conf.members[0].tags = { "dc": "beijing", "rack": "A" }
    // conf.members[1].tags = { "dc": "shanghai", "rack": "B" }
    // conf.members[2].tags = { "dc": "beijing", "rack": "C" }
    // rs.reconfig(conf)
    
    const result4 = await collection.insertOne(
        { orderId: 4, amount: 400, status: 'verified' },
        { 
            writeConcern: { 
                w: "majority",           // 多数派确认
                j: true,                 // 要求写入journal日志
                wtimeout: 10000          // 10秒超时
            } 
        }
    );
    console.log('插入成功,带日志确认');
    
    // 5. 使用自定义写关注标签组
    // 配置写关注规则
    // rs0:PRIMARY> db.adminCommand({
    //     setDefaultRWConcern: 1,
    //     defaultWriteConcern: { w: "majority", wtimeout: 5000 }
    // })
    
    await client.close();
}

demonstrateWriteConcern();

2.2 写关注性能对比

javascript 复制代码
// ==================== 写关注性能测试 ====================

const { MongoClient } = require('mongodb');

async function benchmarkWriteConcern() {
    const client = new MongoClient('mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0');
    await client.connect();
    
    const db = client.db('benchmark');
    const collection = db.collection('test');
    
    const testCases = [
        { name: 'w:1', concern: { w: 1 } },
        { name: 'w:2', concern: { w: 2 } },
        { name: 'w:majority', concern: { w: "majority" } },
        { name: 'w:3 with journal', concern: { w: 3, j: true } }
    ];
    
    for (const testCase of testCases) {
        console.log(`\n=== 测试: ${testCase.name} ===`);
        
        const startTime = Date.now();
        const iterations = 1000;
        
        for (let i = 0; i < iterations; i++) {
            await collection.insertOne(
                { 
                    index: i, 
                    timestamp: new Date(),
                    data: 'x'.repeat(100)  // 100字节数据
                },
                { writeConcern: testCase.concern }
            );
        }
        
        const endTime = Date.now();
        const duration = endTime - startTime;
        const opsPerSecond = (iterations / duration) * 1000;
        
        console.log(`总耗时: ${duration}ms`);
        console.log(`平均延迟: ${duration / iterations}ms`);
        console.log(`吞吐量: ${opsPerSecond.toFixed(2)} ops/sec`);
    }
    
    await client.close();
}

benchmarkWriteConcern();

三、自定义复制保证规则

3.1 配置自定义写关注

javascript 复制代码
// ==================== 配置自定义写关注 ====================

// 连接到主节点
mongo --port 27017

// 1. 查看当前副本集配置
rs.conf()

// 2. 为节点添加标签
config = rs.conf()
// 为成员添加标签
config.members[0].tags = { "dc": "beijing", "rack": "A", "priority": "high" }
config.members[1].tags = { "dc": "shanghai", "rack": "B", "priority": "medium" }
config.members[2].tags = { "dc": "beijing", "rack": "C", "priority": "low" }

// 3. 配置自定义写关注规则
config.settings = {
    getLastErrorModes: {
        // 至少有一个北京机房的节点确认
        "BeijingDC": { "dc": 1 },
        // 至少有两个不同机架的节点确认
        "TwoRacks": { "rack": 2 },
        // 需要北京和上海各一个节点确认
        "BothDCs": { "dc": 2 },
        // 需要高优先级节点确认
        "HighPriority": { "priority": 1 },
        // 组合条件:北京机房或高优先级节点
        "BeijingOrHigh": { 
            "dc": 1,  // 至少有一个北京节点
            "priority": 1  // 或者有高优先级节点
        }
    }
}

// 4. 应用新配置
rs.reconfig(config)

// 5. 验证配置
rs.conf().settings.getLastErrorModes

3.2 使用自定义写关注

javascript 复制代码
// ==================== 应用程序中使用自定义写关注 ====================

const { MongoClient } = require('mongodb');

async function useCustomWriteConcern() {
    const client = new MongoClient('mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0');
    await client.connect();
    
    const db = client.db('production');
    const orders = db.collection('orders');
    const transactions = db.collection('transactions');
    
    // 1. 使用预定义的自定义写关注
    console.log('=== 使用北京DC写关注 ===');
    const order1 = await orders.insertOne(
        {
            orderId: "ORD-001",
            amount: 1000,
            customer: "张三",
            region: "beijing"
        },
        {
            writeConcern: {
                w: "BeijingDC",  // 自定义写关注名称
                wtimeout: 5000
            }
        }
    );
    console.log('订单已保存,确保北京机房已确认');
    
    // 2. 组合使用多个标签
    console.log('\n=== 使用两个机架写关注 ===');
    const order2 = await orders.insertOne(
        {
            orderId: "ORD-002",
            amount: 2000,
            customer: "李四",
            region: "shanghai"
        },
        {
            writeConcern: {
                w: "TwoRacks",   // 至少两个不同机架确认
                wtimeout: 3000,
                j: true          // 同时要求journal确认
            }
        }
    );
    console.log('订单已保存,跨机架复制完成');
    
    // 3. 交易数据使用最高级别保证
    console.log('\n=== 使用最高级别保证 ===');
    const session = client.startSession();
    
    try {
        await session.withTransaction(async () => {
            // 使用最高的写关注级别
            const transactionResult = await transactions.insertOne(
                {
                    txId: "TX-001",
                    amount: 10000,
                    type: "transfer",
                    timestamp: new Date()
                },
                {
                    session,
                    writeConcern: {
                        w: "majority",     // 多数派确认
                        j: true,           // journal确认
                        wtimeout: 10000    // 10秒超时
                    }
                }
            );
            
            // 更新订单状态
            await orders.updateOne(
                { orderId: "ORD-001" },
                { $set: { status: "paid", txId: "TX-001" } },
                { session, writeConcern: { w: "majority" } }
            );
        });
        
        console.log('交易完成,数据已安全写入');
    } catch (error) {
        console.error('交易失败:', error);
    } finally {
        await session.endSession();
    }
    
    await client.close();
}

useCustomWriteConcern();

3.3 读关注(Read Concern)配置

javascript 复制代码
// ==================== 读关注级别配置 ====================

const { MongoClient } = require('mongodb');

async function demonstrateReadConcern() {
    const client = new MongoClient('mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0');
    await client.connect();
    
    const db = client.db('test');
    const collection = db.collection('data');
    
    // 1. local级别 - 读取本地可用数据(默认)
    console.log('=== 读关注: local ===');
    const localData = await collection.find({})
        .readConcern('local')
        .toArray();
    console.log(`读取到 ${localData.length} 条数据`);
    
    // 2. available级别 - 读取可用数据(分片集群使用)
    console.log('\n=== 读关注: available ===');
    const availableData = await collection.find({})
        .readConcern('available')
        .toArray();
    console.log(`读取到 ${availableData.length} 条数据`);
    
    // 3. majority级别 - 读取已被多数节点确认的数据
    console.log('\n=== 读关注: majority ===');
    const majorityData = await collection.find({})
        .readConcern('majority')
        .toArray();
    console.log(`读取到 ${majorityData.length} 条数据`);
    
    // 4. linearizable级别 - 线性一致性读
    console.log('\n=== 读关注: linearizable ===');
    // 需要在事务中使用
    const session = client.startSession();
    try {
        const linearizableData = await collection.find({})
            .readConcern('linearizable')
            .maxTimeMS(10000)
            .session(session)
            .toArray();
        console.log(`读取到 ${linearizableData.length} 条数据`);
    } finally {
        await session.endSession();
    }
    
    // 5. snapshot级别 - 快照读(事务中使用)
    console.log('\n=== 快照读示例 ===');
    const snapshotSession = client.startSession();
    try {
        await snapshotSession.withTransaction(async () => {
            const snapshotData = await collection.find({})
                .readConcern('snapshot')
                .session(snapshotSession)
                .toArray();
            console.log(`快照读取到 ${snapshotData.length} 条数据`);
        });
    } finally {
        await snapshotSession.endSession();
    }
    
    await client.close();
}

demonstrateReadConcern();

四、将读请求发送到从节点

4.1 读偏好(Read Preference)配置

javascript 复制代码
// ==================== 读偏好完整示例 ====================

const { MongoClient } = require('mongodb');

const uri = 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0';

async function demonstrateReadPreference() {
    const client = new MongoClient(uri, {
        useNewUrlParser: true,
        useUnifiedTopology: true
    });
    
    await client.connect();
    const db = client.db('test');
    const collection = db.collection('products');
    
    // 1. primary - 始终从主节点读取(默认)
    console.log('=== 读偏好: primary ===');
    const primaryData = await collection.find({})
        .readPref('primary')
        .limit(5)
        .toArray();
    console.log(`从主节点读取 ${primaryData.length} 条数据`);
    
    // 2. primaryPreferred - 优先主节点,主节点不可用时读从节点
    console.log('\n=== 读偏好: primaryPreferred ===');
    const primaryPreferred = await collection.find({})
        .readPref('primaryPreferred')
        .toArray();
    console.log(`优先主节点读取完成`);
    
    // 3. secondary - 始终从从节点读取
    console.log('\n=== 读偏好: secondary ===');
    const secondaryData = await collection.find({})
        .readPref('secondary')
        .limit(5)
        .toArray();
    console.log(`从从节点读取 ${secondaryData.length} 条数据`);
    
    // 4. secondaryPreferred - 优先从节点,从节点不可用时读主节点
    console.log('\n=== 读偏好: secondaryPreferred ===');
    const secondaryPreferred = await collection.find({})
        .readPref('secondaryPreferred')
        .toArray();
    console.log(`优先从节点读取完成`);
    
    // 5. nearest - 从网络延迟最低的节点读取
    console.log('\n=== 读偏好: nearest ===');
    const nearestData = await collection.find({})
        .readPref('nearest')
        .limit(5)
        .toArray();
    console.log(`从最近节点读取完成`);
    
    await client.close();
}

demonstrateReadPreference();

4.2 使用标签集(Tag Sets)选择特定从节点

javascript 复制代码
// ==================== 标签集读偏好配置 ====================

const { MongoClient } = require('mongodb');

async function demonstrateTagSetReadPreference() {
    const client = new MongoClient('mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0');
    await client.connect();
    
    const db = client.db('analytics');
    const reports = db.collection('reports');
    
    // 1. 读取特定数据中心的从节点
    console.log('=== 从北京数据中心读取 ===');
    const beijingData = await reports.find({ region: "north" })
        .readPref('secondary', [
            { 'dc': 'beijing' },      // 优先北京数据中心
            { }                        // 任意节点作为后备
        ])
        .toArray();
    console.log(`从北京数据中心读取 ${beijingData.length} 条报告`);
    
    // 2. 读取特定机架的节点
    console.log('\n=== 从A机架读取 ===');
    const rackAData = await reports.find({ type: "performance" })
        .readPref('secondary', [
            { 'rack': 'A' },           // 优先A机架
            { 'dc': 'beijing' },       // 其次北京数据中心
            { }                        // 任意节点
        ])
        .toArray();
    console.log(`从A机架读取 ${rackAData.length} 条数据`);
    
    // 3. 结合延迟和标签
    console.log('\n=== 从低延迟节点读取(标签过滤)===');
    const lowLatencyData = await reports.find({ priority: "high" })
        .readPref('nearest', [
            { 'tier': 'ssd' },         // 优先SSD节点
            { 'dc': 'beijing' }        // 其次北京数据中心
        ])
        .maxTimeMS(5000)
        .toArray();
    console.log(`从SSD节点读取完成`);
    
    // 4. 复杂业务场景:报表查询使用专用从节点
    console.log('\n=== 报表查询使用专用节点 ===');
    const monthlyReport = await reports.aggregate([
        { $match: { date: { $gte: new Date('2024-01-01') } } },
        { $group: { _id: "$category", total: { $sum: "$amount" } } },
        { $sort: { total: -1 } }
    ])
    .readPref('secondary', [
        { 'purpose': 'reporting' },    // 优先报表专用节点
        { 'dc': 'beijing' }            // 其次北京数据中心
    ])
    .toArray();
    
    console.log('报表查询结果:', monthlyReport);
    
    await client.close();
}

demonstrateTagSetReadPreference();

4.3 读写分离架构实现

javascript 复制代码
// ==================== 完整的读写分离架构示例 ====================

const { MongoClient } = require('mongodb');
const express = require('express');
const app = express();

// 配置两个客户端:一个用于写操作,一个用于读操作
class ReadWriteSeparator {
    constructor(uri, replicaSet) {
        // 写客户端 - 始终连接主节点
        this.writeClient = new MongoClient(uri, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
            readPreference: 'primary',  // 写操作使用主节点
            retryWrites: true
        });
        
        // 读客户端 - 优先从节点,实现读写分离
        this.readClient = new MongoClient(uri, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
            readPreference: 'secondaryPreferred',  // 读操作优先从节点
            readPreferenceTags: [
                { 'purpose': 'analytics' },  // 优先分析专用节点
                { 'dc': 'beijing' }          // 其次北京数据中心
            ],
            maxPoolSize: 100,  // 读操作连接池更大
            minPoolSize: 20
        });
    }
    
    async connect() {
        await Promise.all([
            this.writeClient.connect(),
            this.readClient.connect()
        ]);
        console.log('读写分离客户端已连接');
    }
    
    getWriteDB(dbName) {
        return this.writeClient.db(dbName);
    }
    
    getReadDB(dbName) {
        return this.readClient.db(dbName);
    }
    
    async close() {
        await Promise.all([
            this.writeClient.close(),
            this.readClient.close()
        ]);
    }
}

// 应用示例
async function runApplication() {
    const separator = new ReadWriteSeparator(
        'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0'
    );
    
    await separator.connect();
    
    // 1. 写入操作 - 使用主节点
    console.log('=== 写入操作(主节点)===');
    const writeDB = separator.getWriteDB('ecommerce');
    const orders = writeDB.collection('orders');
    
    const insertResult = await orders.insertOne({
        orderId: Date.now().toString(),
        userId: 1001,
        products: [
            { id: 1, name: '商品A', price: 99 },
            { id: 2, name: '商品B', price: 199 }
        ],
        totalAmount: 298,
        status: 'pending',
        createdAt: new Date()
    });
    console.log('订单创建成功,ID:', insertResult.insertedId);
    
    // 2. 读操作 - 优先从节点
    console.log('\n=== 读操作(从节点)===');
    const readDB = separator.getReadDB('ecommerce');
    const productStats = readDB.collection('product_stats');
    
    // 统计查询 - 消耗资源,适合从节点
    const stats = await productStats.aggregate([
        { $match: { date: { $gte: new Date('2024-01-01') } } },
        { $group: {
            _id: "$productId",
            totalSold: { $sum: "$quantity" },
            revenue: { $sum: { $multiply: ["$quantity", "$price"] } }
        }},
        { $sort: { revenue: -1 } },
        { $limit: 10 }
    ]).toArray();
    
    console.log('销售排行:', stats);
    
    // 3. 实时查询 - 使用读偏好实现一致性读
    console.log('\n=== 强一致性读(主节点)===');
    const realtimeDB = separator.getWriteDB('ecommerce');  // 使用写客户端实现强一致
    const latestOrder = await realtimeDB.collection('orders')
        .find({ userId: 1001 })
        .sort({ createdAt: -1 })
        .limit(1)
        .readPref('primary')  // 强制从主节点读取
        .toArray();
    
    console.log('最新订单:', latestOrder);
    
    // 4. 监控连接分布
    console.log('\n=== 连接状态监控 ===');
    const adminDB = separator.getWriteDB('admin');
    const serverStatus = await adminDB.command({ serverStatus: 1 });
    console.log('连接数统计:', serverStatus.connections);
    
    await separator.close();
}

// Express API 示例
app.use(express.json());

let separator;

app.post('/api/orders', async (req, res) => {
    // 写操作路由
    try {
        const db = separator.getWriteDB('ecommerce');
        const result = await db.collection('orders').insertOne(req.body);
        res.json({ success: true, id: result.insertedId });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.get('/api/reports/sales', async (req, res) => {
    // 读操作路由 - 从从节点读取报表
    try {
        const db = separator.getReadDB('ecommerce');
        const reports = await db.collection('sales_reports')
            .find({ date: { $gte: new Date(req.query.startDate) } })
            .readPref('secondary')  // 强制使用从节点
            .toArray();
        res.json(reports);
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

// 启动应用
async function startServer() {
    separator = new ReadWriteSeparator(
        'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0'
    );
    await separator.connect();
    
    app.listen(3000, () => {
        console.log('API服务器运行在 http://localhost:3000');
        console.log('读写分离已配置:');
        console.log('- 写操作:主节点');
        console.log('- 读操作:从节点(优先)');
    });
}

startServer();

4.4 读偏好最佳实践

javascript 复制代码
// ==================== 读偏好配置最佳实践 ====================

const { MongoClient } = require('mongodb');

class ReadPreferenceOptimizer {
    constructor(connectionString) {
        this.connectionString = connectionString;
    }
    
    // 根据不同业务场景返回不同的客户端配置
    getClientForScenario(scenario) {
        const baseOptions = {
            useNewUrlParser: true,
            useUnifiedTopology: true
        };
        
        switch(scenario) {
            case 'financial':  // 金融交易 - 强一致性
                return new MongoClient(this.connectionString, {
                    ...baseOptions,
                    readPreference: 'primary',  // 必须主节点
                    readConcern: 'majority',    // 多数派读
                    retryWrites: true
                });
                
            case 'realtime':  // 实时监控 - 低延迟
                return new MongoClient(this.connectionString, {
                    ...baseOptions,
                    readPreference: 'nearest',  // 最近节点
                    readConcern: 'local',       // 本地读
                    maxPoolSize: 50
                });
                
            case 'analytics':  // 数据分析 - 高吞吐
                return new MongoClient(this.connectionString, {
                    ...baseOptions,
                    readPreference: 'secondary',  // 从节点
                    readPreferenceTags: [{ 'purpose': 'analytics' }],
                    maxPoolSize: 200,  // 大连接池
                    socketTimeoutMS: 60000  // 长超时
                });
                
            case 'dashboard':  // 仪表盘 - 高可用
                return new MongoClient(this.connectionString, {
                    ...baseOptions,
                    readPreference: 'secondaryPreferred',  // 优先从节点
                    readConcern: 'available',  // 可用性优先
                    maxPoolSize: 100
                });
                
            default:  // 通用场景
                return new MongoClient(this.connectionString, {
                    ...baseOptions,
                    readPreference: 'primaryPreferred',
                    maxPoolSize: 50
                });
        }
    }
    
    // 监控读偏好分布
    async monitorReadDistribution(client) {
        const admin = client.db('admin');
        const serverStatus = await admin.command({ serverStatus: 1 });
        
        // 获取当前连接分布
        const connections = serverStatus.connections;
        console.log('连接统计:', connections);
        
        // 获取副本集状态
        const replSetStatus = await admin.command({ replSetGetStatus: 1 });
        const members = replSetStatus.members.map(m => ({
            name: m.name,
            state: m.stateStr,
            health: m.health,
            uptime: m.uptime
        }));
        
        return { connections, members };
    }
}

// 使用示例
async function optimizeReadPreference() {
    const optimizer = new ReadPreferenceOptimizer(
        'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0'
    );
    
    // 1. 金融业务 - 强一致性
    const financeClient = optimizer.getClientForScenario('financial');
    await financeClient.connect();
    
    console.log('=== 金融业务(强一致性)===');
    const financeDB = financeClient.db('finance');
    const transaction = await financeDB.collection('transactions')
        .find({ status: 'completed' })
        .readPref('primary')
        .readConcern('majority')
        .toArray();
    console.log(`读取 ${transaction.length} 条交易记录`);
    
    // 2. 分析业务 - 从节点读取
    const analyticsClient = optimizer.getClientForScenario('analytics');
    await analyticsClient.connect();
    
    console.log('\n=== 分析业务(从节点)===');
    const analyticsDB = analyticsClient.db('analytics');
    
    // 大数据量聚合查询
    const result = await analyticsDB.collection('user_behavior')
        .aggregate([
            { $match: { timestamp: { $gte: new Date('2024-01-01') } } },
            { $group: {
                _id: "$action",
                count: { $sum: 1 },
                uniqueUsers: { $addToSet: "$userId" }
            }},
            { $project: {
                action: "$_id",
                count: 1,
                uniqueUserCount: { $size: "$uniqueUsers" }
            }}
        ])
        .readPref('secondary')  // 强制从节点
        .allowDiskUse(true)      // 允许使用磁盘
        .toArray();
    
    console.log('用户行为统计:', result);
    
    // 3. 监控连接分布
    const monitorClient = optimizer.getClientForScenario('dashboard');
    await monitorClient.connect();
    
    console.log('\n=== 监控统计 ===');
    const stats = await optimizer.monitorReadDistribution(monitorClient);
    console.log('副本集成员状态:', stats.members);
    
    await Promise.all([
        financeClient.close(),
        analyticsClient.close(),
        monitorClient.close()
    ]);
}

optimizeReadPreference();

总结

关键知识点速查表

知识点 关键配置 使用场景
连接字符串 replicaSet=rs0 副本集连接必需参数
写关注 w: "majority" 数据持久性保证
读关注 readConcern: "majority" 数据一致性保证
读偏好 readPreference: "secondary" 读写分离、负载均衡
标签集 readPreferenceTags 精细化路由控制
自定义写关注 getLastErrorModes 业务定制化保证

最佳实践建议

  1. 生产环境连接字符串 :始终指定 replicaSet 参数和多个节点
  2. 写关注选择 :关键数据使用 "majority",非关键数据使用 w:1
  3. 读写分离 :读多写少场景使用 secondaryPreferred 读偏好
  4. 连接池配置 :根据并发量合理设置 maxPoolSize
  5. 监控:定期检查副本集状态和连接分布
  6. 标签设计:根据物理架构(机房、机架、硬件配置)设计标签
相关推荐
你才是臭弟弟2 小时前
MongoDB Community Server (社区版)安装流程
数据库·mongodb
X-⃢_⃢-X2 小时前
四、索引的创建与设计原则
数据库·mysql
你才是臭弟弟2 小时前
MongoDB介绍
数据库·mongodb
Armouy2 小时前
Nuxt.js 学习复盘:核心概念与实战要点
前端·javascript·学习
AI科技星2 小时前
万能学习方法论的理论建构与多领域适配性研究(乖乖数学)
人工智能·学习·算法·机器学习·平面·数据挖掘
ZhaoJuFei2 小时前
React生态学习路线
前端·学习·react.js
努力打怪升级2 小时前
使用 pymssql 连接数据库(GBK 编码)乱码问题的完美解决方案
数据库
却话巴山夜雨时i2 小时前
互联网大厂Java面试场景:从Spring到微服务的逐层提问
java·数据库·spring·微服务·日志·性能监控
寒秋花开曾相惜2 小时前
【软考中级系统集成项目管理】1.3 产业现代化(1.3.1 农业农村现代化)
笔记·学习