很多小伙伴遇到datax导数很慢很慢。。慢到一两千万的数据要花十个小时的去导,有的速度真是只有 300-500 rec/s 简直是惨不忍睹。
这篇文章将仔细告诉大家,你的datax任务为什么这么慢,怎么去解决。
首先说明下,个人认为reader提速最重要的一点就是切分任务即split这块,懒得看过程的请直接跳到结尾。。。。结尾的json照着抄就行,莫要瞎改,等你跑好了你再改其他参数
split 又分为两种,就是如何把1个job分解为多个task,以mysqlreader为例
1.手动划分, 通过querySql[ "select * from table where id =1","where id =2 ","where id=3"]
2.自动划分, splitPk:"id"
3.为什么我手动划分或者自动化分了,任务数没有变,或者速度还是很慢? 接着往下看。
背景:一直用datax从oracle到hive数据,一般是1w-2w条/s也凑合用了,但是最近导DM_F_xxxx表 该表根据月在hive分区,每个分区数据在200w-1000w左右,数据量也不算特别大但是速度只有4M/s 速度不到7000条/s,
{
"job":{
"setting":{
"speed":{
"channel":10
},
"errorLimit":{
"record":0,
"percentage":0.02
}
},
"content":[
{
"reader":{
"name":"oraclereader",
"parameter":{
"username":"${username}",
"password":"${password}",
"connection":[
{
"jdbcUrl":[
"${jdbcUrl}"
],
"querySql":[
"select xxxxxxxx from DM_F_xxxx where period_id = ${bd_date}"
]
}
]
}
},
查看json日志发现 channel=10但是切分任务就只有1个。觉得事情没那么简单。
第一步,参数调优 根据datax官网介绍DataX/oraclereader.md at master · alibaba/DataX · GitHub
fetchSize:该配置项定义了插件和数据库服务器端每次批量数据获取条数,该值决定了DataX和服务器端的网络交互次数,能够较大的提升数据抽取性能。
因为服务器内存一般有7-8G 直接搞到2048 无用
Q: OracleReader抽取速度很慢怎么办?
A: 影响抽取时间的原因大概有如下几个:(来自专业 DBA 卫绾)
- 由于SQL的plan异常,导致的抽取时间长; 在抽取时,尽可能使用全表扫描代替索引扫描;
- 合理sql的并发度,减少抽取时间;根据表的大小, <50G可以不用并发, <100G添加如下hint: parallel(a,2),
100G添加如下hint : parallel(a,4);
- 抽取sql要简单,尽量不用replace等函数,这个非常消耗cpu,会严重影响抽取速度;
我这里是直接select所有字段1、排除 2、开并行,这个可以增加查询速度, 试了无用!! 3、无函数没用
设置records 、byte、channel 无用!!!
"setting": {
"speed": {
//设置传输速度 byte/s 尽量逼近这个速度但是不高于它.
// channel 表示通道数量,byte表示通道速度,如果单通道速度1MB,配置byte为1048576表示一个channel
"byte": 1048576
}
还有个splitPk 试了,datax提示和querysql冲突,split不起作用,先停下
第二步,调大内存!!!
DATAX_HOME/bin/data.py 30行
DEFAULT_JVM = "-Xms2g -Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=%s/log" % (DATAX_HOME)
发现由以前的不到7000records/s 提升到7000+records/s 不知道是不是错觉,感觉没啥吊用!
第三步百度记一次DataX-MysqlReader性能优化_Shadow_Light的博客-CSDN博客
其余的只会说 byte channel records 调大 没啥用,这位最详细
打开{datax-path}/conf/core.json 将trace :enable改为true,重跑一下任务可以看到更加详细的日志文件
这个是那位博主的看图可以看住result_next_all时间和wait_read_time时间过长
看我的result_next_all 20多秒感觉是可以接受的,wait_read_time忽略不计, 那这个wait_write_time是啥怎么这么长?看下源码
可以看到wait_write_time中间要做 readerTask init->prepare->read->post->destory 所以耗费时间长点也很正常
2021-11 补充 以前说错了。 wait read_time太长 说明写的太快,读的太慢,此时应该调大读取的fetchsize,增加内存之类的
public class ReaderRunner extends AbstractRunner implements Runnable {
@Override
public void run() {
assert null != this.recordSender;
Reader.Task taskReader = (Reader.Task) this.getPlugin();
//统计waitWriterTime,并且在finally才end。
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();
// automatic flush
// super.markSuccess(); 这里不能标记为成功,成功的标志由 writerRunner 来标志(否则可能导致 reader 先结束,而 writer 还没有结束的严重 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);
}
}
}
}
第四步,想来想去还得从并行开始着手,为啥splitPk和querySql冲突,为啥我设置了10个channel但是始终是一个task呢?
调整splitPk(备注该表无自增id,随便找了个差不多有区别的字段来用) 使用table模式 速度直接彪升到2w多,可能还是因为主键原因感觉还有很大提升
看下源码(如何找源码呢?根据日志打印的两句话。。。)
2020-12-09 16:14:07.947 [job-0] INFO JobContainer - Job set Channel-Number to 10 channels.
2020-12-09 16:14:07.949 [job-0] INFO JobContainer - DataX Reader.Job [oraclereader] splits to [1] tasks.
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();
}
private void adjustChannelNumber() {
先看JobContainer ->adjustChannelNumber
private void adjustChannelNumber() {
int needChannelNumberByByte = Integer.MAX_VALUE;
int needChannelNumberByRecord = Integer.MAX_VALUE;
boolean isByteLimit = (this.configuration.getInt(
CoreConstant.DATAX_JOB_SETTING_SPEED_BYTE, 0) > 0);
if (isByteLimit) {
long globalLimitedByteSpeed = this.configuration.getInt(
CoreConstant.DATAX_JOB_SETTING_SPEED_BYTE, 10 * 1024 * 1024);
// 在byte流控情况下,单个Channel流量最大值必须设置,否则报错!
Long channelLimitedByteSpeed = this.configuration
.getLong(CoreConstant.DATAX_CORE_TRANSPORT_CHANNEL_SPEED_BYTE);
if (channelLimitedByteSpeed == null || channelLimitedByteSpeed <= 0) {
DataXException.asDataXException(
FrameworkErrorCode.CONFIG_ERROR,
"在有总bps限速条件下,单个channel的bps值不能为空,也不能为非正数");
}
needChannelNumberByByte =
(int) (globalLimitedByteSpeed / channelLimitedByteSpeed);
needChannelNumberByByte =
needChannelNumberByByte > 0 ? needChannelNumberByByte : 1;
LOG.info("Job set Max-Byte-Speed to " + globalLimitedByteSpeed + " bytes.");
}
boolean isRecordLimit = (this.configuration.getInt(
CoreConstant.DATAX_JOB_SETTING_SPEED_RECORD, 0)) > 0;
if (isRecordLimit) {
long globalLimitedRecordSpeed = this.configuration.getInt(
CoreConstant.DATAX_JOB_SETTING_SPEED_RECORD, 100000);
Long channelLimitedRecordSpeed = this.configuration.getLong(
CoreConstant.DATAX_CORE_TRANSPORT_CHANNEL_SPEED_RECORD);
if (channelLimitedRecordSpeed == null || channelLimitedRecordSpeed <= 0) {
DataXException.asDataXException(FrameworkErrorCode.CONFIG_ERROR,
"在有总tps限速条件下,单个channel的tps值不能为空,也不能为非正数");
}
needChannelNumberByRecord =
(int) (globalLimitedRecordSpeed / channelLimitedRecordSpeed);
needChannelNumberByRecord =
needChannelNumberByRecord > 0 ? needChannelNumberByRecord : 1;
LOG.info("Job set Max-Record-Speed to " + globalLimitedRecordSpeed + " records.");
}
// 取较小值
this.needChannelNumber = needChannelNumberByByte < needChannelNumberByRecord ?
needChannelNumberByByte : needChannelNumberByRecord;
// 如果从byte或record上设置了needChannelNumber则退出
if (this.needChannelNumber < Integer.MAX_VALUE) {
return;
}
boolean isChannelLimit = (this.configuration.getInt(
CoreConstant.DATAX_JOB_SETTING_SPEED_CHANNEL, 0) > 0);
if (isChannelLimit) {
this.needChannelNumber = this.configuration.getInt(
CoreConstant.DATAX_JOB_SETTING_SPEED_CHANNEL);
LOG.info("Job set Channel-Number to " + this.needChannelNumber
+ " channels.");
return;
}
throw DataXException.asDataXException(
FrameworkErrorCode.CONFIG_ERROR,
"Job运行速度必须设置");
}
1、看job.setting.speed.byte 是否>0
最后算出需要的channel数=(int) job.setting.speed.byte /core.transport.channel.speed.byte
2、job.setting.speed.record是否>0
最后算出需要的channel数=(int)job.setting.speed.record /core.transport.channel.speed.record
3、如果设置了byte或者record或者两个都设置了,那么取他们两个算出的最小的channel数,忽略job.setting.speed.channel参数
4、如果没有设置,那么就用channel的值作为需要的channel数
注意1和2里面的core.transport.channel.speed.byte/record 默认值都是-1........所以直接看第4步就行。
即 adjustChannelNumber()方法主要是根据byte/record/channel算出大概需要多少个channel
再看JobContainer->doReaderSplit方法
private List<Configuration> doReaderSplit(int adviceNumber) {
classLoaderSwapper.setCurrentThreadClassLoader(LoadUtil.getJarLoader(
PluginType.READER, this.readerPluginName));
List<Configuration> readerSlicesConfigs =
this.jobReader.split(adviceNumber);
if (readerSlicesConfigs == null || readerSlicesConfigs.size() <= 0) {
throw DataXException.asDataXException(
FrameworkErrorCode.PLUGIN_SPLIT_ERROR,
"reader切分的task数目不能小于等于0");
}
LOG.info("DataX Reader.Job [{}] splits to [{}] tasks.",
this.readerPluginName, readerSlicesConfigs.size());
classLoaderSwapper.restoreCurrentThreadClassLoader();
return readerSlicesConfigs;
}
接着是oracleReader->split()然后一直追踪到ReaderSplitUtil.doSplit(originalConfig, adviceNumber);
public static List<Configuration> doSplit(
Configuration originalSliceConfig, int adviceNumber) {
boolean isTableMode = originalSliceConfig.getBool(Constant.IS_TABLE_MODE).booleanValue();
int eachTableShouldSplittedNumber = -1;
if (isTableMode) {
// adviceNumber这里是channel数量大小, 即datax并发task数量
// eachTableShouldSplittedNumber是单表应该切分的份数, 向上取整可能和adviceNumber没有比例关系了已经
eachTableShouldSplittedNumber = calculateEachTableShouldSplittedNumber(
adviceNumber, originalSliceConfig.getInt(Constant.TABLE_NUMBER_MARK));
}
String column = originalSliceConfig.getString(Key.COLUMN);
String where = originalSliceConfig.getString(Key.WHERE, null);
List<Object> conns = originalSliceConfig.getList(Constant.CONN_MARK, Object.class);
List<Configuration> splittedConfigs = new ArrayList<Configuration>();
for (int i = 0, len = conns.size(); i < len; i++) {
Configuration sliceConfig = originalSliceConfig.clone();
Configuration connConf = Configuration.from(conns.get(i).toString());
String jdbcUrl = connConf.getString(Key.JDBC_URL);
sliceConfig.set(Key.JDBC_URL, jdbcUrl);
// 抽取 jdbcUrl 中的 ip/port 进行资源使用的打标,以提供给 core 做有意义的 shuffle 操作
sliceConfig.set(CommonConstant.LOAD_BALANCE_RESOURCE_MARK, DataBaseType.parseIpFromJdbcUrl(jdbcUrl));
sliceConfig.remove(Constant.CONN_MARK);
Configuration tempSlice;
// 说明是配置的 table 方式
if (isTableMode) {
// 已在之前进行了扩展和`处理,可以直接使用
List<String> tables = connConf.getList(Key.TABLE, String.class);
Validate.isTrue(null != tables && !tables.isEmpty(), "您读取数据库表配置错误.");
String splitPk = originalSliceConfig.getString(Key.SPLIT_PK, null);
//最终切分份数不一定等于 eachTableShouldSplittedNumber
boolean needSplitTable = eachTableShouldSplittedNumber > 1
&& StringUtils.isNotBlank(splitPk);
if (needSplitTable) {
if (tables.size() == 1) {
//原来:如果是单表的,主键切分num=num*2+1
// splitPk is null这类的情况的数据量本身就比真实数据量少很多, 和channel大小比率关系时,不建议考虑
//eachTableShouldSplittedNumber = eachTableShouldSplittedNumber * 2 + 1;// 不应该加1导致长尾
//考虑其他比率数字?(splitPk is null, 忽略此长尾)
eachTableShouldSplittedNumber = eachTableShouldSplittedNumber * 5;
}
// 尝试对每个表,切分为eachTableShouldSplittedNumber 份
for (String table : tables) {
tempSlice = sliceConfig.clone();
tempSlice.set(Key.TABLE, table);
List<Configuration> splittedSlices = SingleTableSplitUtil
.splitSingleTable(tempSlice, eachTableShouldSplittedNumber);
splittedConfigs.addAll(splittedSlices);
}
} else {
for (String table : tables) {
tempSlice = sliceConfig.clone();
tempSlice.set(Key.TABLE, table);
String queryColumn = HintUtil.buildQueryColumn(jdbcUrl, table, column);
tempSlice.set(Key.QUERY_SQL, SingleTableSplitUtil.buildQuerySql(queryColumn, table, where));
splittedConfigs.add(tempSlice);
}
}
} else {
// 说明是配置的 querySql 方式
List<String> sqls = connConf.getList(Key.QUERY_SQL, String.class);
// TODO 是否check 配置为多条语句??
for (String querySql : sqls) {
tempSlice = sliceConfig.clone();
tempSlice.set(Key.QUERY_SQL, querySql);
splittedConfigs.add(tempSlice);
}
}
}
return splittedConfigs;
}
这个方法一上来就是判断是否是tableMode,这个参数应该就是datax检查你配制的是table还是querysql 然后自己set的
先看else吧比较少
1、如果是querysql connConf.getList(Key.QUERY_SQL, String.class);一般来说我们只会写一个querysql。那么这个sqls.size一般=1,你遍历sqls然后放到splittedConfigs 那么splittedConfigs =1
所以你如果是querysql的话 只要你不是多个querysql 你的读任务数量一直=1
2、如果是使用的是table方式
eachTableShouldSplittedNumber =math.ceil(之前算出的channel数/tableNumber) (这里表的数量是指例如你又两个连接,每个连接有2个表table:["table1","table2"] 那么这里tableNumber=4 )
获取column 获取where条件 获取多个connection
遍历connection
boolean needSplitTable = eachTableShouldSplittedNumber > 1&& StringUtils.isNotBlank(splitPk); 这里 一般如果设置了channel ,eachTableShouldSplittedNumber 基本>1 splitPk也不为空
那么需要切片 然后datax有个算法,随机取数然后算出。。。。。暂时不研究了
附上一个最后的图
最后速度没有特别快只达到2w/s原因应该是 过多的空文件浪费了查询时间,因为我splitPk这里有问题,表没有唯一主键,所以取样后切分肯定存在问题。
____________________________________________________________________
2022-04-11更新
鉴于很多小伙伴加vx问我具体怎么操作,现总结下:
以mysqlreader到oraclereader为例
1. channel数设置为10,设置过高会导致cpu占用过高,也有可能导致oom,加大内存最方便。
2.reader中模式改为 table和column 。 setting里不要设置byte 只设置channel就行。
3.splitPk 这个注意 mysql postgresql 只能用int类型的字段,
oracle可以是string int 类型的
注意这里不需要是主键 唯一键!!!什么字段都可以,只是值需要尽量分散
4.writer里设置batchSIze和batchByteSize。这两个需要一起设置 设置一个可能没用
这里告诉大家一个技巧,怎么估计自己的一条记录多大,以下图为例,
一条record=14.89MB/264268record=0.5kb,
datax默认的 batchSize:2048 batchByteSize:32 * 1024 * 1024
5.mysqlreader里不用设置fetchsize,oraclereader可以设置fetchSize=1024默认自行调整
说下datax官网感觉有点乱,很多md的默认参数都不对,建议还是去源码看下
--更新下 mysql 也能设置fetchSize 但是需要版本5以上 同时开启游标
--如果mysql的数据量特别大可以在jdbc后面 &userCompress=true试下
6.如果这些设置了感觉到速度有提升,但是还没到满意的程度,可以看日志
[INFO] 2022-04-11 10:46:17.944 - [taskAppId=TASK-114-229826-1275259]:[127] - -> 2022-04-11 10:46:17.944 [job-0] INFO StandAloneJobContainerCommunicator - Total 13084832 records, 767171544 bytes | Speed 18.74MB/s, 343340 records/s | Error 0 records, 0 bytes | All Task WaitWriterTime 70.005s | All Task WaitReaderTime 174.642s | Percentage 0.00%
我这个还好,感觉算是读写速度都很快的了,相对来说写慢了点...
reader时间过长的截图找不到,读取太慢,增加fetchsize 增加channel,oracle开启paralize
writer时间过长的截图,等待写的时间1600s 等待读的时间只有11s
调整batchsize,batchByteSize channel
这里着重说下read和write处理速度不一致差异过大一般是任务有点慢的原因:比如你的任务2-3w record/s 感觉有点点慢。以上述为例等待写1600s。你调整batchsize会有一点效果,但是最明显的效果还是splitPk切分任务。切分为10个 1600s->160s*10 也就是每个任务等待160s(这里的10个任务可以看作是开了10个线程)。如果切分为100个那么就是1600s->16s。当然这里是理想情况。。。随着任务的增多,内存 cpu效率都会有所下降,看自己的机器了。
还有最关键的一点!!!查看writer的数据库是否建立了索引!索引!索引!索引!索引!索引本身就是查询快,插入慢 这个是没法避免的
1.去除索引
2.presql :[ alter table index unusable,disable,enable]
postsql:[alter table index rebuild]
第二个我没试验过,百度的,具体自行实验。
最后附上导数较快的截图....希望大家都能这样。
标准的任务json
{
"job": {
"setting": {
"speed": {
"channel": 10
},
"errorLimit": {
"record": 0,
"percentage": 0.02
}
},
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": "root",
"password": "root",
"splitPk":"id"
"column": [
"id",
"name"
],
"where":"id>1"
"connection": [
{
"table": [
"table"
],
"jdbcUrl": [
"jdbc:mysql://127.0.0.1:3306/database"
]
}
]
}
},
"writer": {
"name": "oraclewriter",
"parameter": {
"batchSize":"2048",
"batchByteSize":"33554432",
"username": "root",
"password": "root",
"column": [
"id",
"name"
],
"preSql": [
"delete from test"
],
"connection": [
{
"jdbcUrl": "jdbc:oracle:thin:@[HOST_NAME]:PORT:[DATABASE_NAME]",
"table": [
"test"
]
}
]
}
}
}
]
}
}
2022-06-22更新
本着很多小伙伴点赞和收藏,再更新一波。之前的文章提到了 splitPk的方式。但是这个毕竟有局限性,字段需要是int,数据拆分的不均匀,提速不太明显。
现在介绍另外一种方式。querysql。这种方式适合oracle的分区表,或者mysql 有时间字段的表。
一般来说每天的数据量是差不多的。比如我这个表有1000w数据2022-01到2022-02 我可以写12个sql 从1月到12月 这样任务基本均匀分布,也不用像splitPk的那样对字段的要求较高。
如果是oracle的分区表的话可以尝试用table[ "table1 partition(p_202201)","table1 partition(p_202202)"....]因为我一时找不到oracle的分区表。但是感觉是可行的。
{
"job": {
"setting": {
"speed": {
"channel": 10
},
"errorLimit": {
"record": 0,
"percentage": 0.02
}
},
"content" : [ {
"reader" : {
"name" : "postgresqlreader",
"parameter" : {
"username" : "${username}",
"password" : "${password}",
"connection" : [ {
"querySql": [
"SELECT * FROM db.table1 where period_id='202101'",
"SELECT * FROM db.table1 where period_id='202102'",
"SELECT * FROM db.table1 where period_id='202103'",
"SELECT * FROM db.table1 where period_id='202104'",
"SELECT * FROM db.table1 where period_id='202105'"
],
"jdbcUrl" : [ "${jdbcUrl}" ]
}]
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS" : "hdfs://${defaultFS}",
"fileType": "orc",
"path": "/user/hive/warehouse/dwintdata.db/table1/",
"fileName": "table1",
"writeMode": "append",
"fieldDelimiter": "\u0001",
"haveKerberos":"true",
"kerberosKeytabFilePath":"${kerberosKeytabFilePath}",
"kerberosPrincipal":"${kerberosPrincipal}",
"hadoopConfig" : {
"dfs.client.failover.proxy.provider.${defaultFS}" : "org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider",
"dfs.ha.namenodes.${defaultFS}" : "nn1,nn2",
"dfs.namenode.rpc-address.${defaultFS}.nn2" : "${dfsnn2}",
"dfs.namenode.rpc-address.${defaultFS}.nn1" : "${dfsnn1}",
"dfs.nameservices" : "${defaultFS}"
},
"column": [
{"name": "name", "type": "string"},
{"name": "age", "type": "string"},
{"name": "period_id", "type": "string"}
]
}
}
}]
}
}
--------------------------------------------2023-02-23追加----------------------------------
依旧还是有些小伙伴,还是遇到了一些其他的问题。下面再补充下。
这个任务是把oracle的一个视图数据导入到hive。1000w数据花费了6h,慢的可怜。
2023-02-15 10:06:11.964 [job-0] INFO StandAloneJobContainerCommunicator - Total 0 records, 0 bytes | Speed 0B/s, 0 records/s | Error 0 records, 0 bytes | All Task WaitWriterTime 0.000s | All Task WaitReaderTime 0.000s | Percentage 0.00%
2023-02-15 10:11:42.035 [job-0] INFO StandAloneJobContainerCommunicator - Total 992 records, 366466 bytes | Speed 35.79KB/s, 99 records/s | Error 0 records, 0 bytes | All Task WaitWriterTime 0.001s | All Task WaitReaderTime 321.169s
2023-02-15 18:19:04.244 [job-0] INFO StandAloneJobContainerCommunicator - Total 10502112 records, 4729736400 bytes | Speed 86.15KB/s, 204 records/s | Error 0 records, 0 bytes | All Task WaitWriterTime 1.191s | All Task WaitReaderTime 29,106.971s
我们看日志发现,最后任务结束 All Task WaitWriterTime 1.191s | All Task WaitReaderTime 29,106.971s,总共花费时间29595s
按照我之前的说法,WaitReaderTime 时间过长而且是特别长,那么说明reader出现了问题。此时我们应该从哪里解决问题呢?
1.上文提到的fetchSize
2.视图过于复杂
3.数据库的源头问题
4.数据的网络传输
说明
1.可以提升reader速度 但是注意这个参数只是锦上添花。也就是说你设置最佳的速度,也只能将原先的速度提升个30-50%(个人经验)。那么上文的200rec/s 提升了也没卵用。
2.视图过于复杂,这个是有一定的可能的,我们查询视图的时候,视图的sql里往往又几个表join来join去,所以查询耗费一定的时间是可以存在的。
执行sql的开始时间 2023-02-15 10:06:11 开始查到数据的时间2023-02-15 10:11:42
差不多是耗费5min =300s 。你说这个任务如果是500s内完成,往这方面的优化还行,但是这个任务耗费接近30000s 这个查询时间只占了1%,所以这个可以忽略。
3. 我没有见过,但是根据别人的说法
4.之前有人问我,我当时也觉得是网络原因,后面他那边运维排查,好像是数据库地址用的外网地址速度很慢,内网地址就比较快了,或者是带宽 这种也有影响?(我对网络这块不太了解)
这里我再说下2. 有小伙伴遇到过这个 就是视图查询占了大半的时间,那么怎么解决?
1.视图的sql优化,加索引,explain分析
2.不采用视图作为readersql。 提前把视图的数据落地为表。(oracle 物化视图好像也行。。)
3.把视图sql的所有表全部拉取到hive,在hive端进行聚合。
如果这篇对你有帮助,点个赞是对我最大的鼓励。