From 80860c224d53d43016f4816aec8786e45a496117 Mon Sep 17 00:00:00 2001 From: daizihao Date: Tue, 13 Oct 2020 10:26:58 +0800 Subject: [PATCH 1/3] bug fix and write speed optimization --- kuduwriter/doc/image-20200901193148188.png | Bin 0 -> 40862 bytes kuduwriter/doc/kuduwirter.md | 143 ++++++++++++++ kuduwriter/src/main/assembly/package.xml | 8 +- .../writer/kudu11xwriter/Kudu11xHelper.java | 103 ++++++++-- .../writer/kudu11xwriter/Kudu11xWriter.java | 4 +- .../writer/kudu11xwriter/KuduWriterTask.java | 180 ++++++++++-------- kuduwriter/src/main/resources/plugin.json | 2 +- .../main/resources/plugin_job_template.json | 2 +- 8 files changed, 343 insertions(+), 99 deletions(-) create mode 100644 kuduwriter/doc/image-20200901193148188.png create mode 100644 kuduwriter/doc/kuduwirter.md diff --git a/kuduwriter/doc/image-20200901193148188.png b/kuduwriter/doc/image-20200901193148188.png new file mode 100644 index 0000000000000000000000000000000000000000..7e7b8a1fd7ce4e152089738a4254b1238a6c9331 GIT binary patch literal 40862 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 literal 0 HcmV?d00001 diff --git a/kuduwriter/doc/kuduwirter.md b/kuduwriter/doc/kuduwirter.md new file mode 100644 index 00000000..1a952449 --- /dev/null +++ b/kuduwriter/doc/kuduwirter.md @@ -0,0 +1,143 @@ +# datax-kudu-plugins +datax kudu的writer插件 + + + +eg: + +```json +{ + "name": "kuduwriter", + "parameter": { + "kuduConfig": { + "kudu.master_addresses": "***", + "timeout": 60000, + "sessionTimeout": 60000 + + }, + "table": "", + "replicaCount": 3, + "truncate": false, + "writeMode": "upsert", + "partition": { + "range": { + "column1": [ + { + "lower": "2020-08-25", + "upper": "2020-08-26" + }, + { + "lower": "2020-08-26", + "upper": "2020-08-27" + }, + { + "lower": "2020-08-27", + "upper": "2020-08-28" + } + ] + }, + "hash": { + "column": [ + "column1" + ], + "number": 3 + } + }, + "column": [ + { + "index": 0, + "name": "c1", + "type": "string", + "primaryKey": true + }, + { + "index": 1, + "name": "c2", + "type": "string", + "compress": "DEFAULT_COMPRESSION", + "encoding": "AUTO_ENCODING", + "comment": "注解xxxx" + } + ], + "batchSize": 1024, + "bufferSize": 2048, + "skipFail": false, + "encoding": "UTF-8" + } +} +``` + +必须参数: + +```json + "writer": { + "name": "kuduwriter", + "parameter": { + "kuduConfig": { + "kudu.master_addresses": "***" + }, + "table": "***", + "column": [ + { + "name": "c1", + "type": "string", + "primaryKey": true + }, + { + "name": "c2", + "type": "string", + }, + { + "name": "c3", + "type": "string" + }, + { + "name": "c4", + "type": "string" + } + ] + } + } +``` + +主键列请写到最前面 + + + +![image-20200901193148188](./image-20200901193148188.png) + +##### 配置列表 + +| name | default | description | 是否必须 | +| -------------- | ------------------- | ------------------------------------------------------------ | -------- | +| kuduConfig | | kudu配置 (kudu.master_addresses等) | 是 | +| table | | 导入目标表名 | 是 | +| partition | | 分区 | 否 | +| column | | 列 | 是 | +| name | | 列名 | 是 | +| type | string | 列的类型,现支持INT, FLOAT, STRING, BIGINT, DOUBLE, BOOLEAN, LONG。 | 否 | +| index | 升序排列 | 列索引位置(要么全部列都写,要么都不写),如reader中取到的某一字段在第二位置(eg: name, id, age)但kudu目标表结构不同(eg:id,name, age),此时就需要将index赋值为(1,0,2),默认顺序(0,1,2) | 否 | +| primaryKey | false | 是否为主键(请将所有的主键列写在前面),不表明主键将不会检查过滤脏数据 | 否 | +| compress | DEFAULT_COMPRESSION | 压缩格式 | 否 | +| encoding | AUTO_ENCODING | 编码 | 否 | +| replicaCount | 3 | 保留副本个数 | 否 | +| hash | | hash分区 | 否 | +| number | 3 | hash分区个数 | 否 | +| range | | range分区 | 否 | +| lower | | range分区下限 (eg: sql建表:partition value='haha' 对应:“lower”:“haha”,“upper”:“haha\000”) | 否 | +| upper | | range分区上限(eg: sql建表:partition "10" <= VALUES < "20" 对应:“lower”:“10”,“upper”:“20”) | 否 | +| truncate | false | 是否清空表,本质上是删表重建 | 否 | +| writeMode | upsert | upsert,insert,update | 否 | +| batchSize | 512 | 每xx行数据flush一次结果(最好不要超过1024) | 否 | +| bufferSize | 3072 | 缓冲区大小 | 否 | +| skipFail | false | 是否跳过插入不成功的数据 | 否 | +| timeout | 60000 | client超时时间,如创建表,删除表操作的超时时间。单位:ms | 否 | +| sessionTimeout | 60000 | session超时时间 单位:ms | 否 | + + + + + + + + diff --git a/kuduwriter/src/main/assembly/package.xml b/kuduwriter/src/main/assembly/package.xml index 5b1a10a7..c9497b92 100644 --- a/kuduwriter/src/main/assembly/package.xml +++ b/kuduwriter/src/main/assembly/package.xml @@ -14,21 +14,21 @@ plugin.json plugin_job_template.json - plugin/writer/kudu11xwriter + plugin/writer/kuduwriter target/ - kudu11xwriter-0.0.1-SNAPSHOT.jar + kuduwriter-0.0.1-SNAPSHOT.jar - plugin/writer/kudu11xwriter + plugin/writer/kuduwriter false - plugin/writer/kudu11xwriter/libs + plugin/writer/kuduwriter/libs runtime diff --git a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xHelper.java b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xHelper.java index 10568820..71686c22 100644 --- a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xHelper.java +++ b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xHelper.java @@ -10,11 +10,17 @@ import org.apache.kudu.ColumnSchema; import org.apache.kudu.Schema; import org.apache.kudu.Type; import org.apache.kudu.client.*; +import org.apache.kudu.shaded.org.checkerframework.checker.units.qual.K; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sun.rmi.runtime.Log; import java.nio.charset.Charset; import java.util.*; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** @@ -47,10 +53,10 @@ public class Kudu11xHelper { Map conf = Kudu11xHelper.getKuduConfiguration(kuduConfig); KuduClient kuduClient = null; try { - String masterAddress = (String)conf.get(Key.KUDU_MASTER); + String masterAddress = (String) conf.get(Key.KUDU_MASTER); kuduClient = new KuduClient.KuduClientBuilder(masterAddress) .defaultAdminOperationTimeoutMs((Long) conf.get(Key.KUDU_ADMIN_TIMEOUT)) - .defaultOperationTimeoutMs((Long)conf.get(Key.KUDU_SESSION_TIMEOUT)) + .defaultOperationTimeoutMs((Long) conf.get(Key.KUDU_SESSION_TIMEOUT)) .build(); } catch (Exception e) { throw DataXException.asDataXException(Kudu11xWriterErrorcode.GET_KUDU_CONNECTION_ERROR, e); @@ -106,17 +112,17 @@ public class Kudu11xHelper { throw DataXException.asDataXException(Kudu11xWriterErrorcode.GREATE_KUDU_TABLE_ERROR, e); } finally { AtomicInteger i = new AtomicInteger(5); - while (i.get()>0) { + while (i.get() > 0) { try { - if (kuduClient.isCreateTableDone(tableName)){ + if (kuduClient.isCreateTableDone(tableName)) { Kudu11xHelper.closeClient(kuduClient); - LOG.info("Table "+ tableName +" is created!"); + LOG.info("Table " + tableName + " is created!"); break; } i.decrementAndGet(); LOG.error("timeout!"); - } catch (KuduException e) { - LOG.info("Wait for the table to be created..... "+i); + } catch (KuduException e) { + LOG.info("Wait for the table to be created..... " + i); try { Thread.sleep(1000L); } catch (InterruptedException ex) { @@ -135,6 +141,44 @@ public class Kudu11xHelper { } } + public static ThreadPoolExecutor createRowAddThreadPool(int coreSize) { + return new ThreadPoolExecutor(coreSize, + coreSize, + 60L, + TimeUnit.SECONDS, + new SynchronousQueue(), + new ThreadFactory() { + private final ThreadGroup group = System.getSecurityManager() == null ? Thread.currentThread().getThreadGroup() : System.getSecurityManager().getThreadGroup(); + private final AtomicInteger threadNumber = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, + "pool-kudu_rows_add-thread-" + threadNumber.getAndIncrement(), + 0); + if (t.isDaemon()) + t.setDaemon(false); + if (t.getPriority() != Thread.NORM_PRIORITY) + t.setPriority(Thread.NORM_PRIORITY); + return t; + } + }, new ThreadPoolExecutor.CallerRunsPolicy()); + } + + public static List> getColumnLists(List columns) { + int quota = 8; + int num = (columns.size() - 1) / quota + 1; + int gap = columns.size() / num; + List> columnLists = new ArrayList<>(num); + for (int j = 0; j < num - 1; j++) { + List destList = new ArrayList<>(columns.subList(j * gap, (j + 1) * gap)); + columnLists.add(destList); + } + List destList = new ArrayList<>(columns.subList(gap * (num - 1), columns.size())); + columnLists.add(destList); + return columnLists; + } + public static boolean isTableExists(Configuration configuration) { String tableName = configuration.getString(Key.TABLE); String kuduConfig = configuration.getString(Key.KUDU_CONFIG); @@ -154,7 +198,7 @@ public class Kudu11xHelper { kuduClient.close(); } } catch (KuduException e) { - LOG.warn("kudu client is not gracefully closed !"); + LOG.warn("The \"kudu client\" was not stopped gracefully. !"); } @@ -172,8 +216,8 @@ public class Kudu11xHelper { String type = "BIGINT".equals(column.getNecessaryValue(Key.TYPE, Kudu11xWriterErrorcode.REQUIRED_VALUE).toUpperCase()) || "LONG".equals(column.getNecessaryValue(Key.TYPE, Kudu11xWriterErrorcode.REQUIRED_VALUE).toUpperCase()) ? - "INT64" : "INT".equals(column.getNecessaryValue(Key.TYPE, Kudu11xWriterErrorcode.REQUIRED_VALUE).toUpperCase())? - "INT32":column.getNecessaryValue(Key.TYPE, Kudu11xWriterErrorcode.REQUIRED_VALUE).toUpperCase(); + "INT64" : "INT".equals(column.getNecessaryValue(Key.TYPE, Kudu11xWriterErrorcode.REQUIRED_VALUE).toUpperCase()) ? + "INT32" : column.getNecessaryValue(Key.TYPE, Kudu11xWriterErrorcode.REQUIRED_VALUE).toUpperCase(); String name = column.getNecessaryValue(Key.NAME, Kudu11xWriterErrorcode.REQUIRED_VALUE); Boolean key = column.getBool(Key.PRIMARYKEY, false); String encoding = column.getString(Key.ENCODING, Constant.ENCODING).toUpperCase(); @@ -194,9 +238,9 @@ public class Kudu11xHelper { return schema; } - public static Integer getPrimaryKeyIndexUntil(List columns){ + public static Integer getPrimaryKeyIndexUntil(List columns) { int i = 0; - while ( i < columns.size() ) { + while (i < columns.size()) { Configuration col = columns.get(i); if (!col.getBool(Key.PRIMARYKEY, false)) { break; @@ -244,6 +288,7 @@ public class Kudu11xHelper { } public static void validateParameter(Configuration configuration) { + LOG.info("Start validating parameters!"); configuration.getNecessaryValue(Key.KUDU_CONFIG, Kudu11xWriterErrorcode.REQUIRED_VALUE); configuration.getNecessaryValue(Key.TABLE, Kudu11xWriterErrorcode.REQUIRED_VALUE); String encoding = configuration.getString(Key.ENCODING, Constant.DEFAULT_ENCODING); @@ -268,7 +313,39 @@ public class Kudu11xHelper { Boolean isSkipFail = configuration.getBool(Key.SKIP_FAIL, false); configuration.set(Key.SKIP_FAIL, isSkipFail); - LOG.info("==validate parameter complete!"); + List columns = configuration.getListConfiguration(Key.COLUMN); + List goalColumns = new ArrayList<>(); + //column参数验证 + int indexFlag = 0; + boolean primaryKey = true; + int primaryKeyFlag = 0; + for (int i = 0; i < columns.size(); i++) { + Configuration col = columns.get(i); + String index = col.getString(Key.INDEX); + if (index == null) { + index = String.valueOf(i); + col.set(Key.INDEX, index); + indexFlag++; + } + if(primaryKey != col.getBool(Key.PRIMARYKEY, false)){ + primaryKey = col.getBool(Key.PRIMARYKEY, false); + primaryKeyFlag++; + } + goalColumns.add(col); + } + if (indexFlag != 0 && indexFlag != columns.size()) { + throw DataXException.asDataXException(Kudu11xWriterErrorcode.ILLEGAL_VALUE, + "\"index\" either has values for all of them, or all of them are null!"); + } + if (primaryKeyFlag > 1){ + throw DataXException.asDataXException(Kudu11xWriterErrorcode.ILLEGAL_VALUE, + "\"primaryKey\" must be written in the front!"); + } + configuration.set(Key.COLUMN, goalColumns); +// LOG.info("------------------------------------"); +// LOG.info(configuration.toString()); +// LOG.info("------------------------------------"); + LOG.info("validate parameter complete!"); } public static void truncateTable(Configuration configuration) { diff --git a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xWriter.java b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xWriter.java index 9447a6c2..83620f43 100644 --- a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xWriter.java +++ b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xWriter.java @@ -38,7 +38,7 @@ public class Kudu11xWriter extends Writer { @Override public List split(int i) { - List splitResultConfigs = new ArrayList(); + List splitResultConfigs = new ArrayList<>(); for (int j = 0; j < i; j++) { splitResultConfigs.add(config.clone()); } @@ -76,7 +76,7 @@ public class Kudu11xWriter extends Writer { kuduTaskProxy.session.close(); } }catch (Exception e){ - LOG.warn("kudu session is not gracefully closed !"); + LOG.warn("The \"kudu session\" was not stopped gracefully !"); } Kudu11xHelper.closeClient(kuduTaskProxy.kuduClient); diff --git a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/KuduWriterTask.java b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/KuduWriterTask.java index 127ee0c1..c4b90b8c 100644 --- a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/KuduWriterTask.java +++ b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/KuduWriterTask.java @@ -12,12 +12,13 @@ import org.apache.kudu.client.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; /** * @author daizihao @@ -26,28 +27,30 @@ import java.util.concurrent.atomic.AtomicLong; public class KuduWriterTask { private final static Logger LOG = LoggerFactory.getLogger(KuduWriterTask.class); - public List columns; - public String encoding; - public String insertMode; - public Double batchSize; - public long mutationBufferSpace; - public Boolean isUpsert; - public Boolean isSkipFail; - + private List columns; + private List> columnLists; + private ThreadPoolExecutor pool; + private String encoding; + private Double batchSize; + private Boolean isUpsert; + private Boolean isSkipFail; public KuduClient kuduClient; - public KuduTable table; public KuduSession session; + private KuduTable table; private Integer primaryKeyIndexUntil; + private final Object lock = new Object(); public KuduWriterTask(Configuration configuration) { - this.columns = configuration.getListConfiguration(Key.COLUMN); + columns = configuration.getListConfiguration(Key.COLUMN); + columnLists = Kudu11xHelper.getColumnLists(columns); + pool = Kudu11xHelper.createRowAddThreadPool(columnLists.size()); + this.encoding = configuration.getString(Key.ENCODING); - this.insertMode = configuration.getString(Key.INSERT_MODE); this.batchSize = configuration.getDouble(Key.WRITE_BATCH_SIZE); - this.mutationBufferSpace = configuration.getLong(Key.MUTATION_BUFFER_SPACE); - this.isUpsert = !configuration.getString(Key.INSERT_MODE).equals("insert"); + this.isUpsert = !configuration.getString(Key.INSERT_MODE).equalsIgnoreCase("insert"); this.isSkipFail = configuration.getBool(Key.SKIP_FAIL); + long mutationBufferSpace = configuration.getLong(Key.MUTATION_BUFFER_SPACE); this.kuduClient = Kudu11xHelper.getKuduClient(configuration.getString(Key.KUDU_CONFIG)); this.table = Kudu11xHelper.getKuduTable(configuration, kuduClient); @@ -59,9 +62,9 @@ public class KuduWriterTask { } public void startWriter(RecordReceiver lineReceiver, TaskPluginCollector taskPluginCollector) { - LOG.info("==kuduwriter began to write!"); + LOG.info("kuduwriter began to write!"); Record record; - AtomicLong counter = new AtomicLong(0L); + LongAdder counter = new LongAdder(); try { while ((record = lineReceiver.getFromReader()) != null) { if (record.getColumnNumber() != columns.size()) { @@ -70,7 +73,7 @@ public class KuduWriterTask { boolean isDirtyRecord = false; - for (int i = 0; i <= primaryKeyIndexUntil && !isDirtyRecord; i++) { + for (int i = 0; i < primaryKeyIndexUntil && !isDirtyRecord; i++) { Column column = record.getColumn(i); isDirtyRecord = StringUtils.isBlank(column.asString()); } @@ -80,51 +83,74 @@ public class KuduWriterTask { continue; } + CountDownLatch countDownLatch = new CountDownLatch(columnLists.size()); Upsert upsert = table.newUpsert(); Insert insert = table.newInsert(); - - for (int i = 0; i < columns.size(); i++) { - PartialRow row; - if (isUpsert) { - //覆盖更新 - row = upsert.getRow(); - } else { - //增量更新 - row = insert.getRow(); - } - Configuration col = columns.get(i); - String name = col.getString(Key.NAME); - ColumnType type = ColumnType.getByTypeName(col.getString(Key.TYPE)); - Column column = record.getColumn(col.getInt(Key.INDEX, i)); - Object rawData = column.getRawData(); - if (rawData == null) { - row.setNull(name); - continue; - } - switch (type) { - case INT: - row.addInt(name, Integer.parseInt(rawData.toString())); - break; - case LONG: - case BIGINT: - row.addLong(name, Long.parseLong(rawData.toString())); - break; - case FLOAT: - row.addFloat(name, Float.parseFloat(rawData.toString())); - break; - case DOUBLE: - row.addDouble(name, Double.parseDouble(rawData.toString())); - break; - case BOOLEAN: - row.addBoolean(name, Boolean.getBoolean(rawData.toString())); - break; - case STRING: - default: - row.addString(name, rawData.toString()); - } + PartialRow row; + if (isUpsert) { + //覆盖更新 + row = upsert.getRow(); + } else { + //增量更新 + row = insert.getRow(); } + + for (List columnList : columnLists) { + Record finalRecord = record; + pool.submit(()->{ + + for (Configuration col : columnList) { + + String name = col.getString(Key.NAME); + ColumnType type = ColumnType.getByTypeName(col.getString(Key.TYPE, "string")); + Column column = finalRecord.getColumn(col.getInt(Key.INDEX)); + String rawData = column.asString(); + if (rawData == null) { + synchronized (lock) { + row.setNull(name); + } + continue; + } + switch (type) { + case INT: + synchronized (lock) { + row.addInt(name, Integer.parseInt(rawData)); + } + break; + case LONG: + case BIGINT: + synchronized (lock) { + row.addLong(name, Long.parseLong(rawData)); + } + break; + case FLOAT: + synchronized (lock) { + row.addFloat(name, Float.parseFloat(rawData)); + } + break; + case DOUBLE: + synchronized (lock) { + row.addDouble(name, Double.parseDouble(rawData)); + } + break; + case BOOLEAN: + synchronized (lock) { + row.addBoolean(name, Boolean.getBoolean(rawData)); + } + break; + case STRING: + default: + synchronized (lock) { + row.addString(name, rawData); + } + } + } + countDownLatch.countDown(); + }); + } + countDownLatch.await(); try { - RetryUtil.executeWithRetry(()->{ + RetryUtil.executeWithRetry(() -> { if (isUpsert) { //覆盖更新 session.apply(upsert); @@ -132,26 +158,27 @@ public class KuduWriterTask { //增量更新 session.apply(insert); } - //提前写数据,阈值可自定义 - if (counter.incrementAndGet() > batchSize * 0.75) { + //flush + if (counter.longValue() > (batchSize * 0.8)) { session.flush(); - counter.set(0L); + counter.reset(); } + counter.increment(); return true; - },5,1000L,true); + }, 5, 500L, true); } catch (Exception e) { - LOG.error("Data write failed!", e); + LOG.error("Record Write Failure!", e); if (isSkipFail) { - LOG.warn("Because you have configured skipFail is true,this data will be skipped!"); + LOG.warn("Since you have configured \"skipFail\" to be true, this record will be skipped !"); taskPluginCollector.collectDirtyRecord(record, e.getMessage()); - }else { + } else { throw e; } } } } catch (Exception e) { - LOG.error("write failed! the task will exit!"); + LOG.error("write failure! the task will exit!"); throw DataXException.asDataXException(Kudu11xWriterErrorcode.PUT_KUDU_ERROR, e.getMessage()); } AtomicInteger i = new AtomicInteger(10); @@ -161,23 +188,20 @@ public class KuduWriterTask { session.flush(); break; } - Thread.sleep(1000L); + Thread.sleep(20L); i.decrementAndGet(); } } catch (Exception e) { - LOG.info("Waiting for data to be inserted...... " + i + "s"); - try { - Thread.sleep(1000L); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } - i.decrementAndGet(); + LOG.info("Waiting for data to be written to kudu...... " + i + "s"); + } finally { try { + pool.shutdown(); + //强制刷写 session.flush(); } catch (KuduException e) { - LOG.error("==kuduwriter flush error! the results may not be complete!"); - e.printStackTrace(); + LOG.error("kuduwriter flush error! The results may be incomplete!"); + throw DataXException.asDataXException(Kudu11xWriterErrorcode.PUT_KUDU_ERROR, e.getMessage()); } } diff --git a/kuduwriter/src/main/resources/plugin.json b/kuduwriter/src/main/resources/plugin.json index 948c7e22..f60dc825 100644 --- a/kuduwriter/src/main/resources/plugin.json +++ b/kuduwriter/src/main/resources/plugin.json @@ -1,5 +1,5 @@ { - "name": "kudu11xwriter", + "name": "kuduwriter", "class": "com.q1.datax.plugin.writer.kudu11xwriter.Kudu11xWriter", "description": "use put: prod. mechanism: use kudu java api put data.", "developer": "com.q1.daizihao" diff --git a/kuduwriter/src/main/resources/plugin_job_template.json b/kuduwriter/src/main/resources/plugin_job_template.json index d2723098..3edc6c39 100644 --- a/kuduwriter/src/main/resources/plugin_job_template.json +++ b/kuduwriter/src/main/resources/plugin_job_template.json @@ -1,5 +1,5 @@ { - "name": "kudu11xwriter", + "name": "kuduwriter", "parameter": { "kuduConfig": { "kudu.master_addresses": "***", From 00d8e9783df1fd3337cfccd1c23a45cadfbadae2 Mon Sep 17 00:00:00 2001 From: daizihao Date: Tue, 13 Oct 2020 20:23:27 +0800 Subject: [PATCH 2/3] bug fix and write speed optimization --- .../writer/kudu11xwriter/Kudu11xHelper.java | 4 +- .../writer/kudu11xwriter/KuduWriterTask.java | 101 +++++++++--------- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xHelper.java b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xHelper.java index 71686c22..cf1b0f8f 100644 --- a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xHelper.java +++ b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/Kudu11xHelper.java @@ -111,7 +111,7 @@ public class Kudu11xHelper { } catch (Exception e) { throw DataXException.asDataXException(Kudu11xWriterErrorcode.GREATE_KUDU_TABLE_ERROR, e); } finally { - AtomicInteger i = new AtomicInteger(5); + AtomicInteger i = new AtomicInteger(10); while (i.get() > 0) { try { if (kuduClient.isCreateTableDone(tableName)) { @@ -124,7 +124,7 @@ public class Kudu11xHelper { } catch (KuduException e) { LOG.info("Wait for the table to be created..... " + i); try { - Thread.sleep(1000L); + Thread.sleep(100L); } catch (InterruptedException ex) { ex.printStackTrace(); } diff --git a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/KuduWriterTask.java b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/KuduWriterTask.java index c4b90b8c..bff3509f 100644 --- a/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/KuduWriterTask.java +++ b/kuduwriter/src/main/java/com/q1/datax/plugin/writer/kudu11xwriter/KuduWriterTask.java @@ -94,61 +94,66 @@ public class KuduWriterTask { //增量更新 row = insert.getRow(); } - + List> futures = new ArrayList<>(); for (List columnList : columnLists) { Record finalRecord = record; - pool.submit(()->{ - - for (Configuration col : columnList) { - - String name = col.getString(Key.NAME); - ColumnType type = ColumnType.getByTypeName(col.getString(Key.TYPE, "string")); - Column column = finalRecord.getColumn(col.getInt(Key.INDEX)); - String rawData = column.asString(); - if (rawData == null) { - synchronized (lock) { - row.setNull(name); + Future future = pool.submit(() -> { + try { + for (Configuration col : columnList) { + String name = col.getString(Key.NAME); + ColumnType type = ColumnType.getByTypeName(col.getString(Key.TYPE, "string")); + Column column = finalRecord.getColumn(col.getInt(Key.INDEX)); + String rawData = column.asString(); + if (rawData == null) { + synchronized (lock) { + row.setNull(name); + } + continue; + } + switch (type) { + case INT: + synchronized (lock) { + row.addInt(name, Integer.parseInt(rawData)); + } + break; + case LONG: + case BIGINT: + synchronized (lock) { + row.addLong(name, Long.parseLong(rawData)); + } + break; + case FLOAT: + synchronized (lock) { + row.addFloat(name, Float.parseFloat(rawData)); + } + break; + case DOUBLE: + synchronized (lock) { + row.addDouble(name, Double.parseDouble(rawData)); + } + break; + case BOOLEAN: + synchronized (lock) { + row.addBoolean(name, Boolean.getBoolean(rawData)); + } + break; + case STRING: + default: + synchronized (lock) { + row.addString(name, rawData); + } } - continue; - } - switch (type) { - case INT: - synchronized (lock) { - row.addInt(name, Integer.parseInt(rawData)); - } - break; - case LONG: - case BIGINT: - synchronized (lock) { - row.addLong(name, Long.parseLong(rawData)); - } - break; - case FLOAT: - synchronized (lock) { - row.addFloat(name, Float.parseFloat(rawData)); - } - break; - case DOUBLE: - synchronized (lock) { - row.addDouble(name, Double.parseDouble(rawData)); - } - break; - case BOOLEAN: - synchronized (lock) { - row.addBoolean(name, Boolean.getBoolean(rawData)); - } - break; - case STRING: - default: - synchronized (lock) { - row.addString(name, rawData); - } } + } finally { + countDownLatch.countDown(); } - countDownLatch.countDown(); }); + futures.add(future); } countDownLatch.await(); + for (Future future : futures) { + future.get(); + } try { RetryUtil.executeWithRetry(() -> { if (isUpsert) { @@ -173,7 +178,7 @@ public class KuduWriterTask { LOG.warn("Since you have configured \"skipFail\" to be true, this record will be skipped !"); taskPluginCollector.collectDirtyRecord(record, e.getMessage()); } else { - throw e; + throw DataXException.asDataXException(Kudu11xWriterErrorcode.PUT_KUDU_ERROR, e.getMessage()); } } } From 544debd0556dda888d7eb618247b687e854c520f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A9=E7=BB=8D=E9=94=A6?= Date: Tue, 1 Dec 2020 11:15:43 +0800 Subject: [PATCH 3/3] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 94c40566..a8f5672d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ ![Datax-logo](https://github.com/alibaba/DataX/blob/master/images/DataX-logo.jpg) - # DataX DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL、Oracle、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、DRDS 等各种异构数据源之间高效的数据同步功能。 +# DataX 商业版本 +阿里云DataWorks数据集成是DataX团队在阿里云上的商业化产品,致力于提供复杂网络环境下、丰富的异构数据源之间高速稳定的数据移动能力,以及繁杂业务背景下的数据同步解决方案。目前已经支持云上近3000家客户,单日同步数据超过3万亿条。DataWorks数据集成目前支持离线50+种数据源,可以进行整库迁移、批量上云、增量同步、分库分表等各类同步解决方案。2020年更新实时同步能力,2020年更新实时同步能力,支持10+种数据源的读写任意组合。提供MySQL,Oracle等多种数据源到阿里云MaxCompute,Hologres等大数据引擎的一键全增量同步解决方案。 + +https://www.aliyun.com/product/bigdata/ide # Features