Hbase王国游记之:Hbase客户端API初体验

Java基础

浏览数:368

2019-5-9

§历史回顾

2018年岁末,李大胖朦胧中上了开往Hbase王国的车,伴着一声长鸣,列出缓缓驶出站台,奔向无垠的广袤。

(图片来自于网络)

如不熟悉剧情的,可观看文章:

五分钟轻松了解Hbase列式存储

Hbase给初学者的“下马威”

§生逢其时

随着改革开放的持续推进,移动互联网的长足发展,以及物联网出现,旧有体制下的一些东西已经不能很好的适应发展的需要,无论是壁垒森严且高冷的Oracle,亦或是左右逢源并可爱的MySQL,都表现出了心有余而力不足。

俗话说,一代天子一朝臣,代代都有追梦人。Hbase无疑是当下最璀璨的星光之一,照耀着九州大地。

Hbase秉持的原则就是,以时间为朋友,打不过别人,却可以熬死对手,剩下来的就是历史的王者,这就是所谓的“剩者为王”。依靠着这个战略方针,Hbase渐渐地建立了自己的王国。

§建国之初

Hbase王国虽然稚嫩,却受上帝青睐。背靠高山,面朝大海,风光秀丽,易守难攻。兵家必争之宝地,旅游度假的好去处。无奈国土初建,人烟稀少,急需大量补充子民,大量开垦良田。

这可使国王Hbase有点犯愁,于是大臣们纷纷献计献策。其中Zookeeper大臣道:虽然已经下发了鼓励多生孩子的条文,但毕竟远水不解近渴。依微臣之愚见,当下行之有效的方法就是游说其它国家的子民,把他们睡服,让他们入伙。国王点了点头道:那就依了爱卿吧。

§改革开放

Zookeeper大臣拿到国王的授权后,开始制定一系列方案方针,并亲自督导实施落地。

国内方面,掀起一场学习热潮,人人参与,集中讲解,分组讨论,逐个答疑,共同提高。

国际方面,24小时开门迎客,落地免签,食宿优惠,门票免费。对于愿意加入我们的,只要简单审核即可发放绿卡。

此消息一出,各国游人蜂拥而至,当然也包括误打误撞的李大胖。

§墨守陈规

街上有很多来自各个国家的游客,边走边看。转过一个路口,前面聚集了一些人,于是大家都跟着凑上去,一探究竟。

原来是一个Hbase王国老者准备给这些游客讲解Hbase相关知识。老者穿着运动鞋/牛仔裤/圆领T恤,头发几乎快没了,还戴着一副眼镜。一看就是大神级别的大牛,就叫他道哥吧。道哥环顾四周,发现人已经很多了,于是开始了讲解。

Hbase是使用Java写的,所以自然提供了一套Java API来操作Hbase,今天就学习如何使用它。

以下是非常老套的流程(可跳过)

引入Maven依赖

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-shaded-client</artifactId>
    <version>${hbase.version}</version>
</dependency>

获取链接(Connection)。此时需要配置一些参数,如IP/端口/超时时间等,一般都会有一个配置对象(HBaseConfiguration)来完成这个工作。由于链接的创建较为复杂,所以一般由链接工厂(ConnectionFactory)负责创建。由于链接的创建涉及网络操作较为耗时,频繁创建并不经济划算,所以一般都会缓存起来以便复用,此时就要求这个链接对象是线程安全的。这些都已经是套路了,大家尽管放心吧。

@Bean
public Connection connection() throws IOException {
    org.apache.hadoop.conf.Configuration conf = HBaseConfiguration.create();
    conf.set(“hbase.zookeeper.quorum”, hbaseProperties.getZookeeper().getQuorum());
    conf.set(“hbase.rpc.timeout”, hbaseProperties.getRpc().getTimeout().toString());
    conf.set(“hbase.rpc.read.timeout”, hbaseProperties.getRpc().getReadTimeout().toString());
    conf.set(“hbase.rpc.write.timeout”, hbaseProperties.getRpc().getWriteTimeout().toString());
    conf.set(“hbase.client.operation.timeout”, hbaseProperties.getClient().getOperationTimeout().toString());
    conf.set(“hbase.client.scanner.timeout.period”, hbaseProperties.getClient().getScannerTimeoutPeriod().toString());
    return ConnectionFactory.createConnection(conf);
}

