利用多线程批Put方式压测HBase
背景
在正式上生产之前,一定要对集群的组件做稳定性和性能压测,这是常识。这种压测当然不能指望那些只会鼠标点几下网页并经常指责前端页面样式有bug的测试去做。。。这种稍微有点技术含量的事情,她们其实有心无力。。。
众所周知,表的列用百万为基本单位,行号用10亿为基本单位计数时,才配得上用HBase。。。所以HBase的实际应用场景大部分是大宽表【SQL Boy们手写SQL的那种几百个字段的Hive表叫宽表没啥问题,但是不够资格叫大宽表】。做性能压测绝对不能只用几十个字段或者几百w行数据,这种RDBMS都能轻松应付的场景不配用HBase。。。要做稳定性压测,一定是以压爆HBase集群为目的的,正式上线前压爆了去做一些jvm调优,总比上了生产环境天天爆库强一点。。。
当然有时候不一定是为了压爆集群,还可能是对HBase的协处理器做一些技术调研,例如:RSGroup硬件级资源隔离。这种情况更多是观察在大数据量场景下,协处理器是否会出错。
Demo
上一个简单的Demo。读者可以参照着改参数适应自己公司的场景。
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zhiyong_study</artifactId>
<groupId>com.zhiyong</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hbase_study</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<hbase-client.version>2.1.10</hbase-client.version>
<hbase.version>2.1.10</hbase.version>
<hbase-common.version>2.1.10</hbase-common.version>
<hbase-server.version>2.1.10</hbase-server.version>
<lombok-version>1.18.24</lombok-version>
<encoding>UTF-8</encoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>${hbase-client.version}</version>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 损坏-->
<!-- <dependency>-->
<!-- <groupId>org.apache.hbase</groupId>-->
<!-- <artifactId>hbase</artifactId>-->
<!-- <version>${hbase.version}</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.glassfish</groupId>-->
<!-- <artifactId>javax.el</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>${hbase-common.version}</version>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>${hbase-server.version}</version>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
需要排除org.glassfish
的依赖,否则HBase的Maven依赖会爆红!!!如果公司用的是CDP,当然要改成服务器的组件对应的版本!!!
Java
采用多线程的方式,一个Jar包就可以模拟出所需的并发。当网络的带宽不够时,还是需要多个node上同时启动来减少网络的影响。
package com.zhiyong;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.security.UserGroupInformation;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.UUID;
/**
* @program: zhiyong_study
* @description: HBase的put压力测试
* @author: zhiyong
* @create: 2023-04-02 20:29
**/
public class HBasePutPressTest implements Runnable{
private Thread thread;
@Override
public void run() {
long timeStart = System.currentTimeMillis();//开始时间
long timeEnd = 0;
org.apache.hadoop.hbase.client.Connection connection = null;
Configuration configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.quorum", "192.168.88.101:2181,192.168.88.102:2181,192.168.88.103:2181");
configuration.set("zookeeper.znode.parent", "/hbase");
configuration.set("hbase.client.retries.number", "3");
configuration.set("hbase.security.authentication", "kerberos");
configuration.set("keytab.file", "D:/krb/zhiyong.keytab");//Win路径
// configuration.set("keytab.file","/krb/zhiyong.keytab");//Linux路径
configuration.set("kerberos.principal", "zhiyong");
configuration.set("hbase.master.kerberos.principal", "hbase/_HOST@ZHIYONG.COM");
configuration.set("hbase.regionserver.kerberos.principal", "hbase/_HOST@ZHIYONG.COM");
configuration.set("hadoop.security.authentication", "kerberos");
System.setProperty("java.security.krb5.conf", "D:/krb/krb5.conf");//Win路径
// System.setProperty("java.security.krb5.conf","/krb/krb5.conf");//Linux路径
UserGroupInformation.setConfiguration(configuration);
try {
UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI("zhiyong", "D:/krb/zhiyong.keytab");//Win路径
// UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI("zhiyong","/krb/zhiyong.keytab");//Linux路径
UserGroupInformation.setLoginUser(ugi);
System.out.println("Kerberos->HBase认证成功");
} catch (Exception e) {
e.printStackTrace();
}
try {
HBaseAdmin.available(configuration);
connection = ConnectionFactory.createConnection(configuration);
timeEnd = System.currentTimeMillis();
System.out.println("构建connection成功");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("构建connection耗时:" + (timeEnd - timeStart) + "ms");
Table table = null;
try {
table = connection.getTable(TableName.valueOf("db_lzy:tb1"));
System.out.println("获取到HBase表:db_lzy:tb1");
} catch (Exception e) {
e.printStackTrace();
}
String rowkey = "";
StringBuilder strb = new StringBuilder();
Long rowCounter = 0L;
LinkedHashMap<String, String> map = new LinkedHashMap<>();
//一次put10000列
for (int i = 0; i < 10000; i++) {
map.put("col" + i, UUID.randomUUID().toString() + UUID.randomUUID().toString() + UUID.randomUUID().toString());
}
//批put
for (int i = 0; i < 100000; i++) {
LinkedList<Put> puts = new LinkedList<>();
for (int j = 0; j < 1000; j++) {
strb.delete(0, strb.length());
strb.append(String.valueOf(System.currentTimeMillis()));
strb.reverse();
rowkey = strb.toString();
strb.delete(0, strb.length());
Put put = new Put(rowkey.getBytes());
for (String col : map.keySet()) {
put.addColumn("f1".getBytes(), col.getBytes(), map.get(col).toString().getBytes());
}
puts.add(put);
}
try {
table.put(puts);
rowCounter++;
System.out.println("插入第" + rowCounter + "批次成功");
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("插入批次数:" + rowCounter);
try {
table.close();
} catch (Exception e){
System.out.println("关闭Table失败");
}
}
public void start(){
if (null==thread){
thread=new Thread();
thread.start();
}
}
public static void main(String[] args) {
System.out.println("启动");
int threadCounter = 20;
for (int i = 0; i < threadCounter; i++) {
HBasePutPressTest test = new HBasePutPressTest();
test.start();
}
}
}
过Kerberos认证的模式会比较麻烦,如果没有Kerberos认证,会简单不少。
大体上就是反转时间戳做rowkey,防止出现数据热点。每条数据1个列族,10000个列,每10000条数据做一个Batch put批处理,这是我们的生产环境常用的方式。一般很少有单条put的情况,这样玩容易让HBase频繁GC进而OOM或者别的问题宕掉,这是严厉禁止的做法。
笔者这里是受限于网络带宽不足的问题,20线程并发,实际还是要多台node并行跑这个Jar包。
结果
以当时的压测结果来看,RSGroup配置好后,HBase可以把大批量数据正确地存到指定分组的Region Server中,没有出现差错。期间压爆了几个node,但是整体对外服务依旧可用。最终挂服务器跑了一天多,把集群的HDFS接近写满的时候,由于HDFS先抗不住压力挂掉了,才把整个HBase集群压爆了!
至于RSGroup硬件级资源隔离,是另一个故事了。
转载请注明出处:https://lizhiyong.blog.csdn.net/article/details/129918165