记一次datax全流程debug
源码入口
//Engine.java
Engine.entry(datxArgs);
-ConfigParser.parse(jobPath);//解析配置
-//打印vmInfo,通过java.lang.management.ManagementFactory
VMInfo vmInfo = VMInfo.getVmInfo();
if (vmInfo != null) {
LOG.info(vmInfo.toString());
}
-ConfigurationValidate.doValidate(configuration);//配置校验
-engine.start(configuration);//启动
//根据参数core.container.model初始化JobContainer(默认)/TaskGroupContainer
boolean isJob = !("taskGroup".equalsIgnoreCase(allConf.getString(CoreConstant.DATAX_CORE_CONTAINER_MODEL)));
if (isJob) {
allConf.set(CoreConstant.DATAX_CORE_CONTAINER_JOB_MODE, RUNTIME_MODE);
container = new JobContainer(allConf);
instanceId = allConf.getLong(
CoreConstant.DATAX_CORE_CONTAINER_JOB_ID, 0);
} else {
container = new TaskGroupContainer(allConf);
instanceId = allConf.getLong(
CoreConstant.DATAX_CORE_CONTAINER_JOB_ID);
taskGroupId = allConf.getInt(
CoreConstant.DATAX_CORE_CONTAINER_TASKGROUP_ID);
channelNumber = allConf.getInt(
CoreConstant.DATAX_CORE_CONTAINER_TASKGROUP_CHANNEL);
}
container.start();//JobContainer启动
//根据参数job.setting.dryRun进行空跑,且对读写等容器初始化,相当于预检查
if(isDryRun) {
this.preCheck();
} else {
//预处理操作。
this.preHandle();
//对任务进行初始化操作。这可能包括加载配置文件、连接数据库、初始化数据源等
this.init();
//准备执行任务所需的数据和资源。
this.prepare();
//将任务拆分成多个阶段(或者称为分片),以便并行执行。这个阶段可能会根据数据量、任务类型等因素将任务拆分成多个子任务
this.totalStage = this.split();
//调度并行执行任务的各个阶段或分片。
this.schedule();
//在任务执行完成后进行一些后处理操作。
this.post();
//: 在后处理操作完成后进行一些收尾工作,确保任务执行的完整性和正确性。
this.postHandle();
//用任务执行过程中注册的钩子函数,执行一些额外的逻辑。
this.invokeHooks();
}
this.init();
//初始化jobReader,jobWriter
this.jobReader = this.initJobReader(jobPluginCollector);
this.jobWriter = this.initJobWriter(jobPluginCollector);
//为了防止插件存在多个版本的读写依赖jar包,在获取对应的jar时,需要将当前线程的类加载器切换为该jar的类加载器,防止类加载器发生冲突
private Reader.Job initJobReader(JobPluginCollector jobPluginCollector) {
// 从配置中获取数据读取器插件的名称
this.readerPluginName = this.configuration.getString(CoreConstant.DATAX_JOB_CONTENT_READER_NAME);
// 通过插件类型和插件名称获取相应的 JarLoader 实例,并将当前线程的类加载器切换为该 JarLoader
classLoaderSwapper.setCurrentThreadClassLoader(LoadUtil.getJarLoader(PluginType.READER, this.readerPluginName));
// 加载并实例化数据读取器插件
Reader.Job jobReader = (Reader.Job) LoadUtil.loadJobPlugin(PluginType.READER, this.readerPluginName);
// 设置读取器的任务配置
jobReader.setPluginJobConf(this.configuration.getConfiguration(CoreConstant.DATAX_JOB_CONTENT_READER_PARAMETER));
// 设置读取器的读取器配置
jobReader.setPeerPluginJobConf(this.configuration.getConfiguration(CoreConstant.DATAX_JOB_CONTENT_WRITER_PARAMETER));
// 设置读取器的任务插件收集器
jobReader.setJobPluginCollector(jobPluginCollector);
// 初始化读取器
jobReader.init();
// 恢复当前线程的类加载器为原始类加载器
classLoaderSwapper.restoreCurrentThreadClassLoader();
// 返回初始化完成的数据读取器
return jobReader;
}
this.prepare();
//设置当前类加载器为目标jar的类加载器,并且根据配置做初始化,执行预处理sql
this.prepareJobReader();
this.prepareJobWriter();
//例子
//job
//this.commonRdbmsWriterTask.prepare(this.writerSliceConfig);
public void prepare(Configuration originalConfig) {
int tableNumber = originalConfig.getInt(Constant.TABLE_NUMBER_MARK);
if (tableNumber == 1) {
String username = originalConfig.getString(Key.USERNAME);
String password = originalConfig.getString(Key.PASSWORD);
List<Object> conns = originalConfig.getList(Constant.CONN_MARK,
Object.class);
Configuration connConf = Configuration.from(conns.get(0)
.toString());
// 这里的 jdbcUrl 已经 append 了合适后缀参数
String jdbcUrl = connConf.getString(Key.JDBC_URL);
originalConfig.set(Key.JDBC_URL, jdbcUrl);
String table = connConf.getList(Key.TABLE, String.class).get(0);
originalConfig.set(Key.TABLE, table);
List<String> preSqls = originalConfig.getList(Key.PRE_SQL,
String.class);
List<String> renderedPreSqls = WriterUtil.renderPreOrPostSqls(
preSqls, table);
originalConfig.remove(Constant.CONN_MARK);
if (null != renderedPreSqls && !renderedPreSqls.isEmpty()) {
// 说明有 preSql 配置,则此处删除掉
originalConfig.remove(Key.PRE_SQL);
Connection conn = DBUtil.getConnection(dataBaseType,
jdbcUrl, username, password);
LOG.info("Begin to execute preSqls:[{}]. context info:{}.",
StringUtils.join(renderedPreSqls, ";"), jdbcUrl);
WriterUtil.executeSqls(conn, renderedPreSqls, jdbcUrl, dataBaseType);
DBUtil.closeDBResources(null, null, conn);
}
}
LOG.debug("After job prepare(), originalConfig now is:[\n{}\n]",
originalConfig.toJSON());
}
this.split();
private int split() {
this.adjustChannelNumber();
// 调整设置的通道数量
if (this.needChannelNumber <= 0) {
this.needChannelNumber = 1;
}
// 如果需要的通道数不大于0,则将其设置为1
List<Configuration> readerTaskConfigs = this.doReaderSplit(this.needChannelNumber);
// 根据需要的通道数对reader进行切分(不同数据库有不同逻辑可插件中优化切分sql),并返回切分后的reader配置列表,包含切分查询sql
int taskNumber = readerTaskConfigs.size();
// 获取切分后的任务数量
List<Configuration> writerTaskConfigs = this.doWriterSplit(taskNumber);
// 根据任务数量对writer进行切分,并返回切分后的writer配置列表,包含切分插入sql
List<Configuration> transformerList = this.configuration.getListConfiguration(CoreConstant.DATAX_JOB_CONTENT_TRANSFORMER);
// 获取转换器配置列表
LOG.debug("transformer configuration: "+ JSON.toJSONString(transformerList));
// 记录调试信息:输出转换器配置的JSON字符串
/**
* 输入是reader和writer的parameter list,输出是content下面元素的list
*/
List<Configuration> contentConfig = mergeReaderAndWriterTaskConfigs(
readerTaskConfigs, writerTaskConfigs, transformerList);
// 将reader任务配置列表和writer任务配置列表以及转换器配置列表进行合并,生成最终的配置列表
LOG.debug("contentConfig configuration: "+ JSON.toJSONString(contentConfig));
// 记录调试信息:输出最终整合后的content配置的JSON字符串
this.configuration.set(CoreConstant.DATAX_JOB_CONTENT, contentConfig);
// 将最终的配置列表设置到DataX作业配置中的content部分
return contentConfig.size();
// 返回最终配置列表的大小(即任务的总数)
}
this.schedule();
private void schedule() {
/**
* 这里的全局speed和每个channel的速度设置为B/s
*/
int channelsPerTaskGroup = this.configuration.getInt(
CoreConstant.DATAX_CORE_CONTAINER_TASKGROUP_CHANNEL, 5);
// 从配置中读取每个任务组要使用的通道数,默认为5
int taskNumber = this.configuration.getList(
CoreConstant.DATAX_JOB_CONTENT).size();
// 获取作业内容列表的大小,也就是总的任务数
this.needChannelNumber = Math.min(this.needChannelNumber, taskNumber);
// 确定实际需要的通道数目,不能超过任务数
PerfTrace.getInstance().setChannelNumber(needChannelNumber);
// 设置性能追踪的通道数目
/**
* 通过获取配置信息得到每个taskGroup需要运行哪些tasks任务
*/
List<Configuration> taskGroupConfigs = JobAssignUtil.assignFairly(this.configuration,
this.needChannelNumber, channelsPerTaskGroup);
// 根据需要的通道数和每组通道数,公平地分配给每个任务组具体的任务
LOG.info("Scheduler starts [{}] taskGroups.", taskGroupConfigs.size());
// 日志记录开始调度的任务组总数
ExecuteMode executeMode = null;
AbstractScheduler scheduler;
try {
executeMode = ExecuteMode.STANDALONE;
// 设置执行模式为独立模式
scheduler = initStandaloneScheduler(this.configuration);
// 初始化调度器,设置为独立模式的调度器
//设置 executeMode
for (Configuration taskGroupConfig : taskGroupConfigs) {
taskGroupConfig.set(CoreConstant.DATAX_CORE_CONTAINER_JOB_MODE, executeMode.getValue());
}
// 遍历每个任务组配置,设置执行模式
if (executeMode == ExecuteMode.LOCAL || executeMode == ExecuteMode.DISTRIBUTE) {
if (this.jobId <= 0) {
throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR,
"在[ local | distribute ]模式下必须设置jobId,并且其值 > 0 .");
}
}
// 在分布式或本地模式下,检查jobId必须是有效的
LOG.info("Running by {} Mode.", executeMode);
// 日志记录当前的执行模式
this.startTransferTimeStamp = System.currentTimeMillis();
// 记录任务开始传输的时间戳
scheduler.schedule(taskGroupConfigs);
// 调用调度器进行任务调度
this.endTransferTimeStamp = System.currentTimeMillis();
// 记录任务结束传输的时间戳
} catch (Exception e) {
LOG.error("运行scheduler模式[{}]出错.", executeMode);
this.endTransferTimeStamp = System.currentTimeMillis();
// 如果发生异常,也记录结束时间并抛出运行时异常
throw DataXException.asDataXException(
FrameworkErrorCode.RUNTIME_ERROR, e);
}
/**
* 检查任务执行情况
*/
this.checkLimit();
// 检查任务是否超出执行限制
}
this.taskGroupContainerExecutorService.execute(taskGroupContainerRunner);
taskExecutor.doStart();
public void doStart() {
this.writerThread.start();
// reader没有起来,writer不可能结束
if (!this.writerThread.isAlive() || this.taskCommunication.getState() == State.FAILED) {
throw DataXException.asDataXException(
FrameworkErrorCode.RUNTIME_ERROR,
this.taskCommunication.getThrowable());
}
this.readerThread.start();
// 这里reader可能很快结束
if (!this.readerThread.isAlive() && this.taskCommunication.getState() == State.FAILED) {
// 这里有可能出现Reader线上启动即挂情况 对于这类情况 需要立刻抛出异常
throw DataXException.asDataXException(
FrameworkErrorCode.RUNTIME_ERROR,
this.taskCommunication.getThrowable());
}
}
/**
* 生成writerThread
*/
writerRunner = (WriterRunner) generateRunner(PluginTypeWRITER);
this.writerThread = new Thread(writerRunner,
String.format("%d-%d-%d-writer",
jobId, taskGroupId, this.taskId));
//通过设置thread的contextClassLoader,即可实现同步和主程序不通加载器
this.writerThread.setContextClassLoader(LoadUtil.getJarLoader(
PluginType.WRITER, this.taskConfig.getString(
CoreConstant.JOB_WRITER_NAME)));
ReaderRunner.java
@Override
public void run() {
assert null != this.recordSender;
// 断言确保recordSender不为空
Reader.Task taskReader = (Reader.Task) this.getPlugin();
// 获取读插件的任务实例
// 统计等待写入的时间,并在finally块中结束统计
PerfRecord channelWaitWrite = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.WAIT_WRITE_TIME);
try {
channelWaitWrite.start(); // 开始统计等待写入时间
LOG.debug("task reader starts to do init ...");
// 记录日志,读任务开始初始化
PerfRecord initPerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.READ_TASK_INIT);
initPerfRecord.start(); // 开始初始化性能记录
taskReader.init(); // 调用读插件的初始化方法
initPerfRecord.end(); // 结束初始化性能记录
LOG.debug("task reader starts to do prepare ...");
// 记录日志,读任务开始准备阶段
PerfRecord preparePerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.READ_TASK_PREPARE);
preparePerfRecord.start(); // 开始准备阶段性能记录
taskReader.prepare(); // 调用读插件的准备方法
preparePerfRecord.end(); // 结束准备阶段性能记录
LOG.debug("task reader starts to read ...");
// 记录日志,读任务开始进行数据读取
PerfRecord dataPerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.READ_TASK_DATA);
dataPerfRecord.start(); // 开始读取数据的性能记录
taskReader.startRead(recordSender);
// 调用读插件的读取数据方法,并传给记录发送器
recordSender.terminate(); // 终止记录发送器的发送操作
// 统计读取的记录数和字节数
dataPerfRecord.addCount(CommunicationTool.getTotalReadRecords(super.getRunnerCommunication()));
dataPerfRecord.addSize(CommunicationTool.getTotalReadBytes(super.getRunnerCommunication()));
dataPerfRecord.end(); // 结束读取数据的性能记录
LOG.debug("task reader starts to do post ...");
// 记录日志,读任务开始执行后处理操作
PerfRecord postPerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.READ_TASK_POST);
postPerfRecord.start(); // 开始后处理的性能记录
taskReader.post(); // 调用读插件的后处理方法
postPerfRecord.end(); // 结束后处理的性能记录
// note: 不能在这里标记为成功,成功的标志由 writerRunner 来标志,以防止读任务先结束,而写任务还没结束的严重bug
} catch (Throwable e) {
LOG.error("Reader runner Received Exceptions:", e);
super.markFail(e); // 出现异常时,记录异常并标记任务执行失败
} finally {
LOG.debug("task reader starts to do destroy ...");
// 记录日志,读任务开始进行销毁操作
PerfRecord desPerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.READ_TASK_DESTROY);
desPerfRecord.start(); // 开始销毁的性能记录
super.destroy(); // 调用读任务的销毁方法
desPerfRecord.end(); // 结束销毁的性能记录
channelWaitWrite.end(super.getRunnerCommunication().getLongCounter(CommunicationTool.WAIT_WRITER_TIME));
// 结束统计等待写入时间,并记录
long transformerUsedTime = super.getRunnerCommunication().getLongCounter(CommunicationTool.TRANSFORMER_USED_TIME);
if (transformerUsedTime > 0) {
PerfRecord transformerRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.TRANSFORMER_TIME);
transformerRecord.start(); // 开始转换的性能记录
transformerRecord.end(transformerUsedTime); // 结束转换的性能记录,记录转换使用的时间
}
}
}
WriterRunner.java
@Override
public void run() {
Validate.isTrue(this.recordReceiver != null);
// 确保recordReceiver非空,否则抛出异常
Writer.Task taskWriter = (Writer.Task) this.getPlugin();
// 获取写插件的任务实例
// 统计等待读取的时间,并在finally块中结束统计
PerfRecord channelWaitRead = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.WAIT_READ_TIME);
try {
channelWaitRead.start(); // 开始统计
LOG.debug("task writer starts to do init ...");
// 记录日志,写任务开始初始化
PerfRecord initPerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.WRITE_TASK_INIT);
initPerfRecord.start(); // 开始初始化性能记录
taskWriter.init(); // 调用写入插件的初始化方法
initPerfRecord.end(); // 结束初始化性能记录
LOG.debug("task writer starts to do prepare ...");
// 记录日志,写任务开始准备
PerfRecord preparePerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.WRITE_TASK_PREPARE);
preparePerfRecord.start(); // 开始准备性能记录
taskWriter.prepare(); // 调用写入插件的准备方法
preparePerfRecord.end(); // 结束准备性能记录
LOG.debug("task writer starts to write ...");
// 记录日志,写任务开始写入数据
PerfRecord dataPerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.WRITE_TASK_DATA);
dataPerfRecord.start(); // 开始数据写入性能记录
taskWriter.startWrite(recordReceiver);
// 调用写插件的写入方法,传入记录接收器
// 统计写入的记录数和字节数
dataPerfRecord.addCount(CommunicationTool.getTotalReadRecords(super.getRunnerCommunication()));
dataPerfRecord.addSize(CommunicationTool.getTotalReadBytes(super.getRunnerCommunication()));
dataPerfRecord.end(); // 结束数据写入性能记录
LOG.debug("task writer starts to do post ...");
// 记录日志,写任务开始执行后处理
PerfRecord postPerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.WRITE_TASK_POST);
postPerfRecord.start(); // 开始后处理性能记录
taskWriter.post(); // 调用写插件的后处理方法
postPerfRecord.end(); // 结束后处理性能记录
super.markSuccess(); // 标记该任务执行成功
} catch (Throwable e) {
LOG.error("Writer Runner Received Exceptions:", e);
super.markFail(e); // 异常时标记任务执行失败,并记录异常
} finally {
LOG.debug("task writer starts to do destroy ...");
// 记录日志,写任务开始进行销毁操作
PerfRecord desPerfRecord = new PerfRecord(getTaskGroupId(), getTaskId(), PerfRecord.PHASE.WRITE_TASK_DESTROY);
desPerfRecord.start(); // 开始销毁性能记录
super.destroy(); // 调用写任务的销毁方法
desPerfRecord.end(); // 结束销毁性能记录
// 结束等待读取时间的统计,并将读取时间记录到通信器中
channelWaitRead.end(super.getRunnerCommunication().getLongCounter(CommunicationTool.WAIT_READER_TIME));
}
}
数据的传输
//开始读,传递一个RecordSender参数
@Override
public void startRead(RecordSender recordSender) {
int fetchSize = this.readerSliceConfig.getInt(com.alibaba.datax.plugin.rdbms.reader.Constant.FETCH_SIZE);
this.commonRdbmsReaderSlave.startRead(this.readerSliceConfig, recordSender,
super.getTaskPluginCollector(), fetchSize);
}
//查询结果
rs = DBUtil.query(conn, querySql, fetchSize);
//遍历
while (rs.next()) {
rsNextUsedTime += (System.nanoTime() - lastTime);
//方法处理并传输结果集中的单条记录
this.transportOneRecord(recordSender, rs,
metaData, columnNumber, mandatoryEncoding, taskPluginCollector);
lastTime = System.nanoTime();
}
public void sendToWriter(Record record) {
if(shutdown){
throw DataXException.asDataXException(CommonErrorCode.SHUT_DOWN_TASK, "");
}
Validate.notNull(record, "record不能为空.");
if (record.getMemorySize() > this.byteCapacity) {
this.pluginCollector.collectDirtyRecord(record, new Exception(String.format("单条记录超过大小限制,当前限制为:%s", this.byteCapacity)));
return;
}
//判断缓冲区是否已满,如果满了则执行flush方法,将缓冲区内的数据批量推送。
//如果没满,将记录添加到缓冲区。
boolean isFull = (this.bufferIndex >= this.bufferSize || this.memoryBytes.get() + record.getMemorySize() > this.byteCapacity);
if (isFull) {
flush();
}
this.buffer.add(record);
this.bufferIndex++;
memoryBytes.addAndGet(record.getMemorySize());
}
//通过pushAll方法将缓冲区内的数据推送到通道中。清空缓冲区,并重置相关计数器,为下一次推送做准备。
public void flush() {
if(shutdown){
throw DataXException.asDataXException(CommonErrorCode.SHUT_DOWN_TASK, "");
}
this.channel.pushAll(this.buffer);
this.buffer.clear();
this.bufferIndex = 0;
this.memoryBytes.set(0);
}
public void pushAll(final Collection<Record> rs) {
this.doPushAll(rs);
}
protected void doPushAll(Collection<Record> rs) {
try {
long startTime = System.nanoTime(); // 记录开始推送的时间
lock.lockInterruptibly(); // 可中断地获得锁
int bytes = getRecordBytes(rs); // 获得推送记录集合的总字节大小
// 判断队列和内存是否可容纳即将推送的记录集合
while (memoryBytes.get() + bytes > this.byteCapacity || rs.size() > this.queue.remainingCapacity()) {
notSufficient.await(200L, TimeUnit.MILLISECONDS); // 等待有足够空间或直至超时
}
this.queue.addAll(rs); // 将记录集合添加至队列中
waitWriterTime += System.nanoTime() - startTime; // 更新写入等待的总时间
memoryBytes.addAndGet(bytes); // 更新当前内存字节计数
notEmpty.signalAll(); // 通知所有等待的写消费者队列现在非空
} catch (InterruptedException e) {
throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR, e);
} finally {
lock.unlock(); // 释放锁
}
}
//开始写
public void startWriteWithConnection(RecordReceiver recordReceiver, TaskPluginCollector taskPluginCollector, Connection connection) {
this.taskPluginCollector = taskPluginCollector;
// 设置任务插件收集器,用于在写入过程中收集脏数据和任务指标
// 获取并设置要写入数据库表的字段元数据(比如类型)
this.resultSetMetaData = DBUtil.getColumnMetaData(connection, this.table, StringUtils.join(this.columns, ","));
// 构造写入数据库的SQL语句
calcWriteRecordSql();
// 用于缓存写入记录的缓冲区,容量为批量写入大小
List<Record> writeBuffer = new ArrayList<Record>(this.batchSize);
int bufferBytes = 0; // 缓冲区已经占用的字节大小
try {
Record record;
// 循环获取从读取器传入的记录,直到没有记录为止
while ((record = recordReceiver.getFromReader()) != null) {
// 检查获取的记录的字段数量是否与数据库表中的字段数量匹配
if (record.getColumnNumber() != this.columnNumber) {
// 如果不相等,则抛出配置错误异常
throw DataXException.asDataXException(
DBUtilErrorCode.CONF_ERROR, String.format(
"列配置信息有错误. 因为您配置的任务中,源头读取字段数:%s 与 目的表要写入的字段数:%s 不相等. 请检查您的配置并作出修改.",
record.getColumnNumber(), this.columnNumber));
}
// 将记录添加到写入缓冲区
writeBuffer.add(record);
// 累计增加缓存的字节大小
bufferBytes += record.getMemorySize();
// 如果缓冲区的大小达到了批量写入的记录数或字节大小,就进行批量写入操作
if (writeBuffer.size() >= batchSize || bufferBytes >= batchByteSize) {
doBatchInsert(connection, writeBuffer);
writeBuffer.clear(); // 写入后清空缓冲区
bufferBytes = 0; // 字节计数器重置为0
}
}
// 循环结束后,如果缓冲区还有记录,则再执行一次批量写入
if (!writeBuffer.isEmpty()) {
doBatchInsert(connection, writeBuffer);
writeBuffer.clear(); // 最后一次写入后清空缓冲区
bufferBytes = 0; // 字节计数器重置为0
}
} catch (Exception e) {
// 如果在写入过程中遇到异常,封装并抛出写入数据异常
throw DataXException.asDataXException(DBUtilErrorCode.WRITE_DATA_ERROR, e);
} finally {
// 最终,无论成功与否都清空缓冲区并关闭数据库资源
writeBuffer.clear();
bufferBytes = 0;
DBUtil.closeDBResources(null, null, connection);
}
}
//recordReceiver.getFromReader()
//receive();
//this.channel.pullAll(this.buffer);
protected void doPullAll(Collection<Record> rs) {
assert rs != null; // 断言确认传入的集合rs不为空
rs.clear(); // 清空传入的集合,准备放入新的记录
try {
long startTime = System.nanoTime(); // 记录开始拉取的时间
lock.lockInterruptibly(); // 可中断地获得锁,以确保队列操作的线程安全
// 使用drainTo从队列中移出元素到集合rs中,直到移出的数量大于0或超时
while (this.queue.drainTo(rs, bufferSize) <= 0) {
// 如果队列为空,则等待直到队列非空或超时
notEmpty.await(200L, TimeUnit.MILLISECONDS);
}
waitReaderTime += System.nanoTime() - startTime; // 更新读取等待的总时间
int bytes = getRecordBytes(rs); // 获取rs集合中所有记录的字节总数
memoryBytes.addAndGet(-bytes); // 更新当前内存字节计数,减去移出的记录的字节大小
notSufficient.signalAll(); // 发送信号,通知可能在等待足够内存空间的生产者线程
} catch (InterruptedException e) {
// 如果在等待过程中线程被中断,抛出运行时异常
throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR, e);
} finally {
lock.unlock(); // 释放锁
}
}
总结
读线程的缓冲区:Reader插件负责从数据源读取数据,并将这些数据暂存到它的缓冲区中。这个缓冲区通常是内存中的一段空间,它可能是由一个或一组对象(如Record集合)组成。读线程会不断地填满这个缓冲区,直到达到设定的大小或数量限制。
从读线程缓冲区到MemoryChannel:当读线程的缓冲区填满后,读线程会调用doPush或doPushAll方法将缓冲区中的数据推送到MemoryChannel的队列中。这个过程可能会因队列已满而阻塞。在缓冲区的数据成功转移到队列中之后,读线程的缓冲区被清空,可以开始下一轮的数据读取。
写线程的缓冲区:与读线程类似,写线程(Writer插件)也有自己的缓冲区,用于从MemoryChannel接收数据。当写线程调用doPull或doPullAll方法时,它会尝试从MemoryChannel中取出足够填满其缓冲区的数据。如果此时MemoryChannel的队列为空,则写线程将阻塞等待,直到有足够的数据可供拉取。
从MemoryChannel到写线程缓冲区:当MemoryChannel中有数据时,写线程将数据从队列中拉取到它的缓冲区内。这个操作可能会在缓冲区满或MemoryChannel被读空时阻塞。
写线程缓冲区到目的地:填充了数据之后,写线程会处理缓冲区中的数据并将其写入到目标数据存储系统中。写入完成后,写线程缓冲区被清空,准备接收新一轮从MemoryChannel传输过来的数据
MemoryChannel:
// 条件变量,当内存不足时,生产者线程会等待此条件
private Condition notSufficient;
// 条件变量,当队列为空时,消费者线程会等待此条件
private Condition notEmpty;