Hadoop 之关于 HBase

17 篇文章 1 订阅

1. HBase 基础


 


HBase 是构建于 HDFS 之上的面相列分布式数据库。如果要求实时的随机访问(读/写)大规模数据集,就可以使用 HBase 这一 Hadoop 应用。

虽然数据库的存储和检索有很多不同的策略和实现,但大多数解决方案 ———— 特别是许多关系型数据库的变种 ———— 它们不是构建于大规模和分布式思想。
很多厂商提供复制和分区解决方案来将数据库从单个节点边界扩展出去,但这些附加的技术通常是 "事后" 的解决办法,而且结构复杂,非常难以安装和维护。
并且常常要牺牲一些重要的 RDBMS 特性。运行在一个"扩展的" RDBMS 上,连接,复杂查询,触发器,视图,以及外键约束这些功能,要么运行开销大,要么
根本不工作。

HBase 从另一个方向来解决可伸缩性问题。它自底向上进行构建,能够简单地通过增加节点来达到线性扩展。HBase 不是关系型数据库,也不支持 SQL. 但在
给定的问题空间,它能做到 RDBMS 不能做到的事:在廉价硬件构建的集群上存储管理大规模稀疏表。

HBase 的典型应用场景是 webtable, 爬取的 web 页面和页面属性(例如,language and MIME type)组成的一个表。webtable 非常大,其行数有数十亿。在
webtable 上连续运行用于批处理分析和解析的 MapReduce 作业,能够获取相关的统计信息,增加验证的 MIME 列,并解析文本内容用于之后的搜索引擎建立
索引。

同时,该表会被运行于不同速率的网络爬虫(crawler) 随机访问,并更新任意的行。


    背景(Backdrop)
    -------------------------------------------------------------------------------------------------------------------------------------
    HBase 项目由 Powerset 公司的 Chad Walters 和 Jim Kellerman 发起。在 Google’s Bigtable 模型化(“Bigtable: A Distributed Storage System
    for Structured Data,” November 2006) 发布不久之后,2007 年 2 月,Mike Cafarella 提供代码,形成了一个基本可用使用的系统,然后
    Jim Kellerman 接手继续推进项目。
    
    HBase 的第一个发布版本是在 2007 年 10月 作为 Hadoop 0.15.0 的一部分捆绑发布的。2010年 5 月,HBase 从 Hadoop 子项目晋升为 Apache 顶级
    项目。如今, HBase 已经是一个成熟的技术被应用于广阔的工业生产环境。

    


2. 概念 (Concepts)
-----------------------------------------------------------------------------------------------------------------------------------------
本节,提供 HBase 核心概念的快速总览,最低限度,熟悉这些概念有利于消化后续内容。





2.1 数据模型的 "旋风之旅" (Whirlwind Tour of the Data Model)
-----------------------------------------------------------------------------------------------------------------------------------------
应用程序将数据存储到带标签的表中。表由行和列组成。表单元格 ———— 行和列的坐标交叉 ———— 是版本化的。默认情况下,版本号是由 HBase 在单元格
插入时自动分配的时间戳。单元格的内容是一个未解释的字节序列。

表中行的键(Table row keys) 也是字节数组,因此理论上,任何数据都可以作为一个行键(a row key), 从字符串到 long 的二进制表示,甚至序列号的
数据机构。表的行由行键排序(sorted by row key), 也成为表的主键(table’s primary key). 排序是按字节序的(The sort is byte-ordered). 所有的表
通过主键访问。

行中的列被分组成列族(column families). 所有的列族成员具有一个公共的前缀(common prefix), 因此,例如,列 info:format 和 info:geo 都是 info
列族的成员,而 contents:image 属于 contents 族。列族前缀必须由可打印的字符组成(composed of printable characters)。修饰性的结尾字符,即列族
修饰符,可以为任意字节。列族和修饰符之间总是以一个冒号(:) 分隔。

一个表的列族必须作为表 schema 定义的一部分提前指定,但新的列族成员可以按需加入。例如,一个新的列 info:camera 可以由一个客户端作为更新的
一部分提供,并且他的值是一致的,只要列族 info 已存在于表中。

物理上,所有的列族成员一起存储在文件系统上。因此,虽然我们前面吧 HBase 描述为一个面向列的存储(column-oriented store),但实际上,更准确的
说法是:他是一个面向列族的存储(a column-family-oriented store)。由于调优和存储规格(storage specifications) 都是在列族这个层次上进行的,
所以最好使所以列族成员都有相同的"访问模式"(access pattern) 和大小特征。

总的来说, HBase 表和 RDBMS 中的表类似,只是 HBase 中单元格是有版本的,行是排序的,列可以由客户端自由添加,只要它们所属的列族预先存在即可。




    ■ 区域 (Regions)
    -------------------------------------------------------------------------------------------------------------------------------------
    表自动由 HBase 水平切分为 "区域" (regions) 。每个区域由一个表的行的子集组成。一个区域由其所属的表,它的第一个行(包含,inclusive),它的
    最后的行 (不包含, exclusive) 来标记。初始时,一个表由一个区域组成,但随着区域增长,它最终会超出设定大小的阈值,这时它会在某一行的边界
    处切分为两个大小大致相同的新区域。直到第一个划分发生之前,所有的负载都会发生在存储原始区域那个服务器上。随着表变大,区域的数量也会增加。
    区域是一个 HBase 集群上分布数据的单元。采用这种方式,一个因为太大而无法放在单台服务器上表会被放到服务器集群上,每个节点负责管理表的所有
    区域的一个子集。表的加载也是使用这种方式把数据分布到各个节点。在线排序的区域集构成了表的所有内容。



    ■ 加锁 (Locking)
    -------------------------------------------------------------------------------------------------------------------------------------
    无论多少行的列构成行级别的事务,行的更新是原子的。这使得加锁模型(locking model)能够保持简单。





2.2 实现 (Implementation)
-----------------------------------------------------------------------------------------------------------------------------------------
正如 HDFS 和 YARN,由客户端、工作者(workers)、和一个用于协调的主节点 master 构成 —— HDFS 中的 namenode 和 datanodes, 以及 YARN 中的 resource
manager 和 node managers, HBase 也采用相同的模型,它由一个 HBase master 节点协调管理一个集群的一个或多个 regionserver workers. HBase master
负责启动(bootstrapping) 一个全新安装,将区域分配到注册的 regionserver, 恢复 regionserver 故障。HBase master 节点是轻负载的。regionserver
负责零个或多个区域的管理以及响应客户端的读写请求。regionserver 也负责区域分片,通知 HBase master 有了新的子区域(new daughter regions), 这
样它就能够使父区域离线,并用子区域替换父区域。

