我们总结一下hbase几种写入常见的方式,以及涉及的应用场景,另外再总结一下其中涉及到的一些原理知识。hbase一般的插入过程都使用HTable对象,将数据封装在Put对象中,Put在new创建的时候需要传入rowkey,并将列族,列名,列值add进去。然后HTable调用put方法,通过rpc请求提交到Regionserver端。写入的方式可以分为以下几种
要向hbase中写入就免不了要和HTable打交道,HTable负责向一张hbase表中读或者写数据,HTable对象是非线程安全的。多线程使用时需要注意,创建HTable对象时需要指定表名参数,HTable内部有一个LinkedList
注意:有了BufferedMutator之后,BufferedMutator替换了HTable的setAutoFlush(false)的作用。可以从连接的实例中获取BufferedMutator的实例。在使用完成后需要调用的close()方法关闭连接。对BufferedMutator进行配置需要通过BufferedMutatorParams完成。BufferedMutatorParams要比Htable更搞效,所以心在我们在向hbase插入数据时尽量使用BufferedMutatorParams。
最简单基础的写入hbase,一般应用场景是线上业务运行时,记录单条插入,如报文记录,处理记录,写入后htable对象即释放。每次提交就是一次rpc请求。
table.setAutoFlushTo(true);
/**
* 插入一条记录
* rowkey 为rk001 列族为f1
* 插入两列 c1列 值为001
* c2列 值为002
*
*/
public void insertPut(){
//Configuration 加载hbase的配置信息,HBaseConfiguration.create()是先new Configuration然后调用addResource方法将
//hbase-site.xml配置文件加载进来
Configuration conf = HBaseConfiguration.create();
try {
table = new HTable(conf,tableName);
table.setAutoFlushTo(true);//不显示设置则默认是true
String rowkey = "rk001";
Put put = new Put(rowkey.getBytes());
put.add(cf.getBytes(),"c1".getBytes(),"001".getBytes());
put.add(cf.getBytes(),"c2".getBytes(),"002".getBytes());
table.put(put);
table.close();//关闭hbase连接
} catch (IOException e) {
e.printStackTrace();
}
}
有了单条的put自然就想到这种方式其实是低效的,每次只能提交一条记录,有没有上面方法可以一次提交多条记录呢?减少请求次数, 最简单的方式使用List
/**
* 批量请求,一次提交两条
*/
public void insertPuts() {
Configuration conf = HBaseConfiguration.create();
try {
table = new HTable(conf, tableName);
table.setAutoFlushTo(true);
List lists = new ArrayList();
String rowkey1 = "rk001";
Put put1 = new Put(rowkey1.getBytes());
put1.add(cf.getBytes(), "c1".getBytes(), "001".getBytes());
put1.add(cf.getBytes(), "c2".getBytes(), "002".getBytes());
lists.add(put1);
String rowkey2 = "rk002";
Put put2 = new Put(rowkey2.getBytes());
put2.add(cf.getBytes(), "c1".getBytes(), "v2001".getBytes());
put2.add(cf.getBytes(), "c2".getBytes(), "v2002".getBytes());
lists.add(put2);
table.put(lists);
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
org.apache.hadoop.hbase.client.HTable归根结底持有的就是BufferedMutatorImpl类型的属性mutator,所有后续的操作都是基于mutator操作, 那么其实我们操作hbase客户端,完全可以摒弃HTable对象,直接构建BufferedMutator,然后操作hbase,BufferedMutatorParams主要是收集构造BufferedMutator对象的参数信息,这些参数包括hbase数据表名、hbase客户端缓冲区、hbase rowkey最大所占空间、线程池和监听hbase操作的回调监听器(比如监听hbase写入失败)
使用方式:
正如BufferedMutatorParams需要参数一样,我们需要提供表名,设置好缓存的大小,初始化mutator实例然后提价put对应,向hbase插入数据
//一个Put对象就是一行记录,在构造方法中指定主键
val put = new Put(Bytes.toBytes(MD5Util.getMD5(userId + userName)))
put.addColumn(Bytes.toBytes("hiveData"), Bytes.toBytes("id"), Bytes.toBytes(userId)).addColumn(Bytes.toBytes("hiveData"), Bytes.toBytes("name"), Bytes.toBytes(userName)) .addColumn(Bytes.toBytes("hiveData"), Bytes.toBytes("money"), Bytes.toBytes(userMoney))
putList.add(put)
//设置缓存1m,当达到1m时数据会自动刷到hbase
val params = new BufferedMutatorParams(TableName.valueOf("test6"))
params.writeBufferSize(1024 * 1024) //设置缓存的大小
val mutator = connection.getBufferedMutator(params)
mutator.mutate(putList)
mutator.flush()
putList.clear()
HTable类中提供了get()方法,同时还有与之对应的Get类。get方法分为两类:
这种方法可以从HBase中获取一个特定的值:
Result get(Get get) throws IOException
与put()方法对应Put类相似,get()方法也有对应的Get类,此外还有一个相似之处,那就是在使用下面的方法构造Get实例时,与也需要设置行键:
Get(byte [] row)
Get(byte [] row,RowLock rowLock)
虽然一次get()操作只能读取一行数据,但不会限制一行当中取多少列或者多少单元格。 这两个Get实例都通过row参数指定了要获取的行,其中第二个Get实例还增加了一个可选的rowLock参数,允许用户设置行锁。 与put操作一样,用户有许多方法可用,可以通过多种标准筛选目标数据,也可以指定精确的坐标获取某个单元格的数据:
Get addFamily(byte [] family) // 限制get请求只能取得一个制定的列族,要取得多个列族时需要多次调用。
Get addColumn(byte [] family,byte [] qualifier) // 用户通过他可以指定get取得那一列的数据,从而进一步缩小地址空间。
Get setTimeRange(long minStamp,long maxStamp) // 设置只能在制定的时间戳范围获得列版本。
Get setTimeStamp(long timestamp) // 获得带有制定时间戳的列的版本。
Get setMaxVersions() // 设置最大版本数,设置为最大版本数为Integer的最大值。
Get setMaxVersions(int maxVersions)
说明:
addFamily()方法限制get请求只能取得一个指定的列族,要取得多次调用。addColumn()方法的使用与之类似,用户通过它可以指定get取得哪一列的数据,从而进一步缩小地址空间。有一些方法允许用户设定要获取的数据的时间戳,或者通过设定一个时间段来取得时间戳属于该时间段内的数据。 如果用户没有设定时间戳的话,也有方法允许用户设定要获取的数据的版本数目。默认情况下,版本数为1,即get()方法允许用户设定要获取的数据的版本数目。默认情况下,版本数为1,即get()请求返回最新的匹配版本。如果有疑问,可以使用getMaxVersions()来检查这个Get实例所要获取的最大版本数。不带参数的setMaxVersions()方法会把要取出的最大版本数设为Integer.MAX_VALUE,这是用户在列族描述符中可配置的最大版本数,此时系统会返回这个单元格中所有的版本,换句话说,此时系统会返回用户在列族中已配置的最大版本数以内的所有数据。
方法 | 描述 |
getRow() | 返回创建Get实例时指定的行键 |
getRowLock() | 返回当前Get实例的RowLock实例 |
getLockId() | 返回创建时指定rowLock的锁ID,如果没有指定返回-1L |
getTimeRange() | 返回指定的Get实例的时间戳范围。注意,Get类中已经没有getTimeStamp()方法了,因为API会在内部将setTimeStamp()赋的值转换成TimeRange实例,设定给定时间戳的最大值和最小值 |
setFilter()/getFilter() | 用户可以使用一个特定的过滤器实例,通过多种规则和条件来筛选列和单元格。使用这些方法用户可以设定或查看Get实例的过滤器成员 |
setCacheBlocks()/getCacheBlocks() | 每个HBase的region服务器都有一个块缓存来有效地保存最近存取过的数据,并以此来加速之后的相邻信息的读取。不过在某些情况下,例如完全随机读取时,最好能避免这种机制带来的扰动。这些方法能够控制当次读取的块缓存机制是否启效 |
numFamilies() | 快捷地获取列族FamilyMap大小的方法,包括用addFamily()方法和addColumn()方法添加的列族 |
hasFamilies() | 检查列族或列是否存在于当前的Get实例中 |
familySet()/getFamilyMap() | 这些方法能让用户直接访问addFamily()和addColumn()添加的列族和列。FamilyMap列族中键是列族的名称,键对应的值是指定列族的限定符列表。familySet()方法返回一个所有已存储列族的Set,即一个只包含列族名的集合 |
下边代码展示了从HBase中获取数据的完整过程。
Configuration conf = HBaseConfiguration.create();
HTable table = new HTable(conf, "testtable");
Get get = new Get(Bytes.toBytes("row1"));
get.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
Result result = table.get(get);
byte[] val = result.getValue(Bytes.toBytes("colfam1"),Bytes.toBytes("qual1"));
System.out.println("Values:" + Bytes.toString(val));
当用户使用get()方法获取数据时,HBase返回的结果包含所有匹配的单元格数据,这些数据将被封装在一个Result实例中返回给用户。用它提供的方法,可以从服务器端获取匹配指定行的特定返回值,这些值包括列族、列限定符和时间戳等。提供的方法如下
byte[] getValue(byte[] family,byte[] qualifier) // 方法允许用户取得一个HBase中存储的特定单元格的值。因为该方法不能指定时间戳,所以用户只能获取数据最新的版本。
byte [] value() // 返回第一个列对应的最新单元格的值。
byte [] getRow() // 返回创建Get对象当前实例中的rowKey
int size() // 返回Cell实例的数目
boolean isEmpty() // 查看键值对的数目是否大于0
Cell[] rawCells() // 返回的数组已经按照字典序排列;排序规则是先按列族排序,列族内再按列限定符排序,此后再按时间戳排序,最后按类型排序。
List listCells() // 返回所有Cell对象的集合
List getColumnCells(byte [] family, byte [] qualifier) // 返回特定列的单元格
Cell getColumnLatestCell(byte [] family, byte [] qualifier) // 返回最新版本的Cell对象
boolean containsColumn(byte [] family, byte [] qualifier) // 检查指定列的值是否存在 | |
getValue()方法:
允许用户取得一个HBase中存储的特定单元格的值。因为该方法不能指定时间戳,所以用户只能获取数据最新的版本。
value()方法:
的使用更简单,它会返回第一个列对应的最新单元格的值。因为列在服务器端是按字典存储的,所以会返回名称(包括列族和列限定符)排在首位的那一列的值。
getRow()方法:
它返回创建Get类当前实例使用的行键。size()方法返回服务器端返回值中键值对(KeyValue实例)的数目。用户可以使用size()方法或者isEmpty()方法查看键值对的数目是否大于0,这样可以检查服务器端是否找到了与查询相对应的结果。
raw()方法:
返回原始的底层KeyValue的数据结构。具体来说,是基于当前的Result实例返回KeyValue实例的数组。
list():
调用则把raw()中返回的数组转化为一个list()实例,并返回给用户,创建的List实例由原始返回结果中的KeyValue数组成员组成,用户可以方便地地带使用数据。 raw()方法返回的数组已经按照字典序排列,排列时考虑了KeyValue实例的所有坐标。先按列族排序,列族内再按列限定符排序,此后再按时间戳排序,最后按类型排序
另外还有一些面向列的存取函数如下:
list getColumn(byte[] family,byte[] qualifier)
//获取特定列的多个版本,版本数取决于调用get前,创建get时设置的最大版本数
KeyVlue getColumnLatest(byte[] family,byte[] qualifier)
//返回对于列最新值的KeyVlue ,在不只是需要数据时很有用
boolean containsColumn(byte[] family,byte[] qualifier)
//返回是否有对于列
//这些方法也可以不知道列限定符,将列限定符设置为null。这样会匹配 没有列限定符的特殊列
返回值中的版本数取决于用户调用get()方法之前,创建Get()实例时设置的最大版本数,默认是1。换句话说,getColum()返回的列表中包括0(当本行没有该列值时)或1个条目,这一条目时该列最新版本的值。如果用户指定了一个比默认值1大的版本数,返回的列表中就可能会有多个条目。 getColumnLatest()方法返回对应列的最新版本值,不过与getValue()不同,它不返回值的原始字节数组,而是返回一个KeyValue实例。如果用户需要的不仅仅是数据,那么这个方法将会非常有用。containsColumn()检查返回值中是否有对应的列。不使用限定符就意味着这一列没有标签。当查询表时,例如,用户通过命令行查询时,需要自己明白数据所表示的具体含义。可能只有一种情况能用到空限定符:即一个列族下只有一列,同时列族名就能够表示数据的含义及目的。
下面试第三类取值函数,以映射形式返回结果:
NavigableMap>>getMap()
NavigableMap> getNoVersionMap()
NavigableMap getFamilyMap(byte[] family)
最常用的方法是getMap(),它把所有get()请求返回的内容都装入一个Java的Map类实例,这样用户可以使用该方法遍历所有结果。getNoVersionMap()与getMap()形式上相似,不过它只返回每个列的最新版本。getFamilyMap()允许用户指定一个特定的列族,返回这次结果中这个列族下的所有版本。
不论用户使用什么方法获取Result中的数据,都不会产生额外的性能和资源消耗,因为这些数据都已经通过网络从服务端传输到了客户端。
使用列表参数的get()方法与使用列表参数的put()方法对应,用户可以用一次请求获取多行数据。它允许用户快速高效的从远程服务器获取相关的或完全随机的多行数据,使用时把多个get请求封装到list
API提供的方法签名如下:
Result[] get(List gets)throws IOException
注意:get()方法要么返回与给定列表大小一致的Result数组,要么抛出一个异常
Configuration conf = HBaseConfiguration.create();
HTable table = new HTable(conf, "tableinfo");
byte[] cf1 = Bytes.toBytes("colfam1");
byte[] qf1 = Bytes.toBytes("qual1");
byte[] qf2 = Bytes.toBytes("qual2");
byte[] row1 = Bytes.toBytes("row1");
byte[] row2 = Bytes.toBytes("row2");
List gets = new ArrayList();
Get get1 = new Get(row1);
get1.addColumn(cf1, qf1);
gets.add(get1);
Get get2 = new Get(row2);
get2.addColumn(cf1, qf2);
gets.add(get2);
Result[] results = table.get(gets);
for (Result result : results)
{
String row = Bytes.toString(result.getRow());
System.out.println("Row:" + row + " ");
byte[] val = null;
if (result.containsColumn(cf1, qf1))
{
val = result.getValue(cf1, qf1);
System.out.println("Value:" + Bytes.toString(val));
}
if (result.containsColumn(cf1, qf2))
{
val = result.getValue(cf1, qf2);
System.out.println("Value:" + Bytes.toString(val));
}
}
}