§ 历史回顾 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
- publicConnectionconnection() throws IOException {
- org.apache.hadoop.conf.Configurationconf =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());
- returnConnectionFactory.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. 但在版本号的影响下, 会呈现一些特有的性质. PUTPut 要么是添加新行(如果行键是新的), 或者是更新已有行(如果行键已经存在).
无论是添加还是更新, 默认情况下 (不自己指定版本号), 执行一个 Put 操作会创建一个新版本的单元格, 且版本号是系统当前的毫秒数. 如果是更新时, 新版本数据(新值) 和老版本数据 (旧值) 会同时存在, 且都可以被读取出来.
如果在更新时, 你手动指定了版本号, 且这个版本号和之前旧值的一样, 那么操作执行后, 之前的旧值仿佛被覆盖住了, 无法再读取出来. 换句话说, 如果对一个单元格的多次写入使用相同的版本号, 只有最后一次写入是可读取到的.
还有一点需要明白, 不一定后面的写入就一定要比前面的版本号大, 换句话说, 以一个非增长的版本号顺序对单元格的写入也是没有问题的.
道哥说, 在 Hbase 中, 有关版本号的含义有时会有点懵, 大家再细细品味我刚说过的那些话, 应该都可以理解. DELETEDelete 是删除. 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: 由于内容较多, 前期以入门为主, 后续会进行详细讲解.
来源: https://www.cnblogs.com/lixinjie/p/first-experience-of-java-client-api-for-hbase.html