Java HBase 多线程


Java多线程是一个很麻烦的东西,为了简化开发加快速度封装了HBase多线程操作,包括常用的Scan,Get,Put,Delete四种操作。经过多次修改运行非常稳定,已经用于生产环境。内部线程通信使用wait()/notify()机制,效率很高。本文只在Java层面讨论HBase的多线程,HBase API内部多线程机制不在本文讨论范围之内。HBase客户端需要的jar包自行下载,这里就不提供了,本类源码结尾处可以下载。下面通过demo演示一下用法,结尾处有代码打包下载。

特点:

  • 从Java层面用多线程最大化了读写性能。
  • 大量减少了应用程序的代码量,现在可以集中精力到数据分析上了。
  • 性能平均下来至少比单线程高出n倍。

HScan

HScan有5个public方法:

  1. public HScan(String table, String[] columns, int threadNum),table指定表名,columns指定扫描的列,如果设为null代表扫描所有的列,如果设为空数组代表只扫描rowkey,threadNum设置扫描器的并发数。
  2. public void addTask(String start, String end, int num),前两个参数是起止rowkey,第三个参数用于控制rowkey平分成num段,这个参数主用用于可整型计算的时间戳开头的rowkey,如果非整型范围内数字这个参数设为1即可。这个方法可以调用多次,每次添加num个任务到任务池。
  3. public void fuck(),继承来的方法,开始进行扫描。
  4. public HashMap<String, String> fetch(),从缓冲区里取一个结果出来,结果中的键由列名和”rowKey”组成,如果返回null表示所有行都已经读完了。
  5. public void status(),一个非同步方法,用于近似显示线程的运行状态,总是在当前行显示,内容由4部分组成2013-01-28_103053,第一列显示已经返回的结果数,第二列显示缓冲区中的行数,第三列分别表示 当前并发线程数/Thread.State.WAITING数/Thread.State.BLOCKED数,第四列显示任务池中的任务数。

Demo,扫描gm_player_detail表2013年1月1日到10日的所有数据

01package joyport.hbase.gm;
02 
03import java.util.HashMap;
04 
05public class Test {
06    private static int threadNum = 30;
07    private static int taskNum = 1000;
08    private static String htable = "gm_player_detail";
09 
10    public static void main(String[] args) throws Exception {
11        int[] time = new int[2];
12        if (args.length == 2) {
13            time = Util.getTimeScale(args[0], args[1]);
14        } else if (args.length == 1) {
15            time = Util.getTimeScale(-1);
16            time[0] = Util.getTimeScale(args[0], args[0])[0];
17        } else {
18            time = Util.getTimeScale(-1);
19        }
20        HScan hScan = new HScan(htable, null, threadNum);
21        hScan.addTask(String.valueOf(time[0]), String.valueOf(time[1]), taskNum);
22        hScan.fuck();
23        Test test = new Test();
24        test.analyse(hScan);
25    }
26 
27    public void analyse(HScan hScan) throws InterruptedException {
28        HashMap<String, String> row = null;
29        for (row = hScan.fetch(); row != null; row = hScan.fetch()) {
30            hScan.status();
31            //System.out.println(row);
32        }
33    }
34}

Util.getTimeScale(-1)获取昨天的起止时间戳
Util.getTimeScale(args[0], args[1])根据yyyy:mm:dd格式的日期获取起止时间戳,java用的是微秒计算的时候可能跨天,如果总是用这个函数就不会出现时间断裂或重复。
编译然后运行
java joyport.hbase/gm/Test 2013-01-01 2013-01-10

HGet

get操作还是很有用途的,比如有些需求需要这样来做,统计每一天的前n条记录,但是一条记录字段特别多,可以先只Scan必要字段计算出前n,然后根据rowkey再回去get详细数据,比直接在详细数据上计算性能高多了。因为需要get的rowkey都是放在内存的,所以如果需要大量get需要自己控制内存使用率。public 方法和HScan类似,自己看代码就明白。

Demo,只扫描rowkey然后根据rowkey用get获取所有列。

