【记一次datax源码全流程debug】

记一次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;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值