HBase 依赖于 ZooKeeper,默认情况下,它管理一个 ZooKeeper 实例作为集群的权威机构(authority), 然而它也可以配置为利用一个现有的 ZooKeeper 集群。
ZooKeeper 集合体管理关键数据,如 hbase:meta 目录表(catalog table)和当前集群 master 的地址。如果在区域分配过程中有服务器崩溃,可以由 ZooKeeper
来进行分配协调。在 ZooKeeper 中管理事务状态的分配可以使它从崩溃服务器遗留的状态开始继续分配。最低限度,当启动一个客户端连接到 HBase 集群时,
客户端必须传递 ZooKeeper 集合体的位置,这样,客户端才能访问 ZooKeeper 的层次结构,从而了解集群的属性,例如服务器的位置。

Regionserver 工作节点列于 HBase 的 conf/regionservers 文件,就如在 Hadoop 的 etc/hadoop/slaves 文件中列出 datanodes 和 node manager. 启动和
停止脚本类似于 Hadoop , 并且使用相同的基于 SSH 的机制运行远程命令。集群的站点特定的配置在 conf/hbasesite.xml 和 conf/hbase-env.sh 文件中。
有与 Hadoop 相同的格式。

HBase 通过 Hadoop 文件系统 API 来持久化数据存储。大多数人使用 HDFS 作为存储来运行 HBase 。但是,默认情况系,除非另行指明,HBase 会将存储写入
本地文件系统。如果体验一下新装的 HBase, 这是没有问题的,但如果之后要使用 HBase 集群,首要任务是把 HBase 的存储配置为指向所要使用的 HDFS 集群。



    ■ HBase 操作 (HBase in operation)
    -------------------------------------------------------------------------------------------------------------------------------------
    在内部,HBase 保留一个特殊的目录表(catalog table)名为 hbase:meta, 其内部维护着集群上所有用户空间区域的当前列表、状态、以及位置信息。
    hbase:meta 表的列表项使用区域名(region name)作为键,而区域名由其所属的表的名称,区域的起始行,它创建的时间,以及最后一个对整体数据进行、
    MD5 hash 值(即对表明、起始行、创建的时间戳进行哈希计算后的结果)组成。这里是一个区域名称的示例,表名为 TestTable, 起始行为 xyz:
    
        TestTable,xyz,1279729913622.1b6e176fb8d8aa88fd4ab6bc80247ece.

    逗号(,)界定了表名,起始行,以及时间戳。 MD5 hash 值由一个前导句点(.) 和一个结尾句点(.) 包围。

    如前所述,行键是排序的,因此要找到一个特定行所在的区域,只要在目录表中找到其键小于或等于要查找的行的键最大的表项。在区域事务时———即切分,
    禁用,启用,删除,或者由负载均衡器重新分布,或者由于 regionserver 崩溃而被重新分布——目录表会更新,这样集群上所有区域的状态保持是最新的。
    
    新连接到 ZooKeeper 集群的客户端会首先查找 hbase:meta 的位置。然后客户端对 hbase:meta 区域执行查找来找到用户空间区域所在节点及其位置信息,
    之后,客户端就直接与 regionserver 主机进行交互。
    
    为了节省每一行操作都要进行的三次往返过程,客户端缓存所有对 hbase:meta 执行的查找信息。缓存位置信息以及用户空间区域的起始和停止行,这样
    它们就可以自己找的区域主机而不需要返回到 hbase:meta 表进行查找。客户端在其工作时会继续使用缓存的结果,直到有错误发生。当有错误发生时,
    如,区域已经转移了——客户端会再次查询 hbase:meta 表以获得新的位置信息。如果查询的 hbase:meta 区域也移动了,客户端会再次求教于 ZooKeeper.
    
    到达一个 regionserver 的写操作会首先追加到提交日志(first appended to a commit log), 然后添加到内存中的 memstore. 当 memstore 填满时,它
    的内容会刷新到文件系统。
    
    提交日志存放在 HDFS 中,因此即使一个 regionserver 崩溃,提交日志仍然可用。当 HBase master 发现某个 regionserver 不可访问了,通常是因为
    ZooKeeper 中的服务器的 znode 过期了,它会按区域切分死掉的 regionserver 的提交日志。重新分配后,并且在它们重新打开使用之前,在死掉的
    regionserver 上的区域会挑选出它们刚刚切分的还没有进行持久化编辑的文件,然后重放(replay)它们,以使区域恢复到服务器故障之前的状态。
    
    在读取时,会首先查询区域的 memstore。如果在 memstore 中找到了需要的版本,查询就结束了。否则,按次序查询刷新的文件(flush files), 从最新的
    到最旧的,直到找到满足查询的版本,或者所有刷新文件都处理完为止。
    
    有一个后台进程负责在刷新文件数量达到一个阈值时压缩(compact)它们,它把多个文件重新写入一个文件,这是因为读操作检查的文件越少,它的执行效率
    越高。在执行压缩时(On compaction), 进程会清理掉超出模式所设置最大值(schema-configure maximum)的版本,并移除已删除的或过期的单元(cells).
    在 regionserver 上,另外有一个独立的进程监控着刷新文件的大小,一旦文件超出了预先设置的最大值,便会对区域进行切分。



