§历史回顾
2018年岁末,李大胖朦胧中上了开往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)。所以通常最好不要自己设置这个时间戳。
可以以列簇为单位指定保留的最大版本数目,要么在表创建时指定,或后期再修改。
道哥抬眼望去,发现众人都在认真听讲,生怕错过什么东西。
§数据操作
道哥继续说道,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:由于内容较多,前期以入门为主,后续会进行详细讲解。
相关文章
(完)
编程新说
用独特的视角说技术