otter源码理解 S E T L

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>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值