01package joyport.hbase.gm;
02 
03import java.util.HashMap;
04 
05public class Test {
06    private static int threadNum = 30;
07    private static int taskNum = 1000;
08    private static String htable = "gm_player_detail";
09 
10    public static void main(String[] args) throws Exception {
11        int[] time = new int[2];
12        if (args.length == 2) {
13            time = Util.getTimeScale(args[0], args[1]);
14        } else if (args.length == 1) {
15            time = Util.getTimeScale(-1);
16            time[0] = Util.getTimeScale(args[0], args[0])[0];
17        } else {
18            time = Util.getTimeScale(-1);
19        }
20        String[] cols = {};
21        HScan hScan = new HScan(htable, cols, threadNum);
22        hScan.addTask(String.valueOf(time[0]), String.valueOf(time[1]), taskNum);
23        hScan.fuck();
24        Test test = new Test();
25        test.analyse(hScan);
26    }
27 
28    public void analyse(HScan hScan) throws Exception {
29        HashMap<String, String> row = null;
30        HGet hGet = new HGet(htable, null, 30);
31        for (row = hScan.fetch(); row != null; row = hScan.fetch()) {
32            hScan.status();
33            //System.out.println(row);
34            hGet.addTask(row.get("rowKey"));
35        }
36        hGet.fuck();
37        for (row = hGet.fetch(); row != null; row = hGet.fetch()) {
38            hScan.status();
39            //System.out.println(row);
40        }
41    }
42}

HPut

HPut和HScan,HGet不太一样,因为put操作的数据源可以有多个,而且类型可以任意(从文本,数据库,HBase表,流),所以对数据源线程也进行了封装,数据源线程数由用户决定。

  1. public HPut(String table, int threadNum) threadNum是写入线程的数目。
  2. public void addTask(final Callable<HashMap<String, String>> task) 返回一行记录的接口,每调用一次产生一个新的数据源线程,当返回null的时候数据源线程结束。
  3. public void enableStatus(boolean enable),即使是一个近似的状态也需要在合适的地方显示,用户程序中显示基本上误差非常大,所以放到put操作的时候显示,这个函数用来控制是否显示put状态,默认显示。

Demo,只展示一个数据源的情况,多数据源没有测试(理论上应该没问题)。从gm_player_detail读取一天的数据插入到hbase_test表。

01package joyport.hbase.gm;
02 
03import java.util.HashMap;
04import java.util.concurrent.Callable;
05 
06public class Test extends Thread implements Callable<HashMap<String, String>> {
07    private int threadNum = 30;
08    private int taskNum = 1000;
09    private String htable = "gm_player_detail";
10    private HScan hScan;
11 
12    public static void main(String[] args) throws Exception {
13        int[] time = new int[2];
14        if (args.length == 2) {
15            time = Util.getTimeScale(args[0], args[1]);
16        } else if (args.length == 1) {
17            time = Util.getTimeScale(-1);
18            time[0] = Util.getTimeScale(args[0], args[0])[0];
19        } else {
20            time = Util.getTimeScale(-1);
21        }
22        Test t1 = new Test(time[0], time[1]);
23        // 往hbase_test写数据
24        HPut hPut = new HPut("hbase_test", 10);
25        hPut.addTask(t1);
26        hPut.fuck();
27    }
28 
29    public Test(int startkey, int endkey) throws Exception {
30        String[] cols = null;
31        hScan = new HScan(htable, cols, threadNum);
32        hScan.addTask(String.valueOf(startkey), String.valueOf(endkey), taskNum);
33        hScan.fuck();
34    }
35 
36    public HashMap<String, String> call() throws InterruptedException {
37        return hScan.fetch();
38    }
39}

HDelete

delete操作只需要rowkey就可以,但是缓冲区数据结构是死的,所以也使用HashMap<String,String>格式,以rowKey为键。如果row.isEmpty()为真则这条数据跳过。

Demo,从hbase_test读取所有行,并删除所有行。

