OtterController “otter node”的总入口,管理了setl的所有调度,包含启动,停止setl。
OtterController 通过实现接口NodeTaskListener 来支持manager对node的调度,可以随时停止一个pipeline, 启动一个pipeline 的S E T L中任何一个过程。注意,一个node可以包含S E T L中的任何一个组合。
public boolean process(List<NodeTask> nodeTasks) {
if (nodeTasks == null || nodeTasks.isEmpty()) {
return true;
}
for (NodeTask nodeTask : nodeTasks) {
boolean shutdown = nodeTask.isShutdown();
Long pipelineId = nodeTask.getPipeline().getId();
if (shutdown) {
//停止一个pipeline
Map<StageType, GlobalTask> tasks = controllers.remove(pipelineId);
if (tasks != null) {
logger.info - 最佳的logger 来源和相关信息。("INFO ## shutdown this pipeline sync ,the pipelineId = {} and tasks = {}", pipelineId,
tasks.keySet());
stopPipeline(pipelineId, tasks);
} else {
logger.info - 最佳的logger 来源和相关信息。("INFO ## this pipeline id = {} is not start sync", pipelineId);
}
} else {
//启动或者停止pipeline中的任何S E T L . 根据TaskEvent判断是停止还是启动。 根据StageType阶段类型判断是操作S E T L中的哪一个阶段
startPipeline(nodeTask);
}
}
return true;
}
if (taskType.isSelect()) {
task = new SelectTask(pipeline.getId());
} else if (taskType.isExtract()) {
task = new ExtractTask(pipeline.getId());
} else if (taskType.isTransform()) {
task = new TransformTask(pipeline.getId());
} else if (taskType.isLoad()) {
task = new LoadTask(pipeline.getId());
}
通过任务类型去启动任务,每一个XXXTask都是GlobalTask的子类,GlobalTask继承与Thread。故每一个task都代表一个线程。
下面以SelectTask为列子讲解:
select流处理模式的实现版本
调度模型:
1. 正常运行调度流程
假如并行度为3
----------------------------------------------------------->时间轴
| ProcessSelect
--> 1
--> 2
-->3
--> 1
| ProcessTermin
---> 1 ack
----> 2ack
---> 3ack
a. ProcessSelect拿到数据后,丢入pool池进行异步处理,并通知ProcessTermin顺序接收termin信号
b. 同一时间在s/e/t/l流水线上的数据受并行度控制,满了就会阻塞ProcessSelect,避免取过多的数据,只会多取一份,等待其中一个s/e/t/l完成
c. ProcessTermin接受到termin信号
i. 会严格按照发出去的batchId/processId进行对比,发现不匹配,发起rollback操作.
ii. 会根据terminType判断这一批数据是否处理成功,如果发现不成功,发起rollback操作
2. 异常调度流程
假如并行度为3
|-->1 --> 2 -->3(ing)
a. 当第1份数据,ProcessTermin发现需要rollback,此时需要回滚2,3份数据的批次. (可能第2,3份数据还未提交到s/e/t/l调度中)
i. 如果2批次数据已经提交,等待2批次termin信号的返回,此时需要阻塞ProcessSelect,避免再取新数据
ii. 如果第2批次数据未提交,直接rollback数据,不再进入s/e/t/l调度流程
b. 当所有批次都已经处理完成,再通知ProcessSelect启动 (注意:这里会避免rollback和get并发操作,会造成数据不一致)
3. 热备机制
a. Select主线程会一直监听mainstem的信号,一旦抢占成功,则启动ProcessSelect/ProcessTermin线程
b. ProcessSelect/ProcessTermin在处理过程中,会检查一下当前节点是否为抢占mainstem成功的节点,如果发现不是,立马停止,继续监听mainstem
c. ProcessSelect进行get数据之前,会等到ProcessTermin会读取未被处理过termin信号,对上一次的selector进行ack/rollback处理
i. 注意:ProcessSelect进行get数据时,需要保证batch/termin/get操作状态保持一致,必须都处于同一个数据点上
selecttask: S阶段就是数据获取阶段封装操作,但是没有进行数据转换,是最原始的数据。封装成为EtlEventData到达下一步 E阶段
ExtractTask: E阶段 otterExtractorFactory.extract(dbBatch);// 重新装配一下数据 将数据进行转换 包含很多的转换策略,可以通过代码上传的是EventProcessor 进行自定义处理
TransformTask: T阶段 数据转换阶段,将源库映射到目标库的阶段。
// 根据对应的tid,转化为目标端的tid。后续可进行字段的加工处理
// 暂时认为rowBatchs和fileBatchs不会有异构数据的转化
Map<Class, BatchObject> dataBatchs = otterTransformerFactory.transform(dbBatch.getRowBatch()); 具体转换在RowDataTransformer RowData -> RowData数据的转换;
可能存在一个表对应几个表的映射,这都支持。
LoadTask: L阶段 就是数据持久化阶段,此阶段会进行合并sql,提供sql增删改的性能。 processedContexts = otterLoaderFactory.load(dbBatch);
/**
* pipeline相关的参数类
*
* @author jianghang 2011-9-2 上午10:42:27
*/
public class PipelineParameter implements Serializable {
private static final long serialVersionUID = 8112362911827913152L;
private Long pipelineId;
private Long parallelism = 3L; // 并行度
private LoadBanlanceAlgorithm lbAlgorithm = LoadBanlanceAlgorithm.Random; // 负载均衡算法
private Boolean home = false; // 是否为主站点
private SelectorMode selectorMode = SelectorMode.Canal; // 数据获取方式
private String destinationName;
private Short mainstemClientId; // mainstem订阅id
private Integer mainstemBatchsize = 10000 * 10; // mainstem订阅批次大小
private Integer extractPoolSize = 5; // extract模块载入线程数,针对单个载入通道
private Integer loadPoolSize = 5; // load模块载入线程数,针对单个载入通道
private Integer fileLoadPoolSize = 5; // 文件同步线程数
private Boolean dumpEvent = true; // 是否需要dumpevent对象
private Boolean dumpSelector = true; // 是否需要dumpSelector信息
private Boolean dumpSelectorDetail = true; // 是否需要dumpSelector的详细信息
private PipeChooseMode pipeChooseType = PipeChooseMode.AUTOMATIC; // pipe传输模式
private Boolean useBatch = true; // 是否使用batch模式
private Boolean skipSelectException = false; // 是否跳过select时的执行异常
private Boolean skipLoadException = false; // 是否跳过load时的执行异常
private ArbitrateMode arbitrateMode = ArbitrateMode.ZOOKEEPER; // 调度模式,默认进行自动选择
private Long batchTimeout = -1L; // 获取批量数据的超时时间,-1代表不进行超时控制,0代表永久,>0则表示按照指定的时间进行控制(单位毫秒)
private Boolean fileDetect = false; // 是否开启文件同步检测
private Boolean skipFreedom = false; // 是否跳过自由门数据
private Boolean useLocalFileMutliThread = false; // 是否启用对local
// file同步启用多线程
private Boolean useFileEncrypt = false; // 是否针对文件进行加密处理
private Boolean useExternalIp = false; // 是否起用外部Ip
private Boolean useTableTransform = false; // 是否启用转化机制,比如类型不同,默认为true,兼容老逻辑
private Boolean enableCompatibleMissColumn = true; // 是否启用兼容字段不匹配处理
private Boolean skipNoRow = false; // 跳过反查没记录的情况
private String channelInfo; // 同步标记,设置该标记后会在retl_mark中记录,在messageParse时进行check,相同则忽略
private Boolean dryRun = false; // 是否启用dry
// run模型,只记录load日志,不同步数据
private Boolean ddlSync = true; // 是否支持ddl同步
private Boolean skipDdlException = false; // 是否跳过ddl执行异常
// ================================= channel parameter
// ================================
@Transient
private Boolean enableRemedy; // 是否启用冲突补救算法
@Transient
private RemedyAlgorithm remedyAlgorithm; // 冲突补救算法
@Transient
private Integer remedyDelayThresoldForMedia; // 针对回环补救,如果反查速度过快,容易查到旧版本的数据记录,导致中美不一致,所以设置一个阀值,低于这个阀值的延迟不进行反查
@Transient
private SyncMode syncMode; // 同步模式:字段/整条记录
@Transient
private SyncConsistency syncConsistency; // 同步一致性要求
// ================================= system parameter
// ================================
@Transient
private String systemSchema; // 默认为retl,不允许为空
@Transient
private String systemMarkTable; // 双向同步标记表
@Transient
private String systemMarkTableColumn; // 双向同步标记的列名
@Transient
private String systemMarkTableInfo; // 双向同步标记的info信息,比如类似BI_SYNC
@Transient
private String systemBufferTable; // otter同步buffer表
@Transient
private String systemDualTable; // otter同步心跳表
@Transient
private RetrieverType retriever; // 下载方式
整个S E T L过程中,数据被封装为EtlEventData进行阶段间的传输。
数据转换加工处理的扩展点放到了接口EventProcess这个接口下,我们可以如下实现来满足我们将密码进行脱敏存储
package com.alibaba.otter.node.extend.processor;
import com.alibaba.otter.shared.etl.model.EventColumn;
import com.alibaba.otter.shared.etl.model.EventData;
public class PdyEventPro extends AbstractEventProcessor {
@Override
public boolean process(EventData eventData) {
EventColumn eventColumn = getColumn(eventData, "password_");
eventColumn.setColumnValue("**"); //敏感数据脱敏
return super.process(eventData);
}
}
直接将密码进行存储为"**"
otter配置演示视频_土豆视频 快速使用manager的视频教程
a. 单向同步
单向同步为最基本的同步方式,目前支持mysql -> mysql/oracle的同步.
基本配置方式就如操作视频中所演示的,操作步骤:
配置一个channel
配置一个pipeline
对应node机器选择时,建议遵循:S/E节点选择node需尽可能离源数据库近,T/L节点选择node则离目标数据库近. 如果无法提供双节点,则选择离目标数据库近的node节点相对合适.
配置一个canal
定义映射关系.
canal中配置解析的数据库ip需要和映射关系中源表对应的数据库ip一致. ps. 映射关系进行匹配的时候是基于表名,虽然数据库ip不匹配也会是有效.
FreedomExtractor 源码解读的理解
/**
* 自由之门,允许手工触发数据订正,解析这些记录
* pdy:在不变更原始数据的情况下手动的触发一次数据同步。
* 比如: 用户表(gxqpt_core_test.c_user)中存在一条id为1的记录需要进行一次手动的触发同步。
* 则只需要在buffer中加入一条记录为: id: 无意义的,可以随便处理,
* table_id: 若和otter中定义的tableid有所对接,使用这个很好,但是没有对接,可以留空
* full_name:与table_id任选则一个,若两者都有,则以tableid为主。格式为: schema.table 比如就填写gxqpt_core_test.c_user
* type: 希望进行一次什么同步,可选为: insert update delete
* 经过上面的操作,就会促发一次数据同步,这个就是手动触发同步的功能。
* <pre>
* buffer表结构:
* id , table_id , type , pk_data , gmt_create , gmt_modified , full_name
*
* 一张表存在多主键的解决方案: pk_data针对多主键时,使用char(1)进行分隔
* </pre>
*
* @author jianghang 2012-4-25 下午04:41:33
* @version 4.0.2
*/
public class FreedomExtractor extends AbstractExtractor<DbBatch>
GroupExtractor 源码解读的理解:
/**
* 变更的主键+变更的字段 会和group进行交集处理,发现有交集后会确保当前group的所有字段都可以得到同步
* pdy: 举例,我们希望用户表中的user_id,name,age,sex;作为一个原子进行同步,若数据库中仅仅改变了name,我们也要将user_id,age,sex三个与name一起同步,
* 保证任意时刻不管任何问题,他们一定是一致的。保证强一致性。但是一般这种需求是用不到的,而且会反查数据库,所以尽量不要使用。影响性能
* @author simon 2012-4-26 下午5:04:51
*/
public class GroupExtractor extends AbstractExtractor<DbBatch>
DatabaseExtractor 源码解读的理解:
/**
* 基于数据库的反查 , 使用多线程技术进行加速处理 {@linkplain DatabaseExtractWorker}
*
* <pre>
* 说明:
* 1. 数据反查的总时间 = ( 数据记录数 / (poolsize + 1) ) * 每条记录查询的时间
* 2. 当其中的一次并行查询出现异常,会立即中断之前的并行查询请求,同时忽略后续的查询直接退出(在出错时快速响应)
* 3. 编写{@linkplain DatabaseExtractWorker}代码时需注意,在适合的地方响应Thread.currentThread().isInterrupted(),在dbcp连接池和driver代码中是有支持
* 4. 反查数据库,只会反查update=true的字段,按需反查,因为通过反查之后字段都会变为update=true,不必要的字段会进行数据同步 (modify by ljh at 2012-11-04)
* </pre>
*
* {@link GroupExtractor} 与 {@link FreedomExtractor} 两个类真正的执行者,所以在配置中otterExtractorFactory的list配置顺序是此类在其两个类之后,前面两个类指定
* 哪些数据需要执行反查操作,真正的反查是由此类进行的。若想要其他的Extractor也有控制反查的功能,可以将其放在此类的前面即可。
* 自由门 {@link FreedomExtractor}反查的是整个行数据。而组提取{@link GroupExtractor}反查的是一组数据而非整行数据
* 自由门要求此类反查整行的方式为:
* eventData.setSyncMode(SyncMode.ROW); //指定需要所有数据
* eventData.setSyncConsistency(SyncConsistency.MEDIA);
* eventData.setRemedy(true);
* 而组提取器的方式为:
* eventColumn.setUpdate(true); //指定此列数据需要反查
* eventData.setSyncConsistency(SyncConsistency.MEDIA); 基于当前介质最新数据 要求去数据库最新记录 必须进行反查
*而且仅仅update 与insert才会进行反查
* @author jianghang 2012-4-18 下午04:53:15
* @version 4.0.2
* pdy: 此类是线程不安全的,需要在spring中是property。但是可以作为池化处理,就是做成对象池。
*/
public class DatabaseExtractor extends AbstractExtractor<DbBatch> implements InitializingBean, DisposableBean
ViewExtractor 源码解读的理解:
/**
* 数据字段的排除,我们在配置源表到目标表映射关系的时候,可以选择性的去配置一些字段同步,而一些字段不需要同步。
* 总共有两种模式: {@link ColumnPairMode#INCLUDE} 包含 {@link ColumnPairMode#EXCLUDE} 排除配置。
* 1、不管使用哪一种模式,只要不配做任何的列,都将要求全列都被同步。就是不配置表示不作过滤。
* 2、EXCLUDE 表示排除那些字段。
* 3、INCLUDE 表示只要那些字段
* @author simon 2012-4-26 下午5:04:51
*/
public class ViewExtractor extends AbstractExtractor<DbBatch>
ProcessorExtractor 是为外界提供的扩展功能,可以通过实现EventProcessor进行数据的进一步处理。
/**
* 调用{@linkplain EventProcessor},进行业务数据处理
*
* @author jianghang 2012-7-23 下午03:11:19
*/
public class ProcessorExtractor extends AbstractExtractor<DbBatch>