从应用程序连接副本集
一、客户端到副本集的连接行为
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 |
业务定制化保证 |
最佳实践建议
- 生产环境连接字符串 :始终指定
replicaSet 参数和多个节点
- 写关注选择 :关键数据使用
"majority",非关键数据使用 w:1
- 读写分离 :读多写少场景使用
secondaryPreferred 读偏好
- 连接池配置 :根据并发量合理设置
maxPoolSize
- 监控:定期检查副本集状态和连接分布
- 标签设计:根据物理架构(机房、机架、硬件配置)设计标签