CommonRdbms主要泛指一些常用的傳統(tǒng)數(shù)據(jù)庫(kù)如Mysql、Oracle等,本文以Mysql到Mysql的導(dǎo)入為例說(shuō)明這類(lèi)數(shù)據(jù)庫(kù)的分片過(guò)程。
split的入口是在JobContainer#split,主要包含以下幾個(gè)步驟:
- 根據(jù)用戶(hù)配置的值算出當(dāng)前job的channel的建議值;
- Reader端分片;
- Writer端分片;
- 合并Reader和Writer端的分片,一形成一一對(duì)應(yīng)的關(guān)系,便于后面任務(wù)調(diào)度的操作。
// JobContainer#split
private int split() {
this.adjustChannelNumber();
if (this.needChannelNumber <= 0) {
this.needChannelNumber = 1;
}
List<Configuration> readerTaskConfigs = this
.doReaderSplit(this.needChannelNumber);
int taskNumber = readerTaskConfigs.size();
List<Configuration> writerTaskConfigs = this
.doWriterSplit(taskNumber);
List<Configuration> transformerList = this.configuration.getListConfiguration(CoreConstant.DATAX_JOB_CONTENT_TRANSFORMER);
LOG.debug("transformer configuration: "+ JSON.toJSONString(transformerList));
/**
* 輸入是reader和writer的parameter list,輸出是content下面元素的list
*/
List<Configuration> contentConfig = mergeReaderAndWriterTaskConfigs(
readerTaskConfigs, writerTaskConfigs, transformerList);
LOG.debug("contentConfig configuration: "+ JSON.toJSONString(contentConfig));
this.configuration.set(CoreConstant.DATAX_JOB_CONTENT, contentConfig);
return contentConfig.size();
}
計(jì)算當(dāng)前job的channel的建議值
dataX提供了流量控制,流量控制的主要配置在channel中,先明確幾個(gè)配置:
-
job.setting.speed.channel
用戶(hù)配置的該job所需要的channel的個(gè)數(shù); -
job.setting.speed.byte
用戶(hù)配置的該job最大的流量 -
core.transport.channel.speed.byte
單個(gè)channel容納最多的字節(jié)數(shù) -
job.setting.speed.record
用戶(hù)配置該job最大的record流量 -
core.transport.channel.speed.record
單個(gè)channel容納最多的record數(shù)(一個(gè)record可以包含表中的多行數(shù)據(jù))
上述以core
打頭的配置,在$datax_home/conf/core.json
中的默認(rèn)配置為-1,即默認(rèn)沒(méi)有配置.如果用戶(hù)需要做流量控制,配置了job.setting.speed.byte
就必須配置core.transport.channel.speed.byte
;配置了job.setting.speed.record
也必須配置core.transport.channel.speed.record
,否則會(huì)拋出異常。
當(dāng)前job的channel的建議值計(jì)算過(guò)程是:
- 如果用戶(hù)做了字節(jié)流量控制,變量needChannelNumberByByte = job總字節(jié)流量/單個(gè)channel的字節(jié)流量;
- 如果用戶(hù)做了record配置,變量needChannelNumberByRecord = job總reocrd/單個(gè)channel的record;
- 取min(needChannelNumberByByte, needChannelNumberByRecord)作為返回值。
- 如果上述兩個(gè)變量的沒(méi)有配置,那么返回用戶(hù)配置的
job.setting.speed.channel
代碼主要在JobContainer#adjustChannelNumber,代碼太長(zhǎng)就不貼代碼了。
Reader端分片
Reader端分片主要是確定Task的數(shù)量,一個(gè)分片對(duì)應(yīng)一個(gè)Task,Writer端的分片數(shù)和Reader一樣,做到一一對(duì)應(yīng)。
Reader端分片的主要邏輯是在ReaderSplitUtil.doSplit(originalConfig, adviceNumber)這個(gè)方法中,其中第一個(gè)參數(shù)是已經(jīng)解析好的Reader端配置,第二個(gè)參數(shù)是第一步算好的channel建議數(shù)。看源碼這個(gè)adviceNumber
并沒(méi)有起到很好的作用,因?yàn)閍dviceNumber和表不可能整除另外只有一個(gè)表的時(shí)候,源碼不知道是做了什么騷操作。。。咱也不知道。
- 如果是querySQL模式,這個(gè)時(shí)候分片數(shù)不用算了,有幾個(gè)sql就是幾個(gè)分片了,直接返回配置即可。
- 如果是table模式并且設(shè)置了splitPk,按照下面的邏輯對(duì)每個(gè)table(可以配置多個(gè)table)進(jìn)行分片。
- 具體邏輯是在SingleTableSplitUtil#splitSingleTable中;
- 每個(gè)分片對(duì)應(yīng)一個(gè)Configuration,作為后續(xù)執(zhí)行一個(gè)Task的配置;
- 獲取splitPk字段在該表中最小值和最大值,如果最大值或者最小值是null直接作為一個(gè)分片返回;
- 將minPK和maxPK之間的數(shù)據(jù)分成adviceNum等分,如果不能整除則分成adviceNum+1;
- 按照用戶(hù)的設(shè)置的column和where語(yǔ)句拼接查詢(xún)語(yǔ)句(和querySql中用戶(hù)配置的sql一樣),取上一步每分?jǐn)?shù)據(jù)的minPK和maxPK,并生成where子句拼接到sql上;
- splitPK字段值為null的將會(huì)作為一分片;
- 將生成的sql以"querySql"作為鍵設(shè)置進(jìn)configuration中,一個(gè)分片就此完成。
注意Reader端是可以配置多個(gè)connection
元素的,每個(gè)connection
中的每張表分片的處理邏輯是一樣的,并且最后返回的configuration中已經(jīng)包含了該connection
的連接信息,最后執(zhí)行導(dǎo)入任務(wù)的時(shí)候只需根據(jù)該分片的連接信息執(zhí)行querySql即可。
Writer端的分片
Writer端分片的邏輯是在WriterUtil#doSplit (Configuration simplifiedConf,int adviceNumber)這個(gè)方法中,其中第一個(gè)參數(shù)是已經(jīng)解析好的Writer端配置,第二個(gè)參數(shù)是Reader分片算好的建議分片數(shù),這個(gè)不應(yīng)該叫建議數(shù)了因?yàn)閃riter端一定會(huì)按照這個(gè)數(shù)進(jìn)行分片,以做到Reader端和Writer端的一一對(duì)應(yīng)。
具體操作為:
- 如果將所有數(shù)據(jù)導(dǎo)入到一張表中,那么Reader端有幾個(gè)分片就返回幾個(gè)configuration,configuration主要包括連接信息、表名、字段等信息;
- 如果Writer端配置了多個(gè)
connection
和多個(gè)table(如果adviceNumber和配置的表的個(gè)數(shù)不一樣直接拋出異常),則會(huì)安裝這些在配置文件出場(chǎng)的先后順序解析成configuration并返回。
合并Reader和Writer端的分片
將Reader端和Writer端返回的configuration按照一一對(duì)應(yīng)的順序封裝進(jìn)一個(gè)新的配置中,便于后續(xù)的執(zhí)行。
一個(gè)栗子
這個(gè)栗子主要說(shuō)明多個(gè)分片,Reader端和Writer端一一對(duì)應(yīng)的關(guān)系。下面配置文件中直接用querySql
模式配置了3個(gè)分片,Writer端配置了多個(gè)connection
和多張表,左后執(zhí)行的結(jié)果為:
-
select* from user where id = 1
這條sql查詢(xún)出來(lái)的數(shù)據(jù)會(huì)導(dǎo)入到表user1
中; -
select* from user where id = 2
這條sql查詢(xún)出來(lái)的數(shù)據(jù)會(huì)導(dǎo)入到表user2
中; -
select* from user where id = 3
這條sql查詢(xún)出來(lái)的數(shù)據(jù)會(huì)導(dǎo)入到表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"
]
}
]
}
}
}
]
}