# 二、HBase 和 HBase Shell

对于开发和测试环境，您可以在一台机器上运行 HBase。HBase 支持三种运行模式:独立、伪分布式和分布式。分布式模式适用于由 HDFS 支持的完整集群，多个服务器运行不同的 HBase 堆栈组件，我们将在第 8 章“HBase 的体系结构”中介绍这一点

独立模式适用于单个机器，其中所有组件在单个 Java 虚拟机中运行，本地文件系统用于存储，而不是 HDFS。伪分布式模式在一台服务器上的不同 JVM 中运行每个 HBase 组件，它可以使用 HDFS 或本地文件系统。

![](img/00007.jpeg)提示:伪分布式模式是在本地运行的一个很好的选择——您可以在组件之间获得生产方式的分离，但不必运行多台机器。

[本 HBase 文档](http://hbase.apache.org/)涵盖了在本地安装和运行 HBase，因此我在此不再赘述，但是在本地运行 HBase 最简单的方法是使用 Docker。Docker Hub 上有几个 HBase 映像，包括我自己的一个，这是我为了配合本课程而构建的。

Docker 是一种应用容器技术。容器是一个快速、轻量级的计算单元，允许您在一台机器上运行多个负载。容器在概念上类似于虚拟机，但在磁盘、CPU 和内存使用方面要轻得多。Docker 运行在 Linux、OS/X 和 Windows 机器上。您可以在这里获得安装说明[。Docker Hub 是一个预建图像的公共注册中心，我为这本书准备的图像可以在](http://www.docker.com/)[这里](https://hub.docker.com/r/sixeyed/hbase-succinctly)找到。

使用 Docker 容器的好处是，您可以用很少的开销加速和终止实例，并且您不需要担心任何软件或服务与您的开发机器冲突。

简而言之，这个图像是我专门为这本书准备的，它设置了我们将要使用的服务。要使用该映像，请安装 Docker 并执行代码清单 5 中的命令:

 5:在 Docker 中运行 HBase

```java
          docker run -d -p 2181:2181 \
           -p 60010:60010 -p 60000:60000 \
           -p 60020:60020 -p 60030:60030 \
           -p 8080:8080 -p 8085:8085 \
           -p 9090:9090 -p 9095:9095 \
           --name hbase -h hbase \
           sixeyed/hbase-succinctly

```

docker run 命令中的一些设置是可选的，但是如果您想按照本书中的示例进行编码，您需要运行完整的命令。如果您不熟悉 Docker，以下是该命令的作用:

从公共 Docker Hub 中的六眼存储库中提取名为 h base-简洁的图像

在本地容器中运行该映像，并为服务器和网络用户界面公开所有关键端口

给图像命名，这样我们就可以用其他 Docker 命令来控制它，而不需要知道 Docker 将分配的容器 ID

为映像提供主机名，这样我们就可以使用该名称访问它

第一次运行时，需要一段时间才能将映像从注册表中提取到本地计算机，但是对于未来的运行，容器将在几秒钟内启动，并且您将有一个运行 Java、REST 和节俭 API 的运行良好的 HBase 实例。

![](img/00008.jpeg)注意:这个 Docker 命令公开了 Zookeeper、HBase 外部 API 和 Web UIs 的所有端口。如果它们与您机器上的其他服务器冲突，您可以通过更改–p 参数冒号前的数字来更改本地端口。例如，要让节俭监听 Docker 主机上的端口 91，而不是 9090，请使用–p 91:9090。

此映像将本地文件系统用于 HBase 数据存储，当容器被终止时，该文件系统不会被保留，因此您可以使用 docker kill hbase 将数据库重置回原始状态，然后重复 docker run 命令。

糖化血红蛋白带有一个命令行界面，糖化血红蛋白外壳。外壳不能在远程计算机上使用，因此您需要从本地计算机运行它(对于独立和伪分布式模式)，或者登录到主服务器(对于分布式模式)。

从 HBase bin 目录中，运行 hbase shell 以启动 shell。如果您正在通过我的 Docker 映像运行 HBase，请通过运行代码清单 6 中的交互式命令进行连接:

 6:在 Docker 中运行 HBase Shell

```java
          docker exec -it hbase hbase shell

```

HBase Shell 基于 JRuby，您可以使用它来执行脚本文件，以及交互式命令。Shell 中有大量命令可用。这是从 HBase 开始的理想场所，您还可以在生产中使用它来管理集群。

在本章中，我们将介绍定义表和读写数据的主要命令。

三个 shell 命令将让您开始使用新的 HBase 数据库:

目录

创造

形容

要查看数据库中的所有表，运行 list，您会看到两种形式的输出:纯文本列表和数组表示。代码清单 7 显示了示例输出:

 7:列表

```java
          hbase(main):001:0> list
          TABLE                                                                                                                  
          api-logs                                                                                                                
          social-usage                                                                                                           
          2 row(s) in 0.2550 seconds

          => ["api-logs", "social-usage"]

```

list 命令只提供表名，不提供其他细节。对于大型数据库，可以通过为命令提供正则表达式来过滤输出，以匹配表名；例如列出‘so’。*”将显示以“so”开头的表名。

要创建表，请使用 create 命令，指定表名和列族名。或者，您可以传递列族的配置设置，这就是如何更改 HBase 存储的单元版本的数量。代码清单 8 显示了两个创建命令:

 8:创建表格

```java
          hbase(main):007:0> create 'with-default-config', 'cf1'
          0 row(s) in 1.2380 seconds

          => Hbase::Table - with-default-config
          hbase(main):008:0> create 'with-custom-config', {NAME =>'cf1', VERSIONS=>3}
          0 row(s) in 1.2320 seconds

          => Hbase::Table - with-custom-config

```

带有-default-config 的表只有一个列族 cf1，没有指定配置，因此它将使用 HBase 默认值(包括单个单元格版本)。带有-custom-config 的表也有一个名为 cf1 的列族，但带有一个指定三个单元格版本的自定义配置设置。

![](img/00008.jpeg)注意:HBase Shell 使用 Ruby 语法，用大括号定义指定为名称-值对的对象和属性，用“= >”分隔值。

要查看表的配置，请使用 description 命令。输出告诉您该表是否支持客户端访问，并包括所有列族及其所有设置，如代码清单 9 所示:

 9:描述表格

```java
          hbase(main):009:0> describe 'with-custom-config'
          Table with-custom-config is ENABLED                                                                                    
          with-custom-config                                                                                                      
          COLUMN FAMILIES DESCRIPTION                                                                                            
          {NAME => 'cf1', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER => 'ROW', REPLICATION_SCOPE => '0', VERSIONS => '3', COMPRESS
          ION => 'NONE', MIN_VERSIONS => '0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536', IN_MEMORY =>
          'false', BLOCKCACHE => 'true'}                                                                                          
          1 row(s) in 0.1240 seconds

```

现在，我们有了一些具有不同配置的表，我们可以开始使用 HBase Shell 添加和检索数据了。

使用 HBase Shell，您可以使用 put 命令为单个单元格写入数据，指定表名、行键、列名和单元格值。代码清单 10 中的命令为具有关键字 rk1 的行将 cf1 列族中数据列的值设置为 v1:

 10:写入数据

```java
          hbase(main):019:0> put 'with-default-config', 'rk1', 'cf1:data', 'v1'
          0 row(s) in 0.0070 seconds

```

put 命令的工作方式类似于插入/更新。如果该行不存在，命令会创建它，创建列，并设置单元格值。如果行存在但列不存在，该命令会将列添加到行中并设置单元格值。

使用具有单个单元版本的默认列族配置，如果行和列已经存在，则该命令会用新值覆盖现有值。但是对于具有多个版本的列族，put 命令会使用该值添加一个新的单元格版本，并设置时间戳。

您可以选择指定要使用的自定义时间戳值，作为单元格值之后的长整数。代码清单 11 明确指定了过去的时间戳 UNIX 纪元后一毫秒(1970 年 1 月 1 日):

 11:用时间戳写入数据

```java
          hbase(main):020:0> put 'with-default-config', 'rk1', 'cf1:data', 'v0', 1
          0 row(s) in 0.0060 seconds

```

![](img/00009.jpeg)提示。注意在更新中指定时间戳。如果您的时间戳早于单元格的其他版本(即使在列族中只配置了一个版本)，则您的数据被认为比当前数据旧，单元格值不会被覆盖，并且您的更新会丢失。

您可以使用 get 命令读取一行中的数据，但是与 put 命令不同，您可以使用 get 来读取多个单元格值。若要读取整行，请使用 get 和表名及行键。HBase 将返回该行中每个单元格值的最新版本，如代码清单 12 所示:

 12:读取整行

```java
          hbase(main):017:0> get 'social-usage', 'a'
          COLUMN                          CELL                                                                                   
           i:tw                           timestamp=1446655378543, value=@EltonStoneman                                          
           t:tw                           timestamp=1446655459639, value=900                                                     
           tw:2015110216                  timestamp=1446655423853, value=310                                                      
           tw:2015110316                  timestamp=1446655409785, value=270                                                     
           tw:2015110417                  timestamp=1446655398909, value=320                                                      
          5 row(s) in 0.0360 seconds

```

您可以将结果限制在特定的列或族中，在行关键字后用逗号分隔的列族名称或限定符列表。在代码清单 13 中，我们返回整个 I 系列，以及 tw 系列中的一列:

 13:读取特定列

```java
          hbase(main):022:0> get 'social-usage', 'a', 'tw:2015110316', 'i'
          COLUMN                          CELL                                                                                   
           i:tw                           timestamp=1446655378543, value=@EltonStoneman                                          
           tw:2015110316                  timestamp=1446655409785, value=270
          2 row(s) in 0.0090 seconds

```

您还可以传递具有许多属性的对象，而不仅仅是字符串，以从行中返回更具体的细节。我可以通过指定 versions 属性为一列返回多个版本，并返回数字，如代码清单 14 所示:

 14:读取多个单元格版本

```java
          hbase(main):027:0> get 'with-custom-config', 'rk1', {COLUMN =>'cf1:data', VERSIONS => 3}
          COLUMN                          CELL                                                                                   
           cf1:data                       timestamp=1446655931606, value=v3                                                      
           cf1:data                       timestamp=1446655929977, value=v2                                                      
           cf1:data                       timestamp=1446655928221, value=v1                                                      
          3 row(s) in 0.0120 seconds

```

HBase 表中的行在物理上是按顺序存储的，按行键排序。我们将在第 9 章“区域服务器内部”中查看数据的结构和索引方法，但现在，我们只需要知道:

排序表意味着通过键进行快速直接访问。

排序的表意味着按值进行的搜索很慢。

事实上，您不能在 HBase 中搜索与任意模式匹配的行键。如果您有一个存储系统访问日志的表，其中的行键以{系统标识} | {用户标识}开头，则不能搜索某个特定用户的日志，因为用户标识位于行键的中间。

使用 HBase，您可以通过扫描表找到匹配的行，为扫描提供开始和结束边界。从逻辑上讲，HBase 就像光标一样工作，将表定位到开始行(或开始行的部分匹配)，并一直读取到结束行。

扫描命令很简单，但是对于新的 HBase 用户来说，结果可能是意想不到的。表 2 显示了我的访问日志表中的一些示例数据:

| 行键 |
| 杰里科&#124;戴夫&#124;201510 |
| 杰里科&#124;埃尔顿&#124;201510 |
| 杰里科&#124;埃尔顿&#124;201511 |
| 杰里科\和平\ 201 510 |

 2:样本行键

我们这里有四个行键，都是针对 2015 年 10 月和 11 月的用户戴夫、埃尔顿和弗雷德的名为杰里科的系统。这些行在表 2 中以与存储在 HBase 表中相同的字典顺序列出。

为了找到杰里科访问的所有行，我可以用 STARTROW 值扫描该表，如代码清单 15 所示:

 15:用 STARTROW 扫描

```java
          hbase(main):006:0> scan 'access-logs', {STARTROW => 'jericho'}
          ROW                             COLUMN+CELL                                                                            
           jericho|dave|201510            column=t:3015, timestamp=1446706437576, value=60                                       
           jericho|elton|201510           column=t:3015, timestamp=1446706444028, value=700                                      
           jericho|elton|201511           column=t:0416, timestamp=1446706449473, value=800                                       
           jericho|fred|201510            column=t:0101, timestamp=1446706454401, value=450                                      
          4 row(s) in 0.0540 seconds

```

为了找到埃尔顿对杰里科的所有访问，STARTROW 需要包含用户 ID，我需要添加一个 ENDROW 值来排除埃尔顿之后的任何行。这是扫描变得更有趣的地方。我可以使用 jericho|f 的 ENDROW 值，这只会得到 Elton 的日志，如代码清单 16 所示:

 16:用 ENDROW 扫描

```java
          hbase(main):010:0> scan 'access-logs', {STARTROW => 'jericho|elton', ENDROW => 'jericho|f'}
          ROW                             COLUMN+CELL                                                                            
           jericho|elton|201510           column=t:3015, timestamp=1446706444028, value=700                                      
           jericho|elton|201511           column=t:0416, timestamp=1446706449473, value=800                                      
          2 row(s) in 0.0190 seconds

```

该查询目前有效，但是如果我们稍后为一个名为 Ernie 的用户添加行，当我运行相同的查询时，它也会返回他们的日志。因此，扫描的 STARTROW 和 ENDROW 需要尽可能地具体，同时又不损失查询的灵活性。

一个将返回埃尔顿在任何时间段的所有日志的查询，可以使用 STARTROW = >“杰里科|埃尔顿|”和 ENDROW = >“杰里科|埃尔顿|x”。了解您的 ASCII 字符代码会有所帮助。

管道字符的值高于任何字母数字字符，因此在用户名后包含管道可确保其他用户的日志不会进入扫描。字符 x 比任何数字都高，因此在扫描结束时添加字符意味着查询将返回任何年份的行。

关于扫描的最后一点:上边界是“达到”值，而不是“达到并包括”值。如果我想查看 Elton 在 2015 年 10 月和 11 月的所有访问权限，代码清单 17 中的查询是不正确的:

 17:扫描“直到”末端行

```java
          hbase(main):014:0> scan 'access-logs', {STARTROW => 'jericho|elton|201510', ENDROW => 'jericho|elton|201511'}
          ROW                             COLUMN+CELL                                                                             
           jericho|elton|201510           column=t:3015, timestamp=1446706444028, value=700                                      
          1 row(s) in 0.0140 seconds

```

使用 jericho|elton|201511 的 ENDROW 值意味着 HBase 读取该行，然后停止。要包含 201511 年的行，我需要一个比这些行更远的结束行值，在这种情况下，我可以使用杰里科|埃尔顿|201512。

在这一章中，我们从 HBase 开始，从 Docker 容器本地运行它。然后，我们使用 HBase Shell 进行连接，并使用表管理命令列出数据库中的所有表，创建新表，并描述它们的配置。

我们看到了如何使用 put 命令写入数据，以及如何使用 get 命令读取数据，包括使用一个单元格的多个版本。我们看到了如何扫描表格中的一系列行，使用开始和结束行键作为扫描的边界。

我们将在第 10 章“监控和管理 HBase”中返回到 HBase Shell，其中涵盖了更多高级主题。在下一章中，我们将更仔细地研究 HBase 表设计、行键和区域。