01package joyport.hbase.gm;
02 
03import java.util.HashMap;
04import java.util.concurrent.Callable;
05 
06public class Test implements Callable<HashMap<String, String>> {
07    private static int threadNum = 30;
08    private static String htable = "hbase_test";
09    private static HScan hScan;
10 
11    public static void main(String[] args) throws Exception {
12        String[] cols = {};
13        hScan = new HScan(htable, cols, threadNum);
14        hScan.addTask("0", "2", 1);
15        hScan.fuck();
16 
17        HDelete h = new HDelete(htable, 10);
18        h.addTask(new Test());
19        h.fuck();
20    }
21 
22    public HashMap<String, String> call() throws Exception {
23        return hScan.fetch();
24    }
25}

 

需要注意的问题:

  • 默认缓冲区大小是10000,外部程序线程数(不是构造方法中定义的那个并发)不能超过这个值,否则可能导致无限wait()。
  • 目前HScan的任务池没有进行排重处理,也就是两个任务的起止rowkey交叉和相同任务重复没有进行内部处理,为避免重复扫描需要外部保证。HGet任务排重也需要外部保证。
  • HScan和HGet任务必须一次性添加完毕,因为threadStart()一旦判断完成将关闭缓冲区而造成任务丢失。
  • 本类只在Java层面进行多线程操作!HBaseClient内部同步机制不在本类管辖范围之内!
  • Config.java用到了同目录下的config.conf文件,配置zookeepers和port应该就能用了。

可能的改进:

  • HBuffer的基本类型是HashMap<String,String>,分别是列名和值。如果把HashMap<String,String>改成数组只保存值,新建一个HRow替换HashMap<String,String>作为一行数据,列名作为HRow的静态属性只保存一份,HRow维护列名和值的对应关系,这样会省下大量列名重复占据的内存并降低GC的负担,在大数据量内存运算的情况下能有效提高内存使用率。HRow维护对应关系占用的CPU完全可以忽略。
  • HBase读线程和程序读线程公用一个缓冲区,导致生产和消费使用同一个对象监视器,这样的话notify()效率不是最优,在速度很不稳定的情况下效率可能会有非常小幅度的降低,目前没有解决办法。

hbase-0.92.1

HBaseMulti打包下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
HBase中进行多线程批量数据写入可以提高写入效率。以下是一个简单的示例代码,演示了如何使用Java多线程进行批量数据写入: ```java import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.util.Bytes; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class HBaseMultiThreadedWriter { private static final String TABLE_NAME = "your_table"; private static final String COLUMN_FAMILY = "cf"; private static final String COLUMN_QUALIFIER = "col"; public static void main(String[] args) { Configuration config = HBaseConfiguration.create(); config.set("hbase.zookeeper.quorum", "your_zookeeper_quorum"); try (Connection connection = ConnectionFactory.createConnection(config); Table table = connection.getTable(TableName.valueOf(TABLE_NAME))) { ExecutorService executorService = Executors.newFixedThreadPool(10); // 控制线程池大小 List<Runnable> tasks = new ArrayList<>(); // 创建100个写入任务 for (int i = 0; i < 100; i++) { final int index = i; Runnable task = () -> { try { // 构造Put对象 Put put = new Put(Bytes.toBytes("rowkey_" + index)); put.addColumn(Bytes.toBytes(COLUMN_FAMILY), Bytes.toBytes(COLUMN_QUALIFIER), Bytes.toBytes("value_" + index)); // 执行写入操作 table.put(put); } catch (Exception e) { e.printStackTrace(); } }; tasks.add(task); } // 提交任务给线程池执行 tasks.forEach(executorService::submit); // 关闭线程池 executorService.shutdown(); } catch (Exception e) { e.printStackTrace(); } } } ``` 在上述示例代码中,我们使用了Java的`ExecutorService`和`Runnable`接口来创建一个固定大小的线程池,并提交多个写入任务。每个任务都是独立的,负责向HBase中写入一行数据。 通过使用多线程和批量写入,可以并行地向HBase中写入多个数据行,从而提高写入效率。请根据实际情况调整线程池大小和批量写入的数据量。记得根据需要设置适当的HBase连接参数和表信息。 需要注意的是,多线程写入时可能会对HBase集群产生较大的负载,请确保集群的硬件资源和网络带宽足够支持高并发的写入操作。此外,还要考虑表的预分区策略、RegionServer的负载均衡等因素,以避免潜在的性能问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值