DDL相关操作。此时需要从Connection里获取一个Admin对象,来执行一些和表相关的操作。这个Admin自然是非线程安全的,不能缓存,且使用完要记得关闭(close)。

DML相关操作。此时需要从Connection里获取一个Table对象,来执行一些和数据相关的操作,很显然,Table的特点和Admin是一样的。

众人看完后,不由得感慨,这果然已经是一个非常标准的分布式软件客户端的套路模板了。

§命名空间

道哥继续道,Hbase引入了命名空间的概念,与我们在其它地方见到的命名空间其实大概意思差不多。
它表示若干个相似表的一个逻辑分组。基于它可以实现一些面向多租户的特性,如:
1、配额管理,限制一个命名空间可以使用的资源量
2、安全管理,为租户提供另一个级别的安全管理
3、物理服务器分组,可以把一个命名空间需要的资源固定在若干个指定服务器上,保证一个粗粒度级别的隔离

道哥补充道,其实命名空间就相当于一个群组,便于从不同的方面进行管理。

每个系统都会预留一些东西供自己使用,或在特殊情况下使用。当然,这也是套路了,最明显的如编程语言中的关键字。Hbase也不例外,它有两个预定义的特殊命名空间:
hbase,系统命名空间,用来包含Hbase内部使用的表
default,默认命名空间,用来在没有显式指定命名空间时使用

最后,浏览一下关于命名空间的API:

//列出所有
admin.listNamespaceDescriptors();
//查询
admin.getNamespaceDescriptor(name);
//创建
admin.createNamespace(namespaceDescriptor);
//修改
admin.modifyNamespace(namespaceDescriptor);
//删除
admin.deleteNamespace(name);

命名空间创建好后,就可以在它里面建表了。下面是和表相关的API:

//列出所有表名
admin.listTableNames();
//按正则表达式列出表名
admin.listTableNames(pattern);
//列出某个命名空间下的表名
admin.listTableNamesByNamespace(namespace);
//列出所有表
admin.listTableDescriptors();
//按表名列出表
admin.listTableDescriptors(tableNames);
//按正则表达式列出表
admin.listTableDescriptors(pattern);
//列出某个命名空间下的表
admin.listTableDescriptorsByNamespace(namespace);
//获取某个表
admin.getDescriptor(tableName);
//检测表是否存在
admin.tableExists(tableName);
//检测表是否禁用
admin.isTableDisabled(tableName);
//禁用表
admin.disableTable(tableName);
//检测表是否启用
admin.isTableEnabled(tableName);
//启用表
admin.enableTable(tableName);
//检测整张表是否都可用
admin.isTableAvailable(tableName);
//创建
admin.createTable(tableDescriptor);
//修改
admin.modifyTable(tableDescriptor);
//删除
admin.deleteTable(tableName);
//添加列簇
admin.addColumnFamily(tableName, columnFamilyDescriptor);
//修改列簇
admin.modifyColumnFamily(tableName, columnFamilyDescriptor);
//删除列簇
admin.deleteColumnFamily(tableName, columnFamily);

道哥似乎看出了众人的眼神,说道,不要着急,下面的内容将会和以往你们遇到的有所不同。

§多版本特性

