MongoDB CDC(Change Data Capture)的原理主要基于以下几种技术:
1. 基于变更流的CDC(最常用)
变更流简介
- MongoDB 3.6+ 引入的实时变更通知功能
- 基于oplog的抽象层,提供更友好的API接口
- 支持聚合管道对变更数据进行过滤和转换
工作原理
数据库操作 → Oplog → 变更流 → 应用程序 → 下游系统
具体流程:
- 操作执行:CRUD操作被记录到oplog
- 变更流监听:应用程序订阅变更流
- 事件触发:实时接收插入、更新、删除等事件
- 数据处理:对变更事件进行过滤、转换
- 下游同步:将变更发送到目标系统
2. 基于Oplog尾随的CDC(传统方式)
Oplog简介
- MongoDB的操作日志,用于复制集数据同步
- 固定大小的集合,存储所有数据修改操作
- 位于local数据库的oplog.rs集合
工作原理
数据库操作 → Oplog → Tailable Cursor → CDC工具 → 下游系统
MongoDB变更流核心机制
变更流事件类型
javascript
// 主要事件类型:
{
"insert": {"_id": ObjectId("..."), "fullDocument": {...}},
"update": {
"_id": ObjectId("..."),
"updateDescription": {"updatedFields": {...}, "removedFields": [...]}
},
"delete": {"_id": ObjectId("...")},
"replace": {...},
"invalidate": {...}
}
变更流配置选项
javascript
// 变更流配置参数
const pipeline = [
{$match: {'fullDocument.status': 'active'}},
{$project: {_id: 1, fullDocument: 1}}
];
const options = {
fullDocument: 'updateLookup', // 获取更新后的完整文档
resumeAfter: resumeToken, // 断点续传
batchSize: 100, // 批次大小
maxAwaitTimeMs: 1000 // 最大等待时间
};
关键技术点
1. Oplog结构
javascript
// Oplog条目示例
{
"ts": Timestamp(1638954180, 1), // 时间戳
"t": NumberLong(1), // 任期号
"h": NumberLong("123456789"), // 哈希值
"v": 2, // 版本
"op": "i", // 操作类型: i=插入, u=更新, d=删除
"ns": "test.users", // 命名空间
"o": {"_id": ObjectId("..."), "name": "Alice"}, // 操作文档
"o2": {"_id": ObjectId("...")} // 更新时的查询条件
}
2. 恢复令牌管理
javascript
// 恢复令牌用于断点续传
const changeStream = db.collection('users').watch([], options);
// 获取当前恢复令牌
const resumeToken = changeStream.resumeToken;
// 从特定令牌恢复
const newChangeStream = db.collection('users').watch([], {
resumeAfter: resumeToken
});
3. 聚合管道过滤
javascript
// 使用聚合管道过滤变更
const pipeline = [
{
$match: {
$or: [
{'fullDocument.status': 'active'},
{'operationType': 'delete'}
]
}
},
{
$project: {
'documentKey': 1,
'fullDocument.name': 1,
'operationType': 1
}
}
];
const changeStream = db.collection('users').watch(pipeline);
配置和使用
启用变更流的前提条件
javascript
// 1. MongoDB版本要求:3.6+
// 2. 复制集或分片集群(单机模式不支持)
// 3. 启用副本集
// 4. 适当的权限配置
// 检查副本集状态
rs.status()
// 创建具有变更流权限的用户
db.createUser({
user: "cdc_user",
pwd: "password",
roles: [
{role: "read", db: "test"},
{role: "read", db: "local"}
]
})
基本变更流使用
javascript
// 监听整个数据库的变更
const changeStream = db.watch();
// 监听特定集合的变更
const changeStream = db.collection('orders').watch();
// 处理变更事件
changeStream.on('change', (change) => {
console.log('Received change:', JSON.stringify(change, null, 2));
// 根据操作类型处理
switch(change.operationType) {
case 'insert':
handleInsert(change.fullDocument);
break;
case 'update':
handleUpdate(change.documentKey, change.updateDescription);
break;
case 'delete':
handleDelete(change.documentKey);
break;
}
});
主流CDC工具实现方式
Debezium MongoDB Connector
java
// 工作流程
1. 连接MongoDB副本集或分片集群
2. 初始快照(可选)
3. 开启变更流监听
4. 解析变更事件
5. 转换为CDC事件格式
6. 发送到Kafka Connect
MongoKafka
javascript
// 基于变更流的Kafka连接器
const {MongoClient} = require('mongodb');
const {Kafka} = require('kafkajs');
async function startCDC() {
const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const collection = client.db('test').collection('users');
const changeStream = collection.watch();
const kafka = new Kafka({brokers: ['localhost:9092']});
const producer = kafka.producer();
for await (const change of changeStream) {
await producer.send({
topic: 'mongo-changes',
messages: [{value: JSON.stringify(change)}]
});
}
}
具体实现示例
完整的变更流CDC实现
javascript
class MongoDBCDC {
constructor(connectionString, dbName, collectionName) {
this.connectionString = connectionString;
this.dbName = dbName;
this.collectionName = collectionName;
this.resumeToken = null;
}
async start() {
this.client = new MongoClient(this.connectionString);
await this.client.connect();
const database = this.client.db(this.dbName);
this.collection = database.collection(this.collectionName);
// 从恢复令牌开始(如果存在)
const options = {};
if (this.resumeToken) {
options.resumeAfter = this.resumeToken;
}
// 配置变更流
this.changeStream = this.collection.watch([], {
...options,
fullDocument: 'updateLookup',
batchSize: 100
});
this.processChanges();
}
async processChanges() {
try {
while (await this.changeStream.hasNext()) {
const change = this.changeStream.next();
await this.handleChange(change);
// 保存恢复令牌用于断点续传
this.resumeToken = this.changeStream.resumeToken;
this.saveResumeToken(this.resumeToken);
}
} catch (error) {
console.error('Change stream error:', error);
await this.restart();
}
}
async handleChange(change) {
const event = {
operation: change.operationType,
documentId: change.documentKey._id,
timestamp: change.clusterTime,
data: change.fullDocument || change.updateDescription
};
// 发送到下游系统
await this.sendToDownstream(event);
}
}
分片集群的变更流处理
javascript
// 分片集群需要监听所有mongos节点或直接连接分片
const mongosConnections = [
'mongodb://mongos1:27017',
'mongodb://mongos2:27017',
'mongodb://mongos3:27017'
];
// 或者通过mongos连接
const client = new MongoClient('mongodb://mongos1:27017,mongos2:27017,mongos3:27017');
性能优化策略
1. 变更流配置优化
javascript
const options = {
batchSize: 500, // 增加批次大小
maxAwaitTimeMs: 5000, // 增加等待时间
fullDocument: 'whenAvailable' // 按需获取完整文档
};
2. 聚合管道优化
javascript
// 在数据库层面过滤,减少网络传输
const pipeline = [
{$match: {
operationType: {$in: ['insert', 'update']},
'fullDocument.important': true
}},
{$project: {
'fullDocument._id': 1,
'fullDocument.name': 1,
'operationType': 1,
'clusterTime': 1
}}
];
3. 错误处理和重试
javascript
class ChangeStreamProcessor {
async startWithRetry(maxRetries = 5) {
let retries = 0;
while (retries < maxRetries) {
try {
await this.start();
break;
} catch (error) {
retries++;
console.error(`Attempt ${retries} failed:`, error);
if (retries === maxRetries) throw error;
await this.sleep(Math.pow(2, retries) * 1000); // 指数退避
}
}
}
}
监控与管理
变更流监控
javascript
// 监控变更流状态
db.currentOp({'ns': 'local.oplog.rs'})
// 检查oplog大小和使用情况
db.getReplicationInfo()
// 监控连接状态
db.serverStatus().connections
关键指标监控
javascript
// 自定义监控指标
const metrics = {
eventsProcessed: 0,
lastProcessedTime: null,
resumeToken: null,
errors: 0
};
// 定期报告状态
setInterval(() => {
console.log('CDC Status:', {
eventsProcessed: metrics.eventsProcessed,
lastProcessed: metrics.lastProcessedTime,
uptime: process.uptime()
});
}, 60000);
典型应用场景
- 实时数据同步:MongoDB到其他数据库的实时同步
- 搜索索引更新:同步到Elasticsearch等搜索引擎
- 缓存更新:自动刷新Redis缓存
- 微服务集成:数据库变更作为事件源
- 数据仓库ETL:实时数据入仓
- 审计日志:数据变更审计和合规性
挑战与解决方案
1. Oplog大小限制
- 挑战:oplog是固定大小集合,旧数据会被覆盖
- 解决方案:监控oplog使用情况,及时调整大小,确保CDC跟上进度
2. 分片集群复杂性
- 挑战:分片集群需要处理多个oplog流
- 解决方案:通过mongos统一访问,或分别处理每个分片
3. 网络分区处理
- 挑战:网络问题可能导致变更流中断
- 解决方案:实现健壮的重连机制,使用恢复令牌
4. 大文档处理
- 挑战:大文档可能影响变更流性能
- 解决方案:只投影需要的字段,避免传输不必要的数据
MongoDB基于变更流的CDC方案提供了实时、可靠的数据变更捕获能力,相比传统的oplog尾随更加易用和强大,适合现代的数据集成需求。