Kudu+Impala很适合数据分析, 但直接使用Insert values语句往Kudu表插入数据, 效率实在不好, 测试下来insert的速度仅为80笔/秒. 原因也是显然的, Kudu本身写入效率很高, 但是Impala并没有做这方面优化, 观察下来每次Impala语句执行的overhead都太大了, 导致频繁小批次写入效率非常差, Kudu官方推荐使用Java API或Python API完成数据写入工作. 下面是使用Java API的测试用例, 也可以看出Kudu API的大致用法.

=========================
准备测试Table
=========================

-- kudu table
CREATE TABLE kudu_testdb.tmp_test_perf
(
id string ENCODING PLAIN_ENCODING COMPRESSION SNAPPY,
int_value int ,
bigint_value bigint ,
timestamp_value timestamp ,
boolean_value int,
PRIMARY KEY (id)
)
PARTITION BY HASH (id) PARTITIONS 6
STORED AS KUDU
TBLPROPERTIES (
'kudu.table_name' = 'testdb.tmp_test_perf',
'kudu.master_addresses' = '10.0.0.100:7051,10.0.0.101:7051,10.0.0.101:7051',
'kudu.num_tablet_replicas' = '1'
)
;

=========================
编写测试java程序
=========================
Kudu API 编码注意事项:

1. 尽管建表Impala DDL中,kudu表字段名大小写不敏感, 但在kudu层面, 字段名称已经转成为小写形式, 在Kudu API中, 字段名称必须是小写字母.
2. 建表Impala DDL表名称大小写会被完整地保留下来, 并没有被转成小写, 而且在Kudu API使用中, 表名是大小写敏感的, 必须和建表DDL完全一致.
3. Kudu API给字段赋值函数是不接受传入null, 所以如果在为字段赋值之前, 最好先判断一下取值是否为null. 例如下面两行代码会报错.

Long longTmp=null;
row.addLong("bigint_value",longTmp);

 
package kudu_perf_test;

import java.sql.Timestamp;
import java.util.UUID;
import org.apache.kudu.client.*; public class Test {
private final static int OPERATION_BATCH = 500; //同时支持三个模式的测试用例
public static void insertTestGeneric(KuduSession session, KuduTable table, SessionConfiguration.FlushMode mode,
int recordCount) throws Exception {
// SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND
// SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC
// SessionConfiguration.FlushMode.MANUAL_FLUSH
session.setFlushMode(mode);
if (SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC != mode) {
session.setMutationBufferSpace(OPERATION_BATCH);
}
int uncommit = 0; for (int i = 0; i < recordCount; i++) {
Insert insert = table.newInsert();
PartialRow row = insert.getRow();
UUID uuid = UUID.randomUUID();
row.addString("id", uuid.toString());
row.addInt("int_value", 100);
row.addLong("bigint_value", 10000L); Long gtmMillis;
/* System.currentTimeMillis() 是从1970-01-01开始算的毫秒数(GMT), kudu API是采用纳秒数, 所以需要*1000
另外, 考虑到我们是东8区时间, 所以转成Long型需要再加8个小时, 否则存到Kudu的时间是GTM, 比东8区晚8个小时
*/ //方法1: 获取当前时间对应的GTM时区unix毫秒数
gtmMillis=System.currentTimeMillis(); //方法2: 将timestamp转成对应的GTM时区unix毫秒数
Timestamp localTimestamp = new Timestamp(System.currentTimeMillis());
gtmMillis=localTimestamp.getTime(); //将GTM的毫秒数转成东8区的毫秒数量
Long shanghaiTimezoneMillis=gtmMillis+8*3600*1000;
row.addLong("timestamp_value", shanghaiTimezoneMillis*1000); session.apply(insert); // 对于手工提交, 需要buffer在未满的时候flush,这里采用了buffer一半时即提交
if (SessionConfiguration.FlushMode.MANUAL_FLUSH == mode) {
uncommit = uncommit + 1;
if (uncommit > OPERATION_BATCH / 2) {
session.flush();
uncommit = 0;
}
}
} // 对于手工提交, 保证完成最后的提交
if (SessionConfiguration.FlushMode.MANUAL_FLUSH == mode && uncommit > 0) {
session.flush();
} // 对于后台自动提交, 必须保证完成最后的提交, 并保证有错误时能抛出异常
if (SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND == mode) {
session.flush();
RowErrorsAndOverflowStatus error = session.getPendingErrors();
if (error.isOverflowed() || error.getRowErrors().length > 0) {
if (error.isOverflowed()) {
throw new Exception("Kudu overflow exception occurred.");
}
StringBuilder errorMessage = new StringBuilder();
if (error.getRowErrors().length > 0) {
for (RowError errorObj : error.getRowErrors()) {
errorMessage.append(errorObj.toString());
errorMessage.append(";");
}
}
throw new Exception(errorMessage.toString());
}
} } //仅支持手动flush的测试用例
public static void insertTestManual(KuduSession session, KuduTable table, int recordCount) throws Exception {
// SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND
// SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC
// SessionConfiguration.FlushMode.MANUAL_FLUSH
SessionConfiguration.FlushMode mode = SessionConfiguration.FlushMode.MANUAL_FLUSH;
session.setFlushMode(mode);
session.setMutationBufferSpace(OPERATION_BATCH); int uncommit = 0;
for (int i = 0; i < recordCount; i++) {
Insert insert = table.newInsert();
PartialRow row = insert.getRow();
UUID uuid = UUID.randomUUID();
row.addString("id", uuid.toString());
row.addInt("int_value", 100);
row.addLong("bigint_value", 10000L); Long gtmMillis;
/* System.currentTimeMillis() 是从1970-01-01开始算的毫秒数(GMT), kudu API是采用纳秒数, 所以需要*1000
另外, 考虑到我们是东8区时间, 所以转成Long型需要再加8个小时, 否则存到Kudu的时间是GTM, 比东8区晚8个小时
*/ //方法1: 获取当前时间对应的GTM时区unix毫秒数
gtmMillis=System.currentTimeMillis(); //方法2: 将timestamp转成对应的GTM时区unix毫秒数
Timestamp localTimestamp = new Timestamp(System.currentTimeMillis());
gtmMillis=localTimestamp.getTime(); //将GTM的毫秒数转成东8区的毫秒数量
Long shanghaiTimezoneMillis=gtmMillis+8*3600*1000;
row.addLong("timestamp_value", shanghaiTimezoneMillis*1000); session.apply(insert); // 对于手工提交, 需要buffer在未满的时候flush,这里采用了buffer一半时即提交
uncommit = uncommit + 1;
if (uncommit > OPERATION_BATCH / 2) {
session.flush();
uncommit = 0;
}
} // 对于手工提交, 保证完成最后的提交
if (uncommit > 0) {
session.flush();
}
} //仅支持自动flush的测试用例
public static void insertTestInAutoSync(KuduSession session, KuduTable table, int recordCount) throws Exception {
// SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND
// SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC
// SessionConfiguration.FlushMode.MANUAL_FLUSH
SessionConfiguration.FlushMode mode = SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC;
session.setFlushMode(mode); for (int i = 0; i < recordCount; i++) {
Insert insert = table.newInsert();
PartialRow row = insert.getRow();
UUID uuid = UUID.randomUUID();
row.addString("id", uuid.toString());
row.addInt("int_value", 100);
row.addLong("bigint_value", 10000L); Long gtmMillis;
/* System.currentTimeMillis() 是从1970-01-01开始算的毫秒数(GMT), kudu API是采用纳秒数, 所以需要*1000
另外, 考虑到我们是东8区时间, 所以转成Long型需要再加8个小时, 否则存到Kudu的时间是GTM, 比东8区晚8个小时
*/ //方法1: 获取当前时间对应的GTM时区unix毫秒数
gtmMillis=System.currentTimeMillis(); //方法2: 将timestamp转成对应的GTM时区unix毫秒数
Timestamp localTimestamp = new Timestamp(System.currentTimeMillis());
gtmMillis=localTimestamp.getTime(); //将GTM的毫秒数转成东8区的毫秒数量
Long shanghaiTimezoneMillis=gtmMillis+8*3600*1000;
row.addLong("timestamp_value", shanghaiTimezoneMillis*1000); //对于AUTO_FLUSH_SYNC模式, apply()将立即完成kudu写入
session.apply(insert);
}
} public static void test() throws KuduException {
KuduClient client = new KuduClient.KuduClientBuilder("10.0.0.100:7051,10.0.0.101:7051,10.0.0.101:7051")
.build();
KuduSession session = client.newSession();
KuduTable table = client.openTable("testdb.tmp_test_perf"); SessionConfiguration.FlushMode mode;
Timestamp d1 = null;
Timestamp d2 = null;
long millis;
long seconds;
int recordCount = 0; try {
mode = SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND;
d1 = new Timestamp(System.currentTimeMillis());
insertTestGeneric(session, table, mode, recordCount);
d2 = new Timestamp(System.currentTimeMillis());
millis = d2.getTime() - d1.getTime();
seconds = millis / 1000 % 60;
System.out.println(mode.name() + "耗时秒数:" + seconds); mode = SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC;
d1 = new Timestamp(System.currentTimeMillis());
insertTestInAutoSync(session, table, recordCount);
d2 = new Timestamp(System.currentTimeMillis());
millis = d2.getTime() - d1.getTime();
seconds = millis / 1000 % 60;
System.out.println(mode.name() + "耗时秒数:" + seconds); mode = SessionConfiguration.FlushMode.MANUAL_FLUSH;
d1 = new Timestamp(System.currentTimeMillis());
insertTestManual(session, table, recordCount);
d2 = new Timestamp(System.currentTimeMillis());
millis = d2.getTime() - d1.getTime();
seconds = millis / 1000 % 60;
System.out.println(mode.name() + "耗时秒数:" + seconds); } catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (!session.isClosed()) {
session.close();
}
} } public static void main(String[] args) {
try {
test();
} catch (KuduException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Done"); }
}

=========================
性能测试结果
=========================
MANUAL_FLUSH 模式:8000 row/second
AUTO_FLUSH_BACKGROUND 模式:8000 row/second
AUTO_FLUSH_SYNC 模式:1000 row/second
Impala SQL Insert 语句:80 row/second

=========================
Kudu API 使用总结
=========================
1. 尽量采用 MANUAL_FLUSH, 性能最好, 如果有写入kudu错误, flush()函数就会抛出异常, 逻辑非常清晰.
2. 在性能要求不高的情况下, AUTO_FLUSH_SYNC 也是一个好的选择.
3. 仅仅在demo场景下使用 AUTO_FLUSH_BACKGROUND, 不考虑异常处理时候代码可以很简单, 性能也很好. 在生产环境下, 不推荐的 原因是: 插入数据可能会是乱序, 一旦考虑捕获异常代码就很拖沓.

kudu系列: Java API使用和效率测试的更多相关文章

  1. HBase 系列(六)——HBase Java API 的基本使用

    一.简述 截至到目前 (2019.04),HBase 有两个主要的版本,分别是 1.x 和 2.x ,两个版本的 Java API 有所不同,1.x 中某些方法在 2.x 中被标识为 @depreca ...

  2. ElasticSearch实战系列三: ElasticSearch的JAVA API使用教程

    前言 在上一篇中介绍了ElasticSearch实战系列二: ElasticSearch的DSL语句使用教程---图文详解,本篇文章就来讲解下 ElasticSearch 6.x官方Java API的 ...

  3. kafka2.9.2的伪分布式集群安装和demo(java api)测试

    目录: 一.什么是kafka? 二.kafka的官方网站在哪里? 三.在哪里下载?需要哪些组件的支持? 四.如何安装? 五.FAQ 六.扩展阅读   一.什么是kafka? kafka是LinkedI ...

  4. ubuntu12.04+kafka2.9.2+zookeeper3.4.5的伪分布式集群安装和demo(java api)测试

    博文作者:迦壹 博客地址:http://idoall.org/home.php?mod=space&uid=1&do=blog&id=547 转载声明:可以转载, 但必须以超链 ...

  5. 5 weekend01、02、03、04、05、06、07的分布式集群的HA测试 + hdfs--动态增加节点和副本数量管理 + HA的java api访问要点

    weekend01.02.03.04.05.06.07的分布式集群的HA测试 1)  weekend01.02的hdfs的HA测试 2)  weekend03.04的yarn的HA测试 1)  wee ...

  6. [测试]java IO写入文件效率——几种方法比较

    各类写入方法 /** *1 按字节写入 FileOutputStream * * @param count 写入循环次数 * @param str 写入字符串 */ public void outpu ...

  7. Hadoop 系列(三)Java API

    Hadoop 系列(三)Java API <dependency> <groupId>org.apache.hadoop</groupId> <artifac ...

  8. Hadoop 系列(七)—— HDFS Java API

    一. 简介 想要使用 HDFS API,需要导入依赖 hadoop-client.如果是 CDH 版本的 Hadoop,还需要额外指明其仓库地址: <?xml version="1.0 ...

  9. SuperMap iServer 扩展/JAVA API 系列博客整理

    转载:http://blog.csdn.net/supermapsupport/article/details/70158940 SuperMap iServer为广大用户提供了整套 SDK,应用开发 ...

随机推荐

  1. Angular、React.js 和Node.js到底选谁?

    为了工作,程序员选择正确的框架和库来构建应用程序是至关重要的,这也就是为什么Angular和React之间有着太多的争议.Node.js的出现,让这场战争变得更加复杂,虽然有选择权通常是一件很棒的事情 ...

  2. Mybatis Generator的model生成中文注释,支持oracle和mysql(通过修改源码的方式来实现)

    在看本篇之前,最好先看一下上一篇通过实现CommentGenerator接口的方法来实现中文注释的例子,因为很多操作和上一篇基本是一致的,所以本篇可能不那么详细. 首先说一下上篇通过实现Comment ...

  3. 【BZOJ5213】[ZJOI2018]迷宫(神仙题)

    [BZOJ5213][ZJOI2018]迷宫(神仙题) 题面 BZOJ 洛谷 题解 首先可以很容易的得到一个\(K\)个点的答案. 构建\(K\)个点分别表示\(mod\ K\)的余数.那么点\(i\ ...

  4. 「洛谷5290」「LOJ3052」「十二省联考 2019」春节十二响【启发式合并】

    题目链接 [洛谷传送门] [LOJ传送门] 题目大意 给定一棵树,每次选取树上的一个点集,要求点集中的每个点不能是另一个点的祖先,选出点集的代价为点集中权值最大点的权值,问将所有点都选一遍的最小代价为 ...

  5. VSCode设置Tab键为4个空格

    升级之后莫名蛋疼,Tab键变成了8个,每次缩进之后都要格式化一下,比较麻烦,所以来一篇设置: GIF演示整个过程 分步骤走: 设置一下 设置为4个空格 最后多一句嘴,Python3开始官方不建议使用制 ...

  6. 全文检索 -- Solr从概念到实战(一)

    全文检索: 将整个文本进行“分词”处理,在索引库中为分词得到的每一个词都建立索引,和用户搜索的关键词进行匹配.实现快速查找效果. 传统sql语句实现的局限性: select song_id,song_ ...

  7. A1018. Public Bike Management

    There is a public bike service in Hangzhou City which provides great convenience to the tourists fro ...

  8. MyQR库自动为网址生成二维码

    首先安装MyQR库: pip install MyQR #导包 from MyQR import myqr #生成二维码 words=你要为那个网址生成二维码 save_name=保存后的图片名 pi ...

  9. Day29--Python--缓冲区, 粘包

    tcp: 属于长连接,与一个客户端进行连接了以后,其他的客户端要等待.要想连接另外一个客户端,需要优雅地断开当前客户端的连接 允许地址重用:server.setsockopt(socket.SOL_S ...

  10. postman基于webservice的请求

    以  http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?op=getMobileCodeInfo   为例 1.先理解事例的内容,请求头和响应 ...