dataX中CommonRdbms的分片过程

CommonRdbms主要泛指一些常用的传统数据库如Mysql、Oracle等,本文以Mysql到Mysql的导入为例说明这类数据库的分片过程。
split的入口是在JobContainer#split,主要包含以下几个步骤:

  1. 根据用户配置的值算出当前job的channel的建议值;
  2. Reader端分片;
  3. Writer端分片;
  4. 合并Reader和Writer端的分片,一形成一一对应的关系,便于后面任务调度的操作。
// JobContainer#split
private int split() {
        this.adjustChannelNumber();

        if (this.needChannelNumber <= 0) {
            this.needChannelNumber = 1;
        }

        List readerTaskConfigs = this
                .doReaderSplit(this.needChannelNumber);
        int taskNumber = readerTaskConfigs.size();
        List writerTaskConfigs = this
                .doWriterSplit(taskNumber);

        List transformerList = this.configuration.getListConfiguration(CoreConstant.DATAX_JOB_CONTENT_TRANSFORMER);

        LOG.debug("transformer configuration: "+ JSON.toJSONString(transformerList));
        /**
         * 输入是reader和writer的parameter list,输出是content下面元素的list
         */
        List contentConfig = mergeReaderAndWriterTaskConfigs(
                readerTaskConfigs, writerTaskConfigs, transformerList);


        LOG.debug("contentConfig configuration: "+ JSON.toJSONString(contentConfig));

        this.configuration.set(CoreConstant.DATAX_JOB_CONTENT, contentConfig);

        return contentConfig.size();
    }

计算当前job的channel的建议值

dataX提供了流量控制,流量控制的主要配置在channel中,先明确几个配置:

  • job.setting.speed.channel 用户配置的该job所需要的channel的个数;
  • job.setting.speed.byte 用户配置的该job最大的流量
  • core.transport.channel.speed.byte 单个channel容纳最多的字节数
  • job.setting.speed.record 用户配置该job最大的record流量
  • core.transport.channel.speed.record 单个channel容纳最多的record数(一个record可以包含表中的多行数据)
    上述以core打头的配置,在$datax_home/conf/core.json中的默认配置为-1,即默认没有配置.如果用户需要做流量控制,配置了job.setting.speed.byte就必须配置core.transport.channel.speed.byte;配置了job.setting.speed.record也必须配置core.transport.channel.speed.record,否则会抛出异常。

当前job的channel的建议值计算过程是:

  • 如果用户做了字节流量控制,变量needChannelNumberByByte = job总字节流量/单个channel的字节流量;
  • 如果用户做了record配置,变量needChannelNumberByRecord = job总reocrd/单个channel的record;
  • 取min(needChannelNumberByByte, needChannelNumberByRecord)作为返回值。
  • 如果上述两个变量的没有配置,那么返回用户配置的job.setting.speed.channel
    代码主要在JobContainer#adjustChannelNumber,代码太长就不贴代码了。

Reader端分片

Reader端分片主要是确定Task的数量,一个分片对应一个Task,Writer端的分片数和Reader一样,做到一一对应。
Reader端分片的主要逻辑是在ReaderSplitUtil.doSplit(originalConfig, adviceNumber)这个方法中,其中第一个参数是已经解析好的Reader端配置,第二个参数是第一步算好的channel建议数。看源码这个adviceNumber并没有起到很好的作用,因为adviceNumber和表不可能整除另外只有一个表的时候,源码不知道是做了什么骚操作。。。咱也不知道。

  1. 如果是querySQL模式,这个时候分片数不用算了,有几个sql就是几个分片了,直接返回配置即可。
  2. 如果是table模式并且设置了splitPk,按照下面的逻辑对每个table(可以配置多个table)进行分片。
  • 具体逻辑是在SingleTableSplitUtil#splitSingleTable中;
  • 每个分片对应一个Configuration,作为后续执行一个Task的配置;
  • 获取splitPk字段在该表中最小值和最大值,如果最大值或者最小值是null直接作为一个分片返回;
  • 将minPK和maxPK之间的数据分成adviceNum等分,如果不能整除则分成adviceNum+1;
  • 按照用户的设置的column和where语句拼接查询语句(和querySql中用户配置的sql一样),取上一步每分数据的minPK和maxPK,并生成where子句拼接到sql上;
  • splitPK字段值为null的将会作为一分片;
  • 将生成的sql以"querySql"作为键设置进configuration中,一个分片就此完成。

注意Reader端是可以配置多个connection元素的,每个connection中的每张表分片的处理逻辑是一样的,并且最后返回的configuration中已经包含了该connection的连接信息,最后执行导入任务的时候只需根据该分片的连接信息执行querySql即可。

Writer端的分片

Writer端分片的逻辑是在WriterUtil#doSplit (Configuration simplifiedConf,int adviceNumber)这个方法中,其中第一个参数是已经解析好的Writer端配置,第二个参数是Reader分片算好的建议分片数,这个不应该叫建议数了因为Writer端一定会按照这个数进行分片,以做到Reader端和Writer端的一一对应。
具体操作为:

  • 如果将所有数据导入到一张表中,那么Reader端有几个分片就返回几个configuration,configuration主要包括连接信息、表名、字段等信息;
  • 如果Writer端配置了多个connection和多个table(如果adviceNumber和配置的表的个数不一样直接抛出异常),则会安装这些在配置文件出场的先后顺序解析成configuration并返回。

合并Reader和Writer端的分片

将Reader端和Writer端返回的configuration按照一一对应的顺序封装进一个新的配置中,便于后续的执行。

一个栗子

这个栗子主要说明多个分片,Reader端和Writer端一一对应的关系。下面配置文件中直接用querySql模式配置了3个分片,Writer端配置了多个connection和多张表,左后执行的结果为:

  • select* from user where id = 1这条sql查询出来的数据会导入到表user1中;
  • select* from user where id = 2这条sql查询出来的数据会导入到表user2中;
  • select* from user where id = 3这条sql查询出来的数据会导入到表user3中;
"job": {
        "setting": {
            "speed": {
                 "channel": 10
            },
            "errorLimit": {
                "record": 0,
                "percentage": 0.02
            }
        },
        "content": [
            {
                "reader": {
                    "name": "mysqlreader",
                    "parameter": {
                        "username": "root",
                        "password": "123456",
                        "column": ["*"],
                        "connection": [
                            {
                                "querySql": ["select* from user where id = 1",
                                             "select* from user where id = 2",
                                             "select* from user where id = 3",
                                            ],
                                "jdbcUrl": ["jdbc:mysql://172.10.10.231:3306/test"]
                            }
                        ]
                    }
                },
               "writer": {
                    "name": "mysqlwriter",
                    "parameter": {
                        "writeMode": "insert",
                        "username": "root",
                        "password": "123456",
                        "column": ["*"],
                        "connection": [
                            {
                                "jdbcUrl": "jdbc:mysql://172.10.10.231:3306/test1",
                                "table": [
                                    "user1",
                                    "user2"
                                ]
                            },
                            {
                                "jdbcUrl": "jdbc:mysql://172.10.10.231:3306/test2",
                                "table": [
                                    "user3"
                                ]
                            }
                        ]
                    }
                }
            }
        ]
    }

你可能感兴趣的:(dataX中CommonRdbms的分片过程)