数据同步的幕后英雄:Otter Node的S.E.T.L流水线解析
【免费下载链接】otter 阿里巴巴分布式数据库同步系统(解决中美异地机房) 项目地址: https://gitcode.com/gh_mirrors/ot/otter
你是否曾为跨国数据同步的延迟问题头疼?当业务数据需要在中美两地机房实时流转时,如何确保数据一致性和传输效率?阿里巴巴开源的分布式数据库同步系统Otter(水獭)给出了答案。本文将带你深入Otter Node节点的内部机制,揭秘它如何像精密的流水线一样完成数据"搬运"工作。
读完本文你将了解:
- Otter Node的四大核心工作模块如何协作
- 数据从提取到加载的完整生命周期
- 节点故障时的自我保护机制
- 如何通过JMX监控同步性能
Node节点的架构概览
Otter系统采用分布式架构设计,其中Node节点作为数据同步的执行单元,承担着核心的数据处理工作。每个Node节点运行着一个完整的S.E.T.L流水线——Select(数据选取)、Extract(数据提取)、Transform(数据转换)和Load(数据加载)四个阶段,共同完成数据从源头到目标的同步过程。
核心控制中枢:OtterController.java负责管理整个流水线的生命周期,根据Manager推送的任务指令(NodeTask)启动或停止各个阶段的工作线程。它通过维护一个双层Map结构(controllers变量)来跟踪每个Pipeline的运行状态:
// 第一层为pipelineId,第二层为S.E.T.L模块
private Map<Long, Map<StageType, GlobalTask>> controllers = OtterMigrateMap.makeComputingMap(...);
Select阶段:数据选取的"侦察兵"
Select阶段是数据同步的起点,相当于整个流水线的"侦察兵",负责从源头数据库捕获变更数据。这一阶段的核心实现位于SelectTask.java。
工作原理
SelectTask通过OtterSelector从数据库日志中获取变更数据,采用类似Canal的增量订阅&消费模式。它维护两个关键线程:
- ProcessSelect:负责从源数据库拉取数据并放入处理队列
- ProcessTermin:监听数据处理结果,确保数据可靠传输
关键机制
- 数据可靠性保障:通过BatchTermin队列跟踪每个数据批次的处理状态,确保只有成功处理的数据才会被确认(ack),失败则回滚(rollback):
private void processTermin(boolean lastStatus, Long batchId, Long processId) {
TerminEventData terminData = arbitrateEventService.terminEvent().await(pipelineId);
if (terminData.getType().isNormal()) {
ack(batchId); // 成功处理,确认偏移量
} else {
rollback(batchId); // 处理失败,回滚偏移量
}
}
- 主备切换保护:通过ZooKeeper的MainStem机制实现节点的主备切换,确保同一时间只有一个Node节点在工作:
boolean working = arbitrateEventService.mainStemEvent().check(pipelineId);
if (!working) {
stopup(false); // 失去主节点资格,停止工作
}
Extract阶段:数据提取的"打包工"
Extract阶段相当于数据的"打包工",负责将Select阶段获取的原始数据进行初步处理和封装。核心实现位于ExtractTask.java。
主要职责
- 数据重新装配:通过OtterExtractorFactory对原始数据进行规范化处理:
otterExtractorFactory.extract(dbBatch); // 重新装配数据
- 文件冲突检测:对于文件同步场景,ExtractTask会调用FileBatchConflictDetectService检测文件是否发生变化,避免无效同步:
FileBatch fileBatch = fileBatchConflictDetectService.detect(dbBatch.getFileBatch(), nextNodeId);
Transform阶段:数据转换的"化妆师"
Transform阶段扮演着"化妆师"的角色,根据业务需求对数据进行格式转换和加工处理。核心实现位于TransformTask.java。
数据转换流程
TransformTask通过OtterTransformerFactory对数据进行转换处理,支持多种转换规则:
// 根据对应的tid,转化为目标端的tid
Map<Class, BatchObject> dataBatchs = otterTransformerFactory.transform(dbBatch.getRowBatch());
转换过程中支持两种类型的数据处理:
- RowBatch:数据库表数据变更记录
- FileBatch:文件变更记录
转换完成后,数据会被放入下一个阶段的管道(Pipe)中,等待Load阶段处理。
Load阶段:数据加载的"搬运工"
Load阶段是数据同步的最后一公里,作为数据的"搬运工"将转换后的数据加载到目标数据库。核心实现位于LoadTask.java。
加载过程
LoadTask通过OtterLoaderFactory完成数据的最终加载:
// 进行数据load处理
otterLoaderFactory.setStartTime(dbBatch.getRowBatch().getIdentity(), etlEventData.getStartTime());
processedContexts = otterLoaderFactory.load(dbBatch);
异常处理机制
当数据加载失败时,LoadTask会通过LoadInterceptor记录失败信息,以便后续进行数据修复:
if (processedContexts != null) { // 说明load成功了,但通知仲裁器失败了
for (LoadContext context : processedContexts) {
try {
if (context instanceof DbLoadContext) {
dbLoadInterceptor.error((DbLoadContext) context);
}
} catch (Throwable ie) {
logger.error("interceptor process error failed!", ie);
}
}
}
节点的自我保护机制
Otter Node内置了多种自我保护机制,确保在各种异常情况下的数据安全。
1. 分布式锁与主备切换
基于ZooKeeper实现的分布式锁机制,确保同一时刻只有一个Node节点处理特定Pipeline的数据:
// [OtterController.java] 初始化节点
private void initNid() {
String nid = System.getProperty(OtterConstants.NID_NAME);
if (StringUtils.isEmpty(nid)) {
throw new ConfigException("nid is not set!");
}
arbitrateManageService.nodeEvent().init(Long.valueOf(nid));
}
2. 数据一致性保障
通过版本号(rversion变量)和状态标记(canStartSelector)确保数据处理的原子性,避免部分失败导致的数据不一致:
private void notifyRollback() {
canStartSelector.set(false);
rversion.incrementAndGet(); // 变更版本号,标记发生回滚
}
3. 资源隔离与清理
每个Pipeline拥有独立的数据库连接池和资源管理,确保一个Pipeline的故障不会影响其他Pipeline:
// [OtterController.java] 释放资源
private void releasePipeline(Long pipelineId) {
dataSourceService.destroy(pipelineId); // 销毁数据源
dbDialectFactory.destory(pipelineId); // 销毁数据库方言信息
}
性能监控与调优
Otter Node提供了丰富的监控指标和调优接口,通过JMX暴露给外部系统。
关键监控指标
OtterControllerMBean定义了多种监控方法:
- 各阶段运行状态:
isSelectRunning(pipelineId)、isExtractRunning(pipelineId)等 - 线程池状态:
getThreadActiveSize()、getThreadPoolSize() - 性能统计:
selectStageAggregation(pipelineId)返回Select阶段的处理延迟分布
性能调优
通过setThreadPoolSize(int size)方法可以动态调整线程池大小,适应不同的负载情况:
public void setThreadPoolSize(int size) {
if (executorService instanceof ThreadPoolExecutor) {
ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
pool.setCorePoolSize(size);
pool.setMaximumPoolSize(size);
}
}
总结与最佳实践
Otter Node通过S.E.T.L流水线的精巧设计,实现了高效、可靠的分布式数据同步。每个阶段专注于特定职责,通过松耦合的方式协同工作,既保证了数据一致性,又提供了良好的可扩展性。
最佳实践:
- 监控优先:定期检查各阶段的延迟指标,通过
selectStageAggregation等方法及时发现性能瓶颈 - 合理配置:根据数据量大小调整线程池参数,避免过度并行导致的资源竞争
- 故障演练:定期进行主备切换测试,确保故障转移机制可靠工作
Otter的源码中还有更多值得探索的技术细节,例如arbitrate模块的分布式协调机制、etl模块的数据模型定义等。希望本文能为你深入理解Otter的内部工作原理提供一个良好的起点。
项目完整代码:gh_mirrors/ot/otter 官方文档:README.md
【免费下载链接】otter 阿里巴巴分布式数据库同步系统(解决中美异地机房) 项目地址: https://gitcode.com/gh_mirrors/ot/otter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