Hbase是一个多版本数据存储,可以简单地认为它有点历史记录表的味道。首次插入一个数据时,会给它一个版本号,当你后续再更新它时,并不会把之前的旧值抹掉,而是重新存储了一份新值,且使用一个新的版本号。(旧值、新值同时存在,且按版本号倒序排列。

Hbase明确规定版本号必须是一个长整型的数字。通常使用系统当前的毫秒数。但你也可以自己指定它(只要是一个长整型数字),这意味着你可以指定一个过去或将来的时间,或和时间无关的一个数字。

警告:Hbase内部会使用时间戳版本号计算TTL(time-to-live)。所以通常最好不要自己设置这个时间戳。

可以以列簇为单位指定保留的最大版本数目,要么在表创建时指定,或后期再修改。

在0.96之前的版本默认是3,在0.96及其之后的版本默认是1。

从0.98.2开始,可以在hbase-site.xml文件中使用hbase.column.max.version配置项指定一个全局的默认值。

道哥抬眼望去,发现众人都在认真听讲,生怕错过什么东西。

§数据操作

道哥继续说道,Hbase没有数据类型的概念,按官方说法就是bytes-in/bytes-out(字节进/字节出)。所以,无论是字符串、数字、复杂对象,甚至是图片,只要能被转化为字节数组的,都能被存入Hbase中。这和传统数据库相差较大,不过倒和redis挺相似的。众人都是见过世面,对此没有什么异议。

和传统数据库相似,Hbase也有四种主要的数据模型操作,Get,Put,Scan和Delete。但在版本号的影响下,会呈现一些特有的性质。

PUT

Put要么是添加新行(如果行键是新的),或者是更新已有行(如果行键已经存在)。

无论是添加还是更新,默认情况下(不自己指定版本号),执行一个Put操作会创建一个新版本的单元格,且版本号是系统当前的毫秒数。如果是更新时,新版本数据(新值)和老版本数据(旧值)会同时存在,且都可以被读取出来。

如果在更新时,你手动指定了版本号且这个版本号和之前旧值的一样,那么操作执行后,之前的旧值仿佛被覆盖住了,无法再读取出来。换句话说,如果对一个单元格的多次写入使用相同的版本号只有最后一次写入是可读取到的

还有一点需要明白,不一定后面的写入就一定要比前面的版本号大,换句话说,以一个非增长的版本号顺序对单元格的写入也是没有问题的

道哥说,在Hbase中,有关版本号的含义有时会有点懵,大家再细细品味我刚说过的那些话,应该都可以理解。

DELETE

Delete是删除。Hbase并不会在数据原来的位置处去删除数据(这是底层HDFS决定的)。所以删除实际上是通过创建新的叫做“墓碑”的标记(就是再插入一些数据,标记一下哪些数据将要被删除)来实现的。

所以数据并不会立即被删除。而是在下一个大的压缩时,墓碑标记会被处理,要被删除的数据连同墓碑标记本身都会被移除掉。

道哥害怕众人不解,就解释道,大家都使用过Java,都知道JVM的GC机制。对象不可达时,是立即进行回收的吗?众人都答道,不是,会先进行标记,在满足特定条件时会触发垃圾回收,此时才会真正进行回收。道哥表示非常欣慰。

Hbase有三种不同类型的删除标记,分别表示删除如下数据:
1、删除一个列的某个指定版本
2、删除一个列的所有版本
3、删除一个列族的所有列

见大家都没有疑问,道哥继续道,当删除一个整行时,是在内部为每个列簇创建一个墓碑,而不是为每个单独的列都创建。

当删除一列时,你可以指定一个版本号,如果不指定则默认使用系统当前毫秒数,这意味着将删除所有版本号小于或等于该版本的单元格。如果你指定的版本号比所有的版本都大,可以认为所有数据都将被删除。

GET/SCAN

道哥首先强调,Hbase返回数据时总是以已排好的顺序,首先按行键(字典顺序从小到大排序),然后是列簇,接着是列修饰符,最后是时间戳(时间戳是倒序,所以最新的记录首先返回)。

Scan是在多行上面进行迭代,Get是在Scan上面实现的,所以它俩其实差不多。

如果你没有显式地指定版本,则最大版本号的那个单元格被返回,它可能并不是你最后一次写入的那个

默认行为可以按下面方式修改:
1、如果要返回所有版本的数据,使用Get.readAllVersions()来进行标记
2、要返回多个版本数据,使用Get.readVersions(versions)设置返回的版本数目
3、要返回非最新版本数据,使用Get.setTimeRange(minStamp, maxStamp)设置版本号的范围

道哥最后问了一个问题,如果要获取小于或等于某个版本号的最新版本数据,该怎么做呢?有人答道,将版本范围设置成从0到期望的版本且将最大版本数目设置为1。你认为这样可以吗?

PS:由于内容较多,前期以入门为主,后续会进行详细讲解。

相关文章

五分钟轻松了解Hbase列式存储

Hbase给初学者的“下马威”

(完)

编程新说

用独特的视角说技术

作者:李新杰