3. 安装 (Installation)
-----------------------------------------------------------------------------------------------------------------------------------------
从 Apache Download Mirror(http://www.apache.org/dyn/closer.cgi/hbase/) 下载一个稳定发布版本,并解压到本地文件系统,如:

    % tar xzf hbase-x.y.z.tar.gz

和 Hadoop 一样,首先要告诉 HBase 系统上 Java 的位置。如果设置了 JAVA_HOME 环境变量指向了合适的 Java 安装,HBase 会使用这个环境变量,也就
不需要进一步的配置了。否则,通过编辑 HBase 的 conf/hbase-env.sh 文件来设置 HBase 使用的 Java 安装,并指定 JAVA_HOME 环境变量。

为了方便,把 HBase 的二进制文件目录加入到命令行路径中。示例如下:

    % export HBASE_HOME=~/sw/hbase-x.y.z
    % export PATH=$PATH:$HBASE_HOME/bin

要获得 HBase 的选项列表,使用如下命令:

 

  % hbase

    Options:
        --config DIR Configuration direction to use. Default: ./conf
        --hosts HOSTS Override the list in 'regionservers' file
    Commands:
    Some commands take arguments. Pass no args or -h for usage.
        shell Run the HBase shell
        hbck Run the hbase 'fsck' tool
        hlog Write-ahead-log analyzer
        hfile Store file analyzer
        zkcli Run the ZooKeeper shell
        upgrade Upgrade hbase
        master Run an HBase HMaster node
        regionserver Run an HBase HRegionServer node
        zookeeper Run a Zookeeper server
        rest Run an HBase REST server
        thrift Run the HBase Thrift server
        thrift2 Run the HBase Thrift2 server
        clean Run the HBase clean up script
        classpath Dump hbase CLASSPATH
        mapredcp Dump CLASSPATH entries required by mapreduce
        pe Run PerformanceEvaluation
        ltt Run LoadTestTool
        version Print the version

        CLASSNAME Run the class named CLASSNAME

 

    ■ 测试驱动 (Test Drive)
    -------------------------------------------------------------------------------------------------------------------------------------
    启动一个独立(standalone)模式的 HBase 实例,使用本地文件系统的临时目录作为持久化存储:
    
        % start-hbase.sh

    默认地, HBase 写入到 /${java.io.tmpdir}/hbase-${user.name}. ${java.io.tmpdir} 会映射到 /tmp , 但可以配置 HBase 使用一个更永久性的位置,
    通过在 hbase-site.xml 文件中设置 hbase.tmp.dir 属性。在独立模式下,HBase master, the regionserver, and a ZooKeeper 实例都运行在同一 JVM
    
    管理 HBase 实例,启动 HBase shell:
    
        % hbase shell
        HBase Shell; enter 'help<RETURN>' for list of supported commands.
        Type "exit<RETURN>" to leave the HBase Shell
        Version 0.98.7-hadoop2, r800c23e2207aa3f9bddb7e9514d8340bcfb89277, Wed Oct 8 15:58:11 PDT 2014
        hbase(main):001:0>
    
    这将启动一个加入了 HBase 特有命令的 JRuby IRB 解释器。输入:
        
        help
        
    然后按 RETURN 键可以查看已分组的 shell 环境的命令列表。
    输入:
    
        help COMMAND_GROUP
    
    可以查看某一类命令的帮助
    输入:
        
        help COMMAND
    
    能获取某个特定命令的帮助信息和用法示例
    
    命令使用 Ruby 的格式来指定列表和目录。主帮助页面的最后包含一个快速教程
    
    现在让我们创建一个简单的表,添加一些数据,然后清除
    
    要创建一个表,必须为表命名,并定义其模式(schema). 一个表的模式包含表的属性和列族的列表。列族本身也有属性,可以在定义模式时依次设置。列族
    属性的示例包括,列族内容是否在文件系统上被压缩,以及一个单元格保持多少的版本。模式可以在之后修改,通过使用 shell 的 disable 命令将表设为
    离线状态,使用 alter 命令进行必要的修改,然后再使用 enable 命令将表恢复为在线状态。

    要创建一个名为 test 表,有一个名为 data 的列族,表和列族都使用默认属性,输入:
    
        hbase(main):001:0> create 'test', 'data'
        0 row(s) in 0.9810 seconds

    关于如何在定义模式时添加表和列族属性的示例,可参加 help 命令的输出。
    为了验证新表是否创建成功,运行 list 命令。这会输出用户空间中的所有的表:
    
        hbase(main):002:0> list
        TABLE
        test
        1 row(s) in 0.0260 seconds

    在 data 列族中插入三个不同的行和列,并获取第一个行,然后列出表的所有内容,按如下操作:

        hbase(main):003:0> put 'test', 'row1', 'data:1', 'value1'
        hbase(main):004:0> put 'test', 'row2', 'data:2', 'value2'
        hbase(main):005:0> put 'test', 'row3', 'data:3', 'value3'
        hbase(main):006:0> get 'test', 'row1'
        COLUMN CELL
        data:1 timestamp=1414927084811, value=value1
        1 row(s) in 0.0240 seconds
        hbase(main):007:0> scan 'test'
        ROW COLUMN+CELL
        row1 column=data:1, timestamp=1414927084811, value=value1
        row2 column=data:2, timestamp=1414927125174, value=value2
        row3 column=data:3, timestamp=1414927131931, value=value3
        3 row(s) in 0.0240 seconds

    注意我们是如何在不修改模式(schema) 的情况下加入了三个列(columns)。
    
    要删除这个表,必须首先禁用(disable)它,然后 drop :
    
        hbase(main):009:0> disable 'test'
        0 row(s) in 5.8420 seconds
        hbase(main):010:0> drop 'test'
        0 row(s) in 5.2560 seconds
        hbase(main):011:0> list
        TABLE
        0 row(s) in 0.0200 seconds

    停止 HBase 实例,运行:
    
        % stop-hbase.sh

    
4. 客户端 (Clients)
-----------------------------------------------------------------------------------------------------------------------------------------
有很多种不同的客户端与 HBase 集群进行交互。




4.1 Java
-----------------------------------------------------------------------------------------------------------------------------------------
HBase 和 Hadoop 一样,都是 Java 开发的。下面示例展示了上节的 shell 操作的 Java 版本:

    //Basic table administration and access
    public class ExampleClient {
        public static void main(String[] args) throws IOException {
            Configuration config = HBaseConfiguration.create();
            // Create table
            HBaseAdmin admin = new HBaseAdmin(config);
            try {
                TableName tableName = TableName.valueOf("test");
                HTableDescriptor htd = new HTableDescriptor(tableName);
                HColumnDescriptor hcd = new HColumnDescriptor("data");
                htd.addFamily(hcd);
                admin.createTable(htd);
                HTableDescriptor[] tables = admin.listTables();
                if (tables.length != 1 &&
                Bytes.equals(tableName.getName(), tables[0].getTableName().getName())) {
                    throw new IOException("Failed create of table");
                }
            // Run some operations—three puts, a get, and a scan—against the table.
            HTable table = new HTable(config, tableName);
            try {
                for (int i = 1; i <= 3; i++) {
                    byte[] row = Bytes.toBytes("row" + i);
                    Put put = new Put(row);
                    byte[] columnFamily = Bytes.toBytes("data");
                    byte[] qualifier = Bytes.toBytes(String.valueOf(i));
                    byte[] value = Bytes.toBytes("value" + i);
                    put.add(columnFamily, qualifier, value);
                    table.put(put);
                }
            Get get = new Get(Bytes.toBytes("row1"));
            Result result = table.get(get);
            System.out.println("Get: " + result);
            Scan scan = new Scan();
            ResultScanner scanner = table.getScanner(scan);
            try {
                    for (Result scannerResult : scanner) {
                    System.out.println("Scan: " + scannerResult);
                }
            } finally {
                scanner.close();
            }
                // Disable then drop the table
                admin.disableTable(tableName);
                admin.deleteTable(tableName);
            } finally {
                table.close();
            }
            } finally {
                admin.close();
            }
        }
    }


类只有一个 main() 方法。为了简洁,我们没有包含包名,也没有导入。大多数 HBase 类可以在 org.apache.hadoop.hbase 和 org.apache.hadoop.hbase.client
包中找到。

这个类中,我们首先请求 HBaseConfiguration 类创建一个 Configuration 对象。它会返回一个 Configuration 对象,从程序的类路径上找到的 hbase-site.xml
和 hbase-default.xml 文件读取的 HBase 配置信息。这个 Configuration 随后用于创建 HBaseAdmin 和 HTable 的实例。HBaseAdmin 用于管理 HBase 集群,
特别是添加和删除表。HTable 用于访问一个特地的表。Configuration 实例将这些类指向了执行这些代码的集群。

要创建一个表,我们需要创建一个 HBaseAdmin 实例,然后让它来创建名为 test, 只有一个名为 data 的列族的表,在这个例子中,表的模式(schema) 是默认的。
可以利用 HTableDescriptor 和 HColumnDescriptor 中的方法改变表的模式(schema). 接下来的代码测试了表是否真的被创建了,如果没有则抛出一个异常。

要操作一个表,我们需要一个 HTable 实例,通过将 Configuration 实例和表的名称传递进去构建。然后在循环中创建了 Put 对象,将数据插入到表中。每一个
Put 对象将一个单一的单元格(cell)值为 valuen 放置到名为 rown 的行,列名为 data:n, 这里 n 是 1 到 3 的值。列名由两部分指定:列族名,和列族修饰符。


接下来,创建了一个 Get 对象来获取并打印添加的第一行。然后使用一个 Scan 对象扫描整个表,打印出找到的结果。

程序最后,通过首先禁用表,然后删除它,把这张表清除掉。



4.2 REST and Thrift
-----------------------------------------------------------------------------------------------------------------------------------------
HBase 提供了 REST 和 Thrift 接口。在使用 Java 以外的编程语言和 HBase 交互时,这些接口非常有用。





5. 构建一个在线查询应用 (Building an Online Query Application)
-----------------------------------------------------------------------------------------------------------------------------------------
虽然 HDFS 和 MapReduce 是用于对大数据集进行批处理的强大工具,但对于读或写单独的记录,效率却很低。在这个示例中,我们将看到如何用 HBase 来
填补它们之间的鸿沟。

在前面章节描述的气象数据集包含了过去 100 多年上万个气象站的观测数据,并且这个数据集还在继续地无限增长。在这个例子中,我们构建一个简单的
在线接口(与批处理相反)以允许用户导航到不同的气象站和页面,查看它们整个历史的按时间顺序的气温观测数据。可以为此构建一个简单的命令行  Java
程序。但很容易看出,使用相同的技术构建一个 web 应用可以做到相同的事。

由于这个例子的缘故,让我们允许数据集非常大,观测数据有几亿条记录,且气温更新数据到达的速度非常快——从全球所有的气象站每秒钟有几百到几千次
更新。不仅如此,这个 web 应用要求必须能够在一秒之内实时(most up-to-date)显示观测数据。

第一个数据大小的要求使我们排除了使用一个简单的 RDBMS 实例,HBase 是一个可选的存储选项。第二个延迟性要求排除了直接使用 HDFS. MapReduce 作业
可以在初始时建立索引以支持对观测数据随机访问,但 HDFS 和 MapReduce 并不擅长在更新到达时维护索引更新。



5.1 模式设计 (Schema Design)
-----------------------------------------------------------------------------------------------------------------------------------------
在这个示例中有两个表:



    ● stations:
    -------------------------------------------------------------------------------------------------------------------------------------
    这个表中持有观测站数据,让行的键为 stationid . 让表有 info 列族作为气象站信息的 key-value 字典。让字典的 key 为列名称 info:name,
    info:location, and info:description . 这个表是静态的,这种情况下,info 列族完全映射为一个典型的 RDBMS 表设计。


    ● observations:
    -------------------------------------------------------------------------------------------------------------------------------------
    这个表持有气温观测数据。行的键是一个组合键,由 stationid 加上一个逆序的时间戳组成。给这个表一个 data 列族,包含一个列,airtemp, 其值为
    观测到的气温值。


我们对于模式的选择来源于我们了解的从 HBase 读取的最高效的方式。行和列以词汇升序保存。虽然有二级索引和正则表达式匹配工具,但它们会损失性能。
清楚地理解查询数据的最高效的方式,对于选择最有效的存储和访问数据的设置是至关重要的。

对于 stations 表,选取 stationid 作为键是明显的,因为我们总是会通过 ID 来访问特定气象站的信息。对于 observations 表,然而,使用一个复合键
将观测数据的时间戳加入到尾部。这会将某一特定的气象站的所有的观测数据分组放到一起,使用逆序时间戳((Long.MAX_VALUE - timestamp)的二进制存储,
每一个气象站的观测数据最新的数据存储在最前面。


    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    We rely on the fact that station IDs are a fixed length. In some cases, you will need to zero-pad number components so row keys sort
    properly. Otherwise, you will run into the issue where 10 sorts before 2, say, when only the byte order is considered (02 sorts before
    10).

    Also, if your keys are integers, use a binary representation rather than persisting the string version of a number. The former consumes
    less space.

在 shell 中,定义表如下:

    hbase(main):001:0> create 'stations', {NAME => 'info'}
    0 row(s) in 0.9600 seconds
    hbase(main):002:0> create 'observations', {NAME => 'data'}
    0 row(s) in 0.1770 seconds



    WIDE TABLES
    -------------------------------------------------------------------------------------------------------------------------------------
    All access in HBase is via primary key, so the key design should lend itself to how the data is going to be queried. One thing to keep
    in mind when designing schemas is that a defining attribute of column(-family)-oriented stores, such as HBase, is the ability to host
    wide and sparsely populated tables at no incurred cost.[139]

    There is no native database join facility in HBase, but wide tables can make it so that there is no need for database joins to pull from
    secondary or tertiary tables. A wide row can sometimes be made to hold all data that pertains to a particular primary key.



5.2 加载数据 (Loading Data)
-----------------------------------------------------------------------------------------------------------------------------------------
观测站的数量相对较少,所以可以使用任何一种接口来插入这些观测站的静态数据。示例代码包含一个 Java 应用程序用于这个工作:

    % hbase HBaseStationImporter input/ncdc/metadata/stations-fixed-width.txt

然而,假设我们要加载数十亿条观测数据。这种数据导入是一个极为复杂的,并且是一个长时间运行的数据库操作,但 MapReduce 和 HBase 的分布式模型
让我们可以充分利用集群。可以把原始数据复制到 HDFS, 然后运行一个 MapReduce 作业读取输入并写入到 HBase.

下面示例演示了 MapReduce 作业,从前面章节示例使用的相同的数据文件,将观测数据导入到 HBase.


    //A MapReduce application to import temperature data from HDFS into an HBase table
    public class HBaseTemperatureImporter extends Configured implements Tool {
        
        static class HBaseTemperatureMapper<K> extends Mapper<LongWritable, Text, K, Put> {
            private NcdcRecordParser parser = new NcdcRecordParser();
            
            @Override
            public void map(LongWritable key, Text value, Context context) throws
                IOException, InterruptedException {
                
                parser.parse(value.toString());
                if (parser.isValidTemperature()) {
                    byte[] rowKey = RowKeyConverter.makeObservationRowKey(parser.getStationId(),
                    parser.getObservationDate().getTime());
                    Put p = new Put(rowKey);
                    p.add(HBaseTemperatureQuery.DATA_COLUMNFAMILY, HBaseTemperatureQuery.AIRTEMP_QUALIFIER
                    , Bytes.toBytes(parser.getAirTemperature()));
                    context.write(null, p);
                }
            }
        }
        
        @Override
        public int run(String[] args) throws Exception {
            if (args.length != 1) {
                System.err.println("Usage: HBaseTemperatureImporter <input>");
                return -1;
            }
            Job job = new Job(getConf(), getClass().getSimpleName());
            job.setJarByClass(getClass());
            FileInputFormat.addInputPath(job, new Path(args[0]));
            job.getConfiguration().set(TableOutputFormat.OUTPUT_TABLE, "observations");
            job.setMapperClass(HBaseTemperatureMapper.class);
            job.setNumReduceTasks(0);
            job.setOutputFormatClass(TableOutputFormat.class);
            return job.waitForCompletion(true) ? 0 : 1;
        }
        
        public static void main(String[] args) throws Exception {
            int exitCode = ToolRunner.run(HBaseConfiguration.create(),
            new HBaseTemperatureImporter(), args);
            System.exit(exitCode);
        }
    }


HBaseTemperatureImporter 有一个内部类 HBaseTemperatureMapper. 外部类实现了 Tool 并设置启动 map-only 作业。HBaseTemperatureMapper 创建
一个 Put 对象并将解析的气温观测数据写入到 HBase 的 observations 表,data:airtemp 列。(对 data 和 airtemp 使用了静态常量)

对每一条的观测数据 row key 由 RowKeyConverter 的 makeObservationRowKey() 方法通过气象站 ID 和观测时间创建:

    public class RowKeyConverter {
        private static final int STATION_ID_LENGTH = 12;
        /**
        * @return A row key whose format is: <station_id> <reverse_order_timestamp>
        */
        public static byte[] makeObservationRowKey(String stationId,
        long observationTime) {
            byte[] row = new byte[STATION_ID_LENGTH + Bytes.SIZEOF_LONG];
            Bytes.putBytes(row, 0, Bytes.toBytes(stationId), 0, STATION_ID_LENGTH);
            long reverseOrderTimestamp = Long.MAX_VALUE - observationTime;
            Bytes.putLong(row, STATION_ID_LENGTH, reverseOrderTimestamp);
            return row;
        }
    }


转换利用了气象站 ID 为一个固定长度的 ASCII 字符串这个事实。类似之前的例子,使用了 HBase 的 Bytes 类来在字节数组和普通 Java 类型间转换。
Bytes.SIZEOF_LONG 常量用于计算 row key  字节数组的时间戳部分数据大小。putBytes() 和 putLong() 方法用于在字节数组的相对偏移量位置填充
key 的气象站 ID  和 timestamp 部分。

作业在 run() 方法中使用 HBase 的 TableOutputFormat 配置。写入表必须在作业的配置中设置 TableOutputFormat.OUTPUT_TABLE 属性。

使用 TableOutputFormat 是很方便的,因为它为我们管理 HTable 的创建,否则我们需要在 mapper 的 setup() 方法中创建(以及在 cleanup() 方法中
调用 close())。TableOutputFormat 也禁用了 HTable 的 auto-flush 特性,因此 put() 的调用缓存起来,获得更好的性能。

示例代码包含 HBaseTemperatureDirectImporter 类来演示如何从一个 MapReduce 程序直接使用 HTable . 运行程序如下:

    % hbase HBaseTemperatureImporter input/ncdc/all



    ● 载入分布 (Load distribution)
    -------------------------------------------------------------------------------------------------------------------------------------
    要特别当心数据导入所引发的这个表的 "步调一致" 的情况,所有客户端都对同一个表的区域(在单个节点上)进行操作,然后再对下一个区域进行操作,
    依次进行,而非均衡地分布加载操作到所有的区域。这通常是排序输入(sorted input) 和分区切分工作(splitter works) 相互作用造成的。在插入之前
    随机化 row key 的顺序可能有助于减少这种情况。在我们的示例中,给定的 stationid 值的分布情况和 TextInputFormat 切分方式,上载(upload)
    足以保证足够的分布式特性。
    
    如果一个表是新想,它将只有一个区域,所有的更新(updates)操作都会到这一个区域,直到区域切分(split)为止。即使 row key 是随机分布的也是如此。
    这种启动现象意味着上传操作开始会比较慢,直到有足够多的区域被分布到集群各个节点上能够分担上传。不要把这种情况和我们在上文中说明的情况混淆
    
    这两个问题都可以通过使用 bulk load 而避免。
    
    
    
    ● 大块载入 (Bulk load)
    -------------------------------------------------------------------------------------------------------------------------------------    
    HBase 有一个高效的大块载入 HBase 工具,通过 MapReduce 以其内部数据格式直接写入到文件系统。
    
    Bulk loading 是两步骤的过程。第一步,利用 HFileOutputFormat2 使用一个 MapReduce 作业,将 HFiles 直接写入到 HDFS 目录。由于行必须按次序
    写入,作业必须对 row keys 执行一次 total sort. HFileOutputFormat2 的 configureIncrementalLoad() 方法可以做所有必要的配置工作。
    
    第二步,涉及将 HFiles 从 HDFS 移动到一个现有的 HBase 表。该表可以在此过程期间存在。示例代码中包含的类 HBaseTemperatureBulkImporter 利用
    bulk load 载入观测数据。
    
    
    
    
5.3 在线查询 (Online Queries)
-----------------------------------------------------------------------------------------------------------------------------------------    
为了实现在线查询应用程序,我们将直接使用 HBase Java API. 这里,将深刻体会到选取模式 (schema) 和存储格式(storage format) 的重要性。
    
    
    
    ● 气象站查询 (Station queries)
    -------------------------------------------------------------------------------------------------------------------------------------
    最简单的查询就是获取静态的观测站信息。这是一个单行查找,使用 get() 操作执行。这类查询想传统数据库中也很简单,但 HBase 提供了更多的控制
    和灵活性。利用 info 列族作为 key-value 字典(key-value dictionary), 列明作为 key, 列的值作为 value. HBaseStationQuery 的代码类似如下:
    
    static final byte[] INFO_COLUMNFAMILY = Bytes.toBytes("info");
    static final byte[] NAME_QUALIFIER = Bytes.toBytes("name");
    static final byte[] LOCATION_QUALIFIER = Bytes.toBytes("location");
    static final byte[] DESCRIPTION_QUALIFIER = Bytes.toBytes("description");
    
    public Map<String, String> getStationInfo(HTable table, String stationId)
    throws IOException {
        Get get = new Get(Bytes.toBytes(stationId));
        get.addFamily(INFO_COLUMNFAMILY);
        Result res = table.get(get);
        if (res == null) {
            return null;
        }
    
        Map<String, String> resultMap = new LinkedHashMap<String, String>();
        resultMap.put("name", getValue(res, INFO_COLUMNFAMILY, NAME_QUALIFIER));
        resultMap.put("location", getValue(res, INFO_COLUMNFAMILY,
        LOCATION_QUALIFIER));
        resultMap.put("description", getValue(res, INFO_COLUMNFAMILY, DESCRIPTION_QUALIFIER));
        return resultMap;
    }
    
    private static String getValue(Result res, byte[] cf, byte[] qualifier) {
        byte[] value = res.getValue(cf, qualifier);
        return value == null? "": Bytes.toString(value);
    }
    
    示例中, getStationInfo() 接收一个 HTable 实例和一个气象站 ID. 获取气象站信息,使用了 HTable 的 get() 方法,传递了配置好的 Get 实例,
    获取由气象站 ID 标识的行,已定义列族 INFO_COLUMNFAMILY 中所有的列的值。
    
    get() 方法的结果返回到一个 Result 中。它包含数据行(row),可以通过操作所需要的列的单元格来获取单元格(cell) 的值。getStationInfo() 方法
    将 Result 转换成更便于使用的 String 类型的 key 和 value 的 Map.
    
    我们已经看出在使用 HBase 时为什么需要工具函数了。有越来越多的抽象构建于 HBase 之上(atop) 来处理底层交互,但理解它们的工作机理,以及
    各个存储选项之间的差异,非常重要。
    
    HBase 比关系型数据库强大之处之一是,不需要事先指定所有的列。这样,如果每个气象站现在至少有这三个属性,但还有几百个可选的属性,将来
    可以直接插入这些属性而不需要修改 schema. (当然,需要修改应用程序的读取和写入代码)
    
    下面是气象站查询运行示例:
    
        % hbase HBaseStationQuery 011990-99999
        name SIHCCAJAVRI
        location (unknown)
        description (unknown)
    
    
    
    ● 观测数据查询 (Observation queries)
    -------------------------------------------------------------------------------------------------------------------------------------
    对 observations 表的查询利用一个 station ID, 一个起始时间(start time), 以及一个最大返回行数量(maximum number of rows to return)。由于
    行是按照气象站的时间顺序的逆向次序存储的,查询将返回 start time 之前的观测数据。
    
    下面示例中 getStationObservations() 方法利用一个 HBase scanner 来遍历表的行。返回一个 NavigableMap<Long, Integer>, 其 key 是 timestamp
    值是温度值(temperature). 由于这个 map  是按 key 升序排序的,因此它的项是按时间顺序排序的。
    
    
    // An application for retrieving a range of rows of weather station observations from an HBase table
    public class HBaseTemperatureQuery extends Configured implements Tool {
        
        static final byte[] DATA_COLUMNFAMILY = Bytes.toBytes("data");
        static final byte[] AIRTEMP_QUALIFIER = Bytes.toBytes("airtemp");
        
        public NavigableMap<Long, Integer> getStationObservations(HTable table,
            String stationId, long maxStamp, int maxCount) throws IOException {
            
            byte[] startRow = RowKeyConverter.makeObservationRowKey(stationId, maxStamp);
            NavigableMap<Long, Integer> resultMap = new TreeMap<Long, Integer>();
            Scan scan = new Scan(startRow);
            scan.addColumn(DATA_COLUMNFAMILY, AIRTEMP_QUALIFIER);
            ResultScanner scanner = table.getScanner(scan);
            
            try {
                Result res;
                int count = 0;
                while ((res = scanner.next()) != null && count++ < maxCount) {
                    byte[] row = res.getRow();
                    byte[] value = res.getValue(DATA_COLUMNFAMILY, AIRTEMP_QUALIFIER);
                    Long stamp = Long.MAX_VALUE -
                    Bytes.toLong(row, row.length - Bytes.SIZEOF_LONG, Bytes.SIZEOF_LONG);
                    Integer temp = Bytes.toInt(value);
                    resultMap.put(stamp, temp);
                }
            } finally {
                scanner.close();
            }
            return resultMap;
        }
        
        public int run(String[] args) throws IOException {
            if (args.length != 1) {
                System.err.println("Usage: HBaseTemperatureQuery <station_id>");
                return -1;
            }
            HTable table = new HTable(HBaseConfiguration.create(getConf()), "observations");
            try {
                NavigableMap<Long, Integer> observations =
                getStationObservations(table, args[0], Long.MAX_VALUE, 10).descendingMap();
                for (Map.Entry<Long, Integer> observation : observations.entrySet()) {
                    // Print the date, time, and temperature
                    System.out.printf("%1$tF %1$tR\t%2$s\n", observation.getKey(),
                    observation.getValue());
                }
                return 0;
            } finally {
                table.close();
            }
        }
        
        public static void main(String[] args) throws Exception {
            int exitCode = ToolRunner.run(HBaseConfiguration.create(), new HBaseTemperatureQuery(), args);
            System.exit(exitCode);
        }
    }
    
    
    run() method 调用 getStationObservations(), 请求最近 10 个观测数据,然后通过调用 descendingMap() 把结果按降序排序。
    观测数据格式化后并打印到控制台(记得气温值是 10 倍的),如:
    
    % hbase HBaseTemperatureQuery 011990-99999
    1902-12-31 20:00 -106
    1902-12-31 13:00 -83
    1902-12-30 20:00 -78
    1902-12-30 13:00 -100
    1902-12-29 20:00 -128
    1902-12-29 13:00 -111
    1902-12-29 06:00 -111
    1902-12-28 20:00 -117
    1902-12-28 13:00 -61
    1902-12-27 20:00 -22
    
    按时间逆序排序 timestamps 的好处是容易获取到最新的观测数据,这正是在线应用程序经常想要的结果。如果观测数据按实际的 timestamps
    次序排序,对于一个给定的偏移,只能得到最老的行,并且限制效率。得到最新的 n 行非常高效,然后退出 scanner (这通常称为 “early-out”
    场景)
    
    
    
6. HBase 和 RDBMS 比较 (HBase Versus RDBMS)
-----------------------------------------------------------------------------------------------------------------------------------------    
HBase 和其它面向列的数据库(column-oriented databases) 经常被拿来和更传统的流行的关系型数据库做比较,或称为 RDBMS。虽然它们在实现和出发点
有着较大的区别,但它们都力图解决相同的问题,不管它们有多么大的不同,仍然能够对它们进行客观的比较。

如前所述,HBase 是一个分布式的,面向列的数据存储系统。它通过在 HDFS 上提供随机读写来解决 Hadoop 不能解决的问题。HBase 自底层设计开始就专注
于各种可伸缩性问题:高度方向(tall) 可以有数十亿行,宽度方向(wide) 可以有数百万个列,以及水平方向分区可以在数千个普通商用机节点上自动复制。
表的模式(schema) 映射到物理存储,为高效的数据结构序列化,存储,以及检索创建一个系统。负担在应用程序开发者身上,要以正确的方式使用这种存储
和检索方式。

严格地说, RDBMS 是遵循 Codd’s 12 规则的数据库。典型的 RDBMS 是固定模式的(fixed-schema)、面向行的数据库,具有 ACID 属性和一个复杂的 SQL
查询引擎。 RDBMS 强调强一致性,引用完整性,物理层抽象,以及通过 SQL 语言的复杂查询。可以很容易创建二级索引,执行复杂的 inner 和 outer 连接,
以及跨多个表、行,和列,对数据进行 count, sum, sort, group, 以及分页(page)等操作。

对于大多数中小规模(small- to medium-volume applications)的应用,易于使用的,灵活的,成熟的,强大的可用的开源 RDBMS 解决方案如 MySQL 和
PostgreSQL 不可替代。然而,如果要在数据规模,和并发读写,这两方面中的一个或全部进行向上扩展(scale up),就会很快发现 RDBMS 的易用性会损失
非常多的性能,而要进行分布式处理,更是非常困难。RDBMS 数据规模的扩展通常要求打破 Codd 规则,如放松 ACID 的限制,使 DBA 的管理变得复杂,
并同时放弃大多数关系型数据库引以为傲的易用性。



6.1 成功的服务 (Successful Service)
-----------------------------------------------------------------------------------------------------------------------------------------
这里将简单介绍一个典型的 RDBMS 如何进行扩展。下面给出一个成功服务从小到大的生长过程。



    ● 服务首次提供公开访问 (Initial public launch)
    -------------------------------------------------------------------------------------------------------------------------------------
    将服务从本地工作站迁移到一个共享的,远程的运行 MySQL 实例的主机上, MySQL 实例上具有定义良好的 schema.
    
    
    
    ● 服务变得很受欢迎,数据库收到太多的读请求 (Service becomes more popular; too many reads hitting the database)
    -------------------------------------------------------------------------------------------------------------------------------------    
    添加 memcached 来缓存常用查询结果。这时读操作不再是严格意义上的 ACID; 缓存的数据必须在某个时间到期。



    ● 服务的欢迎程度持续增长,数据库收到太多的写请求 (Service continues to grow in popularity; too many writes hitting the database)
    -------------------------------------------------------------------------------------------------------------------------------------
    通过购买一个 16 核、128 GB RAM 、配备一组 15 k RPM 硬盘驱动器的增强型服务器来垂直升级 MySQL . 非常昂贵。



    ● 新的特性增加了查询的复杂度,现在需要很多连接操作 (New features increase query complexity; now we have too many joins)
    -------------------------------------------------------------------------------------------------------------------------------------
    对数据进行反规范化以减少连接的次数。



    ● 激增的欢迎程度使服务器陷于艰难;所有操作都变得很慢 (Rising popularity swamps the server; things are too slow)
    -------------------------------------------------------------------------------------------------------------------------------------
    停止任何服务器端计算操作。



    ● 有些查询仍然太慢 (Some queries are still too slow)
    -------------------------------------------------------------------------------------------------------------------------------------
    定期对最复杂的查询进行 prematerialize, 并尝试在大多数情况下停止使用连接。



    ● 读取性能尚可,但写操作越来越慢 (Reads are OK, but writes are getting slower and slower)
    -------------------------------------------------------------------------------------------------------------------------------------
    放弃使用二级索引和触发器(no indexes?).

    
    到这一点为止,没有一个清晰的解决方案来解决扩展问题。多数情况,需要开始水平扩展。尝试咋最大的表上构建某种类型的分区,或者查找一些商业
    解决方案以提供多个 master 能力。
    
    无数的应用、商业组织、以及 web 站点都已解决了构建于 RDBMS 之上的可伸缩性、容错和分布式数据系统,并且很可能使用了很多前述的策略。但最终
    已不再是一个真正的 RDBMS 系统。为了折中和复杂性,牺牲了系统的某些特性和易用性。任何形式的从属复制或外部缓存都会对现已反规范化的数据引入
    弱一致性(weak consistency). 无效的连接(join) 和二级索引(secondary indexes)意味着几乎所有的查询都变成对主键(primary key)的查找。对于
    多写入(multiwriter) 的设置很可能意味着根本没有实际的连接(joins), 而分布式事务变成了噩梦。现在,要管理一个单独用于缓存的集群,网络拓扑
    变得极其复杂。即使有了一个系统,并做了那么多妥协处理,仍然会担心主控机(primary master) 会崩溃,并畏惧几个月之后可能的出现 10 倍于现在
    的数据和 10 倍于现在负载。
    


6.2 HBase
-----------------------------------------------------------------------------------------------------------------------------------------
进入 HBase, 它具有以下特性。



    ● 没有真正的索引 (No real indexes)
    -------------------------------------------------------------------------------------------------------------------------------------
    行是按顺序存储的,每一行的列也是。因此不存在索引膨胀的问题,而且插入性能和表大小无关。
    
    

    ● 自动分区 (Automatic partitioning)
    -------------------------------------------------------------------------------------------------------------------------------------
    在表增长的时候,表会自动切分成区域(regions),并分布到可用的节点上。



    ● 扩展是线性的,并且自动扩展到新节点 (Scale linearly and automatically with new nodes)
    -------------------------------------------------------------------------------------------------------------------------------------
    增加一个新节点,指向到现有集群,并运行 regionserver. 区域(regions) 会自动重新均衡(rebalance), 并且负载会均匀分布。



    ● 普通商用硬件支持 (Commodity hardware)
    -------------------------------------------------------------------------------------------------------------------------------------
    集群可用 $1,000–$5,000 价格的节点搭建,而不需要使用 $50,000 单价的节点。 RDBMS 都需要大量的 I/O 操作,因此需要更昂贵的硬件。
    
    
    
    ● 容错 (Fault tolerance)
    -------------------------------------------------------------------------------------------------------------------------------------    
    大量的节点意味着每个节点的重要性并不突出。不用担心单个节点失效的问题。



    ● 批处理 (Batch processing)
    -------------------------------------------------------------------------------------------------------------------------------------
    MapReduce 的集成可以使用完全并行的,分布式的作业通过位置感知(locality awareness) 对数据进行操作。


    如果日夜担心数据库((uptime, scale, or speed), 需要认真考虑从 RDBMS 的世界转移到 HBase. 应该使用一个针对扩展性问题的解决方案,而不是
    性能越来越差,投入大量的金钱用于工作。使用 HBase, 软件是免费的,硬件是廉价的,以及天生的分布式处理。
    


7. Praxis
-----------------------------------------------------------------------------------------------------------------------------------------
在本节,讨论在运行 HBase 集群时用户遇到的一些常见问题。




7.1 HDFS
-----------------------------------------------------------------------------------------------------------------------------------------
HBase 使用 HDFS 的方式与 MapReduce 使用 HDFS 的方式截然不同。在 MapReduce 中,通常,通过 map 任务打开 HDFS 文件,然后 map 任务流式处理文件
内容,最后关闭文件。在 HBase 中,数据文件在集群启动时就被打开,并始终保持打开状态,以避免每次访问操作打开文件所需的代价。正因为如此,HBase
更容易碰到 MapReduce 客户端不常碰到的问题:



    ● 文件描述符用完 (Running out of file descriptors)
    -------------------------------------------------------------------------------------------------------------------------------------
    由于保持文件打开,在一个载入的集群上用不了多长时间就可能达到系统和 Hadoop 设定的限制。例如,有一个三个节点的集群,每个节点上运行一个
    datanode 实例和一个 regionserver. 并且正在运行一个上传任务,表由 100 区域(regions) 和 10 个列族。允许每个列族平均有两个刷入文件 (flush
    files), 通过计算,需要同时打开 100 × 10 × 2, 或 2,000 个文件。此外,还有各种外部扫描器和 Java 库文件占用的其它文件描述符。每一个打开
    的文件至少在这个远程 datanode 上占用一个文件描述符。
    
    一个进程默认的文件描述符限制是 1024. 当使用的描述符数量超过文件系统的 ulimit 值时,会在日志中看到 “Too many open files” ,但通常会在
    HBase 中首先看到异常行为。要修正这个问题可以增加文件描述符限制数量,通常设置为 10240.



    ● datanode 线程用完 (Running out of datanode threads)
    -------------------------------------------------------------------------------------------------------------------------------------
    类似地,Hadoop datanode 上有一个它可以同时运行的线程数量的上限。这个设置(dfs.datanode.max.xcievers) 在 Hadoop 1 中的默认值为较低的 256
    会导致 HBase 的行为不确定。Hadoop 2 增加了这个默认值为 4096, 因此最近版本的 HBase 可能很少看到这个问题了(运行在 Hadoop 2 及其以后版本上)
    可以通过配置 hdfs-site.xml 文件的 dfs.datanode.max.transfer.threads 属性设置这个值(这个属性的新名称)。
    
    


7.2 UI
-----------------------------------------------------------------------------------------------------------------------------------------
HBase 在 master 上运行一个 web server 用于提供一个集群运行状态的视图。默认情况下,它在 60010 端口上侦听。 master UI 显示一个基本属性列表,
例如软件版本,集群负载,请求速率,集群上表的列表,以及加入的 regionserver 等。在 master UI 上单击一个 regionserver, 会进入到运行在单个
regionserver 上运行的 web server. 它会列出这个服务器上的分区及其它基本度量信息,如资源消耗和请求率。




7.3 度量 (Metrics)
-----------------------------------------------------------------------------------------------------------------------------------------
Hadoop 有一个度量系统(metrics system) 可用于每过一段时间获取系统的重要环境信息。启用 Hadoop 度量系统,并把它捆绑到 Ganglia 或发送到 JMX,
会得到集群上正在发生状况的视图。HBase 也有它自己的度量(比如请求频率、组件计数、资源使用情况等),相关信息可以参考 HBase 的 conf 目录下的
hadoop-metrics2-hbase.properties 文件。



7.4 计数器 (Counters)
-----------------------------------------------------------------------------------------------------------------------------------------
在 StumbleUpon, 第一个在 HBase 上部署的产品特性是为 stumbleupon.com 前端维护计数器。计数器以前存储在 MySQL 中,但计数器的更新太频繁,计数
器所导致的写操作太多,所以 web 设计者必须对计数器的值进行限定。使用 HTable 的 incrementColumnValue() method 后,计数器每秒可以实现数千次
更新。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值