整套大数据学习资料(视频+笔记)百度网盘无门槛下载:http://www.edu360.cn/news/content?id=3377

第十三章 关于HBase

hadoop 花牛 35℃ 0评论

关于HBase


13.1 HBase 基础


HBase是一个在HDFS上开发的面向列的分布式数据库。如果需要实时地随机访问超大规模数据集,就可以使用HBase这一Hadoop应用。

虽然数据库存储和检索的实现可以选择很多不同的策略,但是绝大多数解决办法一特别是关系数据库技术的变种——不是为大规模可伸缩的分布式处理设计的。很多厂商提供了复制(replication)和分区(partitioning)解决方案,让数据库能够从单个节点上扩展出去,但是这些附加的技术大都属于“事 后”的解决办法,而且非常难以安装和维护。并且这些解决办法常常要牺牲一些重要的RDBMS特性。在一个“扩展的” RDBMS上,连接、复杂查询、触发器、视图以及外键约束这些功能要么运行开销大,要么根本无法用。

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

HBase的一个典型应用是wefcteWe, —个以网页URL为主键的表,其中包 含爬取的页面和页面的属性(例如语言和MIME类型)。webtable非常大,行 数可以达十亿级(billion)之级。在webtable上连续运行用于批处理分析和解 析的MapReduce作业,能够获取相关的统计信息,增加验证的MIME类型 列以及供捜索引擎进行索引的解析后的文本内容。同时,表格还会被以不同运行速度的“爬取器”(crawler)随机访问并随机更新其中的行,在用户点 击访问网站的缓存页面时,需要实时地将这些被随机访问的页面提供给 他们。

在本章中,我们将介绍如何使用HBase。要想了解更多信息,请参考Lars George所著的《HBase权威指南》。


背景


HBase 项目是由 Powerset 公司的 Chad Walters 和Jim Kelleman 在 2006 年末 发起的。当时,它起源于GoogleChang等人发表的论文“大表:结构化 数据的分布存储系统”(Bigtable: A Distributed Storage System for Structured Date),网址为http://labs.goole.com/papers.bigtable.hrml2007 2月, Mike Cafarella提供代码,形成了一个基本可以用的系统,然后Jim Kellerman接手继续推进项目。

HBase的第一个发布版本是在200710月和Hadoop 0.15.0捆绑在一起发布的。2010年5月,HBaseHadoop子项目升级成Apache顶层项目。 HBase的产品用户包括AdobeStumbleUponTwitter和雅虎的一些

小组。


13.2概念


在本节中,我们只对HBase的核心概念进行快速、简单的介绍。掌握这些概念至少有助于消化后续内容。

13.3.1数据模型的“旋风之旅”

应用把数据存放在带标签的表中。表由行和列组成。表格的“单元格”cell) 由行和列的坐标交叉决定,是有版本的。默认情况下,版本号是自动分配的,为HBase插入单元格时的时间戳。单元格的内容是未解释的字节数组。

表中行的键也是字节数组。所以理论上,任何东西都可以通过表示成字符串或将二进制形式转化为长整型或直接对数据结构进行序列化,来作为键值。表中的行根据行的键值(也就是表的主键)进行排序。排序根据字节序进行。所有对表的访问都要通过表的主键。

行中的列被分成“列族”(column family)。同一个列族的所有成员具有相同 的前缀。因此,像列 和化都是列族化的成员,而station:identifier则属于station族。列族的前缀必须由“可打印的”(printable)字符组成。而修饰性的结尾字符,即列族修 饰符,可以为任意字节。

一个表的列族必须作为表模式定义的一部分预先给出。但是新的列族成员可以随后按需要加入。例如,只要目标表中已经有了列族station,那么客户 端就可在更新时提供新的列并存储它的值。

物理上,所有的列族成员都一起存放在文件系统中。所以,虽然我们前面 HBase描述为一个面向列的存储器,但实际上更准确的说法是:它是一 个面向列族的存储器。由于调优和存储都是在列族这个层次上进行的,所 以最好使所有列族成员都有相同的“访问模式access pattern)和大小特征。

简而言之,HBase表和RDBMS中的表类似,单元格有版本,行是排序的, 而只要列族预先存在,客户端随时可以把列添加到列族中去。

1.区域

HBase自动把表水平划分成“区域”region)。每个区域由表中行的子集构成。每个区域由它所属于的表、它所包含的第一行及其最后一行(不包括这行)来表示。一开始,一个表只有一个区域。但是随着区域开始变大,等到 它超出设定的大小阈值,便会某行的边界上把表分成两个大小基本相同 的新分区。在第一次划分之前,所有加载的数据都放在原始区域所在的那 台服务器上。随着表变大,区域的个数也会增加。区域是在HBase集群上 分布数据的最小单位。用这种方式,一个因为太大而无法放在单台服务器 上的表会被放到服务器集群上,其中每个节点都负责管理表所有区域的一个子集。表的加载也是使用这种方法把数据分布到各个节点。在线的所有 区域按次序排列就构成了表的所有内容。

2.加锁

无论对行进行访问的事务牵涉多少列,对行的更新都是“原子的” (atomic)。这使得“加锁模型locking model)能够保持简单。

13.3.2 实现

正如HDFSMapReduce由客户端、从属机(slave)和协调主控机(master) (即 HDFS 的 namenode datanode,..以及 MapReduce jobtrackertasktracker)——组成,HBase也采用相同的模型,它用一个master节点 协调管理一个或多个regionserver从属机(参见图13-1)。HBase主控机 (master)负责启动(bootstrap) —个全新的安装,把区域分配给注册的 regionserver,恢复 regionserver 的故障。master 的负载很轻。regionserver 负责零个或多个区域的管理以及响应客户端的读写请求。regionserver还负 责区域的划分并通知HBase master有了新的子区域(daughter region),这样 主控机就可以把父区域设为离线,并用子区域替换父区域。

 

 image.png

13-1. HBase集群的成员


HBase依赖于ZooKeeper(参见第14章)。默认情况下,它管理一个ZooKeeper实例,作为集群的“权威机构”authority)。HBase负责根目录表(root catalog table)的位置、当前集群主控机地址等重要信息的管理。如果 在区域的分配过程中有服务器崩溃,就可以通过ZooKeeper来进行分配的 协调。在ZooKeeper上管理分配事务的状态有助于在恢复时能够从崩溃服 务器遗留的状态开始继续分配。在启动一个客户端到HBase集群的连接 时,客户端必须至少拿到到集群所传递的ZooKeeper集合体(ensemble)的位 置。这样,客户端才能访问ZooKeeper的层次结构,从而了解集群的属性,例如服务器的位置。

类似于在Hadoop中可以通过文件查看datanode和tasktracker, regionserver从属机节点列在HBase的文件中。启动和结束服务的脚本和Hadoop类似,使用相同的基于SSH的机制来运行远程命 令。集群的站点配置(site-specific configuration)在 HBase的conf/hbase-site.xml和conf/hbase-env.sh文件中。它们的格式和Hadoop项目中对应的格式相同(参见第9章)。

对于HBaseHadocjp上相同的服务或类型HBase实际上直接使用 或继承Hadoop的实现。在无法直接使用或继承时HBase会尽量遵 循 Hadoop 的模型。例如,HBase 使用 Hadoop Configuration 系统,所以它们的配置文件格式相同。对于作为用户的你来说,这意味着 你使用HBase时感觉就像使用Hadoop —样“亲切”。HBase只是在 增加特殊功能时才不遵循这一规则。

HBase通过Hadoop文件系统API来持久化存储数据。有多种文件系统接口的实现:一种用于本地文件系统;一种用于KFS文件系统Amazon S3 HDFS。多数人使用HDFS作为存储来运行HBase。但是,在默认情况 下,除非另行指明,HBase'將存储写入本地文件系统。如果是体验一下 新装HBase,这是没有问题的,但如果稍后要使用HBase集群,首要任务 通常是把HBase的存储配置为指向所要使用的HDFS集群。

运行中的HBase

HBase内部保留名为R00T-和.META.的特殊目录表(catalog table)。它们维护着当前集群上所有区域的列表、状态和位置。ROOT表包含.META.表的区域列表。.META.表包含所有用户空间区域(user-space region)的列表。表 中的项使用区域名作为键。区域名由所属的表名、区域的起始行、区域的 创建时间以及对其整体进行的MD5哈希值(即对表名、起始行、创建的时 间戳进行哈希后的结果)组成。如前所述,行的键是排序的。因此,要査找一个特定行所在的区域只要在目录表中找到第一个键大于或等于给定行 键的项即可。区域变化时——即分裂、禁用/启用(disable/enable)、删除、为负载均衡重新部署区域或由于regionserver崩溃而重新部署区域时目录表会进行相应的更新。这样,集群上所有区域的状态信息就能保持是最新的。

新连接到ZooKeeper集群上的客户端首先查找-ROOT-的位置。然后客户端通过-ROOT-获取所请求行所在范围所属.META.区域的位置。客户端接着 査找.META.区域来获取用户空间区域所在节点及其位置。接着,客户端就 可以直接和管理那个区域的regionserver进行交互。

每个行操作可能要访问三次远程节点。为了节省这些代价,客户端会缓存 它们遍历ROOT时获取的信息和.META.位置以及用户空间区域的开始行和结束行。这样,它们以后不需要访问.META.表也能得知区域存放的位置。客户端在碰到错误之前会一直使用所缓存的项。当发生错误时一一即区 域被移动了——客户端会再去査看.META.获取区域的新位置。如果.META.区域也被移动了,客户端会再去査看ROOT-。

到达Regionserver的写操作首先被追加到“提交日志commit log)中,然 后被加入内存中的memstore。如果memstore满,它的内容会被“刷入”(flush)文件系统。

提交日志存放在HDFS中,因此即使一个regionserver崩溃,提交日志仍然 可用。如果发现一个regionserver不能访问(通常因为服务器的znode ZooKeeper中过期)主控机会根据区域对死掉的regionserver的提交日志进行 分割。重新分配后,在打开并使用死掉的regionserver上的区域之前,这些 区域会找到属于它们的从被分割提交日志中得到的文件,其中包含还没有 被持久化存储的更新。这些更新会被“重做”replay)以使区域恢复到服务 器失败前的状态。

在读的时候首先査看区域的memstore。如果在memstore中找到了需要的版 本,査询就结束了。否则,需要按照次序从新到旧检査“刷新文件”(flush file),直到找到满足査询的版本,或所有刷新文件都处理完为止。

有一个后台进程负责在刷新文件个数到达一个阈值时压缩它们。它把多个 文件重新写入一个文件。这是因为读操作检査的文件越少,它的执行效率 越高。在压缩(compaction)时,进程会清理掉超出模式所设最大值的版本以 及删除单元格或标识单元格为过期。在regionserver上,另外有一个独立的 进程监控着刷新文件的大小,一旦文件大小超出预先设定的最大值,便对区域进行分割。

13.3安装

挑选一个 Apache Download Mirror(Apache 下载镜像),下载一个 HBase的稳定发布版本,然后在本地文件系统解压。示例如下:

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

和用Hadoop —样,首先需要告诉HBase系统中的Java在哪里。如果设置 了JAVA_HOME环境变量,把它指向了正确的Java安装,HBase就会使用那 Java安装。这样便不需要进行其他配置。否则,可以通过编辑HBase ]AVA_HOME变量指向Java 1.6.0版本(参见附录A的示例),从而设置HBase所使用的Java安装。

  和 Hadoop—HBase 需要使用 Java 6

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

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

要想获取HBase的选项列表,输入以下命令即可:

% hbase
Usage: hbase <command> 
where <command> is one of:
shell run the HBase shell
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 an HBase Thrift server 
avro         run an HBase Avro server 
migrate      upgrade an hbase.rootdir
hbck         run the hbase 'fsck' tool
or
CLASSNAME
Most commands print help when invoked w/o parameters.

测试驱动

要启动一个使用本地文件系统目录作为持久化存储的HBase临时实例,键入以下命令:

% start-hbase.sh

这会启动一个独立(standalone)HBase实例。它使用本地文件系统作为持久化存储。默认情况下,HBase会使用/tmp/hbase-${USERID}。

要管理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.89.0-SNAPSHOT, ra4eala9a7b074a2e5b7b24f761302d4ea28edlb2, Sun Jul 18 15:01:50 PDT 2010 hbase(main):001:0>

这将启动一个加入了一些HBase特有命令的JRubyRB解释器。输入help 然后按RETURN键可以查看已分组的外壳环境的命令列表。输入help COMMAND_GROUP可以査看某一类命令的帮助,而输入help COMMAND则能 获得某个特定命令的帮助信息和用法示例。命令使用Ruby的格式来指定列表和目录。主帮助屏幕的最后包含一个快速教程。

现在,让我们创建一个简单的表,添加一些数据,再把表清空。

要新建一个表,首先必须为你的表起一个名字,并为其定义模式。一个表的模式包含表的属性和列族的列表。列族本身也有属性。可以在定义模式 时依次定义它们。例如,列族的属性包括列族是否应该在文件系统中被压 缩存储,一个单元格要保存多少个版本等。模式可以被修改,需要修改时把表设为“离线”offline)即可。在外壳环境中使用disable命令可以把表设为离线,使用alter命令可以进行必要的修改,而enable命令则可以 把表重新设为“在线”(online)。

要想新建一个名为test的表,使其只包含一个名为data的列,表和列族 属性都为默认值,则键入以下命令:

hbase(main):007:0> create 'test', 'data'
0 now(s) in 1.3066 seconds

如果前面有命令没有成功完成,那么外壳环境会提示错误并显示堆 栈跟踪stack trace)信息。这时你的安装肯定没有成功。请检査 HBase日志目录中的主控机日志,査看哪里出了问题。默认的日志 巨录是:${HBASEHOME}/logs

关于如何在定义模式时添加表和列族属性的示例,可参见help命令的输出。为了验证新表是否创建成功,运行list命令。这会输出用户空间中的所有表:

hbase(main):019:0> list 
test
1 row(s) in 0.1485 seconds

要在列族data中三个不同的行和列上插入数据,然后列出表的内容,输入如下:

hbase(main):021:0> put 'test', 'rowl', 'datail1, 'valuel'
0 row(s) in 0.0454 seconds
hbase(main):022:0> put 'test', 'row2', 'data:2', 'value2'
0 row(s) in 0.0035 seconds
hbase(main):023:0> put 'test', 'row3', ,data:3,,> 'value3'
0 row(s) in 0.0090 seconds hbase(main):024:0> scan 'test'
ROW COLUMN+CELL _
rowl column=data:1, timestamp=1240148026198J value=valuel
row2 column=data:2, timestamp=1240148040035, value=value2
row3 column=data:3, timestamp=1240148047497, value=value3
3 row(s) in 0.0825 seconds

请注意我们是如何在添加三个新列的时候不修改模式的。

为了移除这个表,首先要把它设为禁用,然后删除:

hbase(main):025:0> disable 'test'
09/04/19 06:40:13 INFO client.HBaseAdmin: Disabled test 0 row(s) in 6.0426 seconds hbase(main):026:0> drop 'test'
09/04/19 06:40:17 INFO client.HBaseAdmin: Deleted test
 
0 row(s) in 0.0210 seconds hbase(main):027:0> list 0 row(s) in 2.0645 seconds

通过运行以下命令来关闭HBase实例:

% stop-hbase.sh

要想了解如何设置分布式的HBase,并把它指向正运行的HDFS,请参见HBase文档中的 Getting Started 小节。

13.4客户端

HBase集群进行交互,有很多种不同的客户端可供选择。

13.4.1 Java

HBaseHadoop —样,都是用Java开发的。范例13-1展示了 13.3.1节中

外壳环境操作的hva实现版本。

范例13-1.基本的表管理与访问

public class ExampleClient {
    public static void main(String[] args) throws IOException {
    Configuration config = HBaseConfiguration.create();
    // Create table
    HBaseAdmin admin = new HBaseAdmin(config);
    HTableDescriptor htd = new HTableDescriptor("test");
    HColumnDescriptor hcd = new HColumnDescripton("data");
    htd.addFamily(hcd);
    admin.createTable(htd);
    byte [] tablename = htd.getName();
    HTableDescriptor [] tables = admin.listTables();
    if (tables.length != 1 && Bytes.equals(tablename, tables[0].getName())) { 
        throw new IOException("Failed create of table");
    }
    // Run some operations --a put,a get, and a scan --against the table. 
    HTable table = new HTable(config, tablename); 
    byte [] rowl = Bytes.toBytes("rowl");
    Put pi = new Put(rowl);
    byte [] databytes = Bytes.oBytes(” data” );
    pi.add(databytes, Bytes.toBytes("1"), Bytes.toBytes("valuel")); 
    table.put(pi);
    Get g = new Get(rowl);
    Result result = table.get(g);
    System.out.println("Get: " + result);
    Scan scan = new Scan();
    ResultScanner scanner = table.getScanner(scan); 
    try {
        for (Result scannerResult: scanner) {
            System.out.println(v Scan: ” + scannerResult);
        }
    } finally {
        scanner.close();
    }
    // Drop the table
    admin.disableTable(tablename);
    admin.deleteTable(tablename);
    }
}

这个类只有一个主方法。为了简洁,我们没有给出导入包的信息。在这个类中,我们首先创建了一个 of'g.apache.hadoop.conf.Configuration 实例。我们让 or'g.apache.hadoop.hbase.HBaseConfiguration 类来创 建这个实例。这个类会返回一个Configuration,其中已经读入了程序 classpath下hbase-site.xml和hbase-default.xml文件中的HBase配置信息。 这个Configuration接下来会被用于创建HBaseAdminHTable实例。 HBaseAdmin HTable 这两个类在 Java  org.apache.hadoop. hbase.client包中。HBaseAdmin用于管理HBase集群,添加和丢弃表。 HTable则用于访问指定的表。Configuration实例将这些类指向了执行这些代码的集群。

要创建一个表,我们需要首先创建一个HBaseAdmin的实例,然后让它来 创建名为test、只有一个列族data的表。在我们的示例中,表的模式是默认的。可以使用 org.apache.hadoop.hbase.HTableDescripttor  org.apache.hadoop. hbase JColumnDescriptor 中的方法来修改表的模式。接下来的代码测试了表否真的被创建了。接着,程序对新建的表进行操作。

要对一个表进行操作,我们需要新建一个org. apache, hadoop. hbase.client.HTable的实例,并把我们的Configuration实例和我们要操作的表的名称传递给它。在新建HTable以后,我们新建一个 org.apache.hadoop.hbase.client 的实例Put 把单个的单元格值valuel 放入名为rowl的行的名为data:l的列上。注意:列名通过两部分指定,列 族名是字节类型的——在前面代码中是databytes——而接着列族修饰词用 Bytes.toBytes("l")。接着,我们新建一个 org.apache.hadoop. hbase.


client.Get来获取刚添加的单元格。然后,再使用一个org.apache.hadoop. hbase. client .Scan来扫描新建的表,并打印扫描结果。

最后,我们首先禁用表,接着删除它,把这张表清除掉。在丢弃表前必须先禁用它。

MapReduce

org.apache.hadoop.hbase.mapreduce包中的类和工具有利于将 HBase 作为MapReduce作业的源/输出。TablelnputFormat类可以在区域的边界进行分割,使map能够拿到单个的区域进行处理。TableOutputFormat reduce的结果写入HBase。范例13-2中的ROwCounter类可以在HBase mapreduce包中找到。它用TablelnputFormat来运行一个map任务,以计算行数。

范例13-2.—个计算HBase表中行数的MapReduce应用程序

public class RowCounter {
    /** Name of this 'program'. */
    static final String NAME = "rowcounter";
    static class RowCounterMapper
    extends TableMapper<ImmutableBytesWritable, Result〉 {
    /** Counter enumeration to count the actual rows. */ 
    public static enum Counters {ROWS}
        @Override
        public void map(ImmutableBytesWritable row, Result values,
        Context context)
        throws IOException {
            for (KeyValue value: values.list()) { 
                if (value.getValue().length > 0) { 
                    context.getCounter(Counters.ROWS).increment(l); break;
                }
            }
        }
    }
    public static Job createSubmittableJob(Configuration conf, String[] args) 
    throws IOException {
    String tableName = args[0];
    Job job = new Job(conf, NAME + tableName)j
    job.setDarByClass(RowCounter.class);
    // Columns are space delimited 
    StningBuilder sb = new StringBuilden(); 
    final int columnoffset = 1;
    for (int i = columnoffset; i < args.length; i++) { 
        if (i > columnoffset) { 
            sb.append(w w );
        }
        sb.append(args[i]);
    }
    Scan scan = new Scan();
    scan.setFilter(new FirstKeyOnlyFilter());
    if (sb.length() > 0) {
        for (String columnName :sb.toStning().split(M M )) {
            String [] fields = columnName.split(M M ); 
            if(fields.length == 1) {
                scan.addFamily(Bytes.toBytes(fields[0]));
            } else {
            scan.addColumn(Bytes.toBytes(fields[0]), Bytes.toBytes(fields[1]));
            }
        }
    }
    // Second argument is the table name.
    job.setOutputFormatClass(NullOutputFormat.class);
    TableMapReduceUtil.initTableMapper3ob(tableName, scan,
        RowCounterMapper.class, ImmutableBytesWritable.class, Result.class^ job); 
    job.setNumReduceTasks(0);
    return job;
    }
    public static void main(String[] args) throws Exception {
    Configuration conf = HBaseConfiguration.create();
    String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs(); 
    if (otherArgs.length < 1) {
        System.err.println(M ERROR: Wrong number of parameters: M + args.length); 
        System.err.println(M Usage: RowCounter <tablename> [<columnl> <column2>...]M ); 
        System.exit(-1) j
    }
    Dob job = createSubmittableJob(conf, otherArgs);
    System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

    

这个类使用了 5.2.2节介绍的GenericOptionParser,用于解析命令行参数。内部类RowCounterMapper实现了HBaseTableMapper抽象。后者是org.apache. hadoop.mapneduce.Mapper “特化”specialization)。它设 map的输入类型由 TablelnpytFormat 来传递。createSubmittable]ob() 方法对添加到配置的参数进行解析。配置由命令行传递,指出我们要运行 RowCounter 的表和列。列名用于配置 org.apache.hadoop.hbase. client.Scan的实例。这是一个扫描用的对象。它将被传递给 TablelnputFormat,并用干限制Mapper所能看到的信息。注意这里是如 何设置扫描的过滤器(即org.apache.hadoop.hbase.filter. FirstKeyOnlyFilter的实例)的。这个过滤器告诉服务器,让它在运行服 务器端任务时只需要验证一行是否有数据项。这样可以加快行计数的速度。createSubmittable]ob()方法为了设置所使用的map类、设定输入 格式为 TablelnputFormat 还调用了TableMapReduceUtil.


initTableMap]ob()这一工具方法(utility method)。map 很简单。它只测试行为否是空。如果为空,就不对行进行计数。否则,使Counters.ROWS增1。


13.4.1 Avro、REST 和 Thrift


HBase提供了 Avro, RESTThrift接口。在使用Java以外的编程语言和 HBase交互时,会用到这些接口。在所有情况下,java服务器上都运行着 一个HBase客户端实例,它负责协调AvroRESTThrift应用请求和 HBase集群间的双向交互。由于需要代理这些请求和响应工作,因此使用这些接口比直接使用Java客户端更慢。


1. REST服务器 


如果要启动一个实例(stargateHBase REST服务的名称),可以运行下面的命令:

% hbase-daemon.sh start rest

这将启动一个服务器实例,默认情况下使用端口号8080,在后台运行,并捕捉HBase logs识目录下日志文件中服务器的任何动静。

客户端可以要求响应以JSON格式、Googleprotobuf格式或XML格式输出。采用哪种格式取决于客户端的外头的设置。如何进行REST 客户端请求的文档和示例请参见REST的英文维基页面。

要停止REST服务器,键入:

% hbase-daemon.sh stop rest

2. Thrift服务器

同样,可以运行以下命令启动Thrift客户端的服务器,从而启动Thrift服务:

% hbase-daemon.sh start thrift

这将启动一个在后台运行的服务器实例,默认端口号为9090,捕捉HBase logs目录下日志文件中的服务器的任何动静。HBase Thrift文档说明了Thrift版本所使用的生成类HBase Thrift IDL可以在HBase源代码的 src/main/resources/org/apache/hadoop/hbase/thrift/Hbase.thift 中找到。

要终止Thrift服务器,键入以下命令即可:

% hbase-daemon.sh stop thrift

3. Avro服务器

Avro服务器的启动和终止与启动和终止ThriftREST服务方式相同。Avro服务器在默认情况下使用端口号9090(和Thrift服务器相同。你一般不会同时运行它们)。

13.5 示例

虽然HDFSMapReduce是用于对大数据集进行批处理的强大工具,但对于读或写单独的记录,效率却很低。在这个示例中,我们将看到如何用 HBase来填补它们之间的鸿沟。

前面几章描述的气象数据集包含过去100多年上万个气象站的观测数据。 这个数据集还在继续增长,它的大小几乎是无限的。在这个示例中,我们将构建一个Web界面来让用户査看不同观测站的数据。这些数据按照时间 顺序分页显示。为此,让我们假设数据集非常大,观测数据达到上亿条记录,且气温更新数据到达的速度很快——比如从全球观测站收到超过每秒几 百到几千次更新。不仅如此,我们还假设这个Web应用必须能够及时(most uptodate)显示观测数据,即在收到数据后大约1秒就能进行显示。

对数据集的第一个要求使我们排除了使用RDBMSHBase是一个可能的选择。对于查询延时的第二个要求排除了直接使用HDFSMapReduce作业可 以用于建立索引以支持对观测数据进行随机访问,但HDFSMapReduce 并不擅长在有更新到达时维护鸾引。

13.5.1模式

在我们的示例中,有两个表。

1. stations

这个表包含观测站数据。行的键是stationid。这个表还有一个列族info,它能作为键/值字典来支持对观测站信息的査找。字典的键就是列名 info:nameinfo:location以及 info:description。这个表是静态的,在这里,列族h/o的设计类似于RDBMS中表的设计。

2. observations


这个表存放气温观测数据。行的键是stationid和逆序时间戳构成的组合键。这个表有一个列族data,它包含一列airtemp,其值为观测到的气温值。

我们对模式的选择取决于我们知道最高效的读取HBase的方式。行和列以 字典序升序保存。虽然有二级索引和正则表达式匹配工具,但它们会损失 其他性能。清楚地理解查询数据最高效的方式对于选择最有效的存储和访 问数据的设置非常关键。

stations表中,显然选择stationid作妁键,因为我们总是根据特定站点的ID来访问观测站的信息。但observations表使用的是一个组合键 (把观测的时间戳加在键之后)。这样,同一个观测站的观测数据就会被分组 放到一起,使用逆序时间戳(Long.MAX_VALUE – epoch)的二进制存储,系统把每个观测站观测数据中最新的数据存储在最前面。

在外壳环境中,可以用以下方法来定义表:

hbase(main):036:0> create 'stations', {NAME => 'info', VERSIONS => 1}
0 row(s) in 0.1304 seconds
hbase(main):037:0> create 'observations' , {NAME => 'data', VERSIONS => 1}
0 row(s) in 0.1332 seconds

在两个表中,我们都只对表单元格的最新版本感兴趣,所以VERSIONS设为1(默认值是3)。

13.5.2加载数据

观测站的数量相对较少,所以我们可以使用任何.种接口来插入这些观测站的静态数据。

但是,假设我们要加载数十亿条观测数据。这种数据导入是一个极为复杂 的过程,是一个需要长时间运行的数据库操作。MapReduceHBase的分布式模型让我们可以充分利用集群。通过把原始输入数据复制到HDFS,接着运行MapReduce作业,我们就能读到输入数据并将其写入HBase

范例13-3展示了一个MapReduce作业,它将观测数据从前几章所用的输入文件导入HBase


范例13-3.HDFSHBase表导入气温数据的MapReduce应用

public class HBaseTemperaturelmporter extends Configured implements Tool {
    // Inner-class for map
    static class HBaseTemperatureMapper<K> V> extends MapReduceBase implements
            Mapper<LongWritableJ Text, K, V>{
        private NcdcRecordParser parser = new NcdcRecordPanser(); 
        private HTable table;
        public void map(LongWritable key, Text value,
            OutputCollector<K, V> output, Reporter reporter) 
        throws IOException {
            parser.parse(value.toString()); 
            if (parser.isValidTemperature()) {
                byte[] rowKey = RowKeyConverter.makeObservationRowKey(parser.getStationId(), 
                    parser.getObservationDate().getTime());
                Put p = new Put(rowKey);
                p.add(HBaseTemperatureCli.DATA_COLUMNFAMILY,
                    HBaseTemperatureCli.AIRTEMP_QUALIFIER,
                    Bytes.toBytes(parser.getAirTemperature())); 
                table.put(p);
            }
        }
        public void configure(JobConf jc) { 
            super.configure(jc);
            // Create the HBase table client once up-front and keep it around 
            // rather than create on each map invocation. 
            try {
                this .table = new HTable(new HBaseConfiguration( jc), "observations"); 
            } catch (IOException e) {
                throw new RuntimeException(” Failed HTable construction”,e);
            }
        }
        @Override
        public void close() throws IOException {
            super.close(); 
            table.close();
        }
    }
    public int run(String[] args) throws IOException { 
        if (args.length != 1) {
            System.err.println(w Usage: HBaseTemperaturelmporter <input>” ); 
            return -1;
        }
        JobConf jc = new JobConf(getConf(), getClass()); 
        FilelnputFormat.addInputPath(jc, new Path(args[0])); 
        jc.setMapperClass(HBaseTemperatureMapper.class); 
        jc.setNumReduceTasks(0);
        jc.setOutputFormat(NullOutputFormat.class);
        DobClient.runJob(jc);
        return(0);
    }
    public static void main(String[] args) throws Exception { 
        int exitCode = ToolRunner.run(new HBaseConfiguration(), 
            new HBaseTemperatureImporter(), args);
        System.exit(exitCode);
    }
}

HBaseTemperatureImporter有一个名为HbaseTemperatureMapper的内部类,它类似于第5章的MaxTemperatureMaper类。外部类实现了 Tool并对调用 HBaseTemperatureMapper 内部类进行设置。HBaseTemperatureMapper  MaxTemperatpreMapper 的输入相同,所进行的解析方法 都使用第5章所介绍的NcdcRecordParser来进行也相同。解析时会检查输入是否为有效的气温。但是不同于在 MaxTemperatureMapper中仅把有效气温加到输出集合中,这个类把有效 的气温值添加到HBaseobservations表的列。(我们使用了从HBaseTemperatureCli类中导出的dataairtemp静态常量。 后面对此会有介绍。)在configure()方法中,我们对observations表新 建了一个HTable实例,在后面调用mapHBase进行交互时会用到它。 最后,在HTable实例中调用close把所有尚未清空的写缓存中的数据刷入磁盘。

我们所用的行的键在 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_epoch>
*/
public static byte[] makeObservationRowKey(String stationId,
    longobservationTime){
byte[]row=newbyte[STATION_ID_LENGTH+Bytes.SIZEOF_LONG];
Bytes.putBytes(row,0,Bytes.toBytes(stationld),0,STATION_ID_LENGTH); longreverseOrderEpoch=Long.MAX_VALUE-observationTime;
        Bytes.putLong(row,STATION_ID_LENGTHJ reverseOrderEpoch);
         returnrow;
        }
    }

观测站ID其实是一个定长字符串。在转换时利用了这一点。 makeObservationRowKey()中使用的Byte类来自HBase工具包,它包含 字节数组和普通的JavaHadoop数据类型间的转换方法。在 makeObservationRowKey()中,Bytes.putLong()方法被用来填充键的字 节数组Bytes.SIZEOF_LONG常量被用来确定数据行键的数组大小和其中元素的位置。

我们可以用下面的命令来运行程序:

% hbase HBaseTemperaturelmporter input/ncdc/all

对优化的几点说明。

要特别当心数据导入所引发的“步调一致”的情况。这时所有的客 户端都对同一个表的区域(在单个节点上)进行操作,然后再对下一个区域进行操作,依次进行。这时加载操作并没有均勻地分布在所 有区域上。这通常是由“排序后输入sorted input)和切分的原理 共同造成的。如果在插入数据前,针对行的键按数据排列的次序进 行随机处理,可能有助于减少这种情况。在我们的示例中,基于当 stationid值的分布情况和TextlnputFormat分割数据的方式,上传操作应该足以保证足够的分布式特性。

•每个任务只获得一个HTable实例。实例化HTable是有代价的,所以如果为每个插入操作实例化一个HTable,会对性能造成负面 影响,因此,我们在configure()步骤设置HTable

•在默认情况下,每个HTable.put(put)在进行插入操作时事实上 不使用任何缓存。可以使用HTable.setAutoFlush(false), 着设置写缓存的大小,以此禁用HTable的自动刷入特性。插入的 数据占满写缓存之后,缓存才会被刷入存储。但要记住,必须在每 个任务的最后手工调用 HTable.flushCommits() HTable.close(), 后者会调用HTable.flushCommits(),以确保缓存中最后没有剩下未被刷入的更新。这可以在mapper重载的close()中完成。

HBase 包含 TablelnputFormat TableOutputFormat它们可 用于把Hbase作为源或目标的MapReduce(参见范例丨3-2)。也可以 像第5章那样使用MaxTemperatureMapper,增加.一个reducer任务 来接收 MaxTemperatureMapper 的输出并通过 TableOutputFormat把结果导入HBase

13.5.3 Web 查询

为了实现Web应用,我们将直接使用HBaseJava API。在这里,我们将 深刻体会到选择模式和存储格式的重要性。

最简单的査询就是获取静态的观测站信息。这一类査洵在传统数据库中也 很简单,但HBase提供了额外的控制功能和灵活性。我们把info列族作为 /值字典(列名作为键,列值作为值),代码如下所示:

public Map<String, String> getStationInfo(HTable table. String stationld) 
    throws IOException {
    Get get = new Get(Bytes.toBytes(stationld)); get.addColumn(INFO_COLUMNFAMILY);
    Result res = table.get(get); 
    if (res == null) { 
        return null;
    }
    Map<String, String〉resultMap = new HashMap<String, String>(); 
    resultMap.put(” name” , getValue(res, INFOJIOLUMNFAMILY, 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 。它被设置为用于获取已定义列族INFO_COLUMNFAMILY中由观测站ID

所指明的列的值。

get()的结果返回给Result。它包含数据行,你可以通过操作需要的列单


元格来取得单元格的值。getStationInfo()方法把Result Map转换为更 便于使用的由String类型的键和值构成的Map

我们已经看出在使用HBase时为什么需要工具函数了。在HBase上,为了 处理底层的交互,我们已经开发出越来越多的抽象。但是,理解它们的工 作机理以及各个存储选项之间的差异,非常重要。

和关系型数据库相比,HBase的优势之一是不需要我们预先设定列。所以 在将来,如果每个观测站在这三个必有的属性以外还有几百个可选的属 性,我们便可以直接插入这些属性而不需要修改模式。当然,你的应用中 读和写的代码是需要修改的。在示例中,我们可以循环遍历Result来获取 每个值,而不用显式获取各个值。

我们将在Web应用中使用HBase扫描器(scanner)来检索观测数据。

在这里,需要的是Map<ObservationTime, ObservedTemp>结果。我们将 使用NavigableMap<Long, Integer〉,因为它提供的结果是有序的,且 有一个descendingMap()方法。这样,我们既可以用升序也可以用降序来 访问观测数据了。代码如范例13-4所示。

范例13-4.检索HBase表中某范围内气象站观测数据行的方法

public NavigableMap<Long, Integer〉 getStationObsenvations(HTable table, 
    String stationld, long maxStamp, int maxCount) throws IOException { 
    byte[] startRow = RowKeyConverter.makeObservationRowKey(stationId, maxStamp); 
    NavigableMap<Longi Integer〉 resultMap = new TreeMap<LongJ Integer>();
    Scan scan = new Scan(startRow);
    scan.addColumn(DATA_COLUMNFAMILY, AIRTEMP_QUALIFIER);
    ResultScanner scanner = table.getScanner(scan);
    Result res = null; 
    int count = 0; 
    try {
    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.tolnt(value); 
            resultMap.put(stamp, temp);
        }
    } finally {
        scanner.close();
    }
    return resultMap;
}
public NavigableMap<Long, Integer〉 getStationObservations(HTable table, 
    String stationld) throws IOException { 
    return getStationObservations(table, stationld, Long.MAX_VALUE, 10);

getStationObservations()方法接受观测站ID、由maxStamp定义的范 围以及最大数据行数(maxCount)作为参数。注意,返回的NavigableMap 实际上是按降序排列的。如果想以升序读它,需要使用NavigableMap. descendingMap()

扫描器

HBase扫描器(scanner)和传统数据库中的游标(cursor)Java中的迭代器 (iterator)类似。它和后者的不同在于在使用后需要关闭。扫描器按次序返 回数据行。用户使用已设置的Scan对象实例作为scan参数,调用 HTable.getScanner(scan),以此来获取HBase中一个表上的扫描器。 通过Scan实例,你可以传递担描开始位置和结束位置的数据行、返回结 果中要包含的列以及(可选的)运行在服务器端的过滤器。ResultScanner 接口是调用HTable.getScanner()时返回的接口。它的定义如下:

public interface ResultScanner extends Closeable, Iterable<Result> { 
    public Result next() throws IOException; 
    public Result [] next(int nbRows) throws IOException; 
    public void close();
}

可以查看接下来的一个或多个数据行。每次调用next()都会访问一次 regionserver.因此,一次获取多行可以显著提升性能。

在前面的示例中,把数据存成Long.MAX_VALUE – stamp,其优势还不是

特别明显。如果要根据“偏移量”offset)“限制范围”limit)来获取最新 观测数据,这种存储方式就显得更为有用。而这种查询在Web应用中是屡 见不鲜的。如果观测数据直接用实际的时间戳来存放,我们就只能根据偏 移量和限制范围高效地获取最老的观测数据。要获取最新的数据意味着要 拿到所有的数据,直到最后才能获得结果。从RDBMS转向HBase的一个主要原因就是HBase允许这种“提早过滤”early-out)情况。

13.5 HBase  RDBMS 的比较

HBase和其他面向列的数据库常常被拿来和更流行的传统关系数据库(或简 写为RDBMS)进行比较。虽然它们在实现和设计上的出发点有着较大的区别,但它们都力图解决相同的问题。所以,虽然它们有很多不同点,但我 们仍然能够对它们进行客观的比较。

如前所述,HBase是一个分布式的、面向列的数据存储系统。它通过在 HDFS上提供随机读写来解决Hadoop不能处理的问题。HBase自底层设计开始即聚焦于各种可伸缩性问题:表可以很“高”(数十亿个数据行)表可 以很“宽”(数百万个列);水平分区并在上千个普通商用机节点(commodity node)上自动复制。表的模式是物理存储的直接反映,使系统有可能提供高效的数据结构的序列化、存储和检索。但是,应用程序的开发者必须承担 重任,选择以正确的方式使用这种存储和检索方式。 严格来说,RDBMS是一个遵循Codd12条规则”Codd’s 12 Rules) 数据库。标准的RDBMS是模式固定、面向行的数据库且具有ACID性质和 复杂的SQL査询处理引擎。RDBMS强调事务的“强一致性strong consistency)、参照完整性(referential integrity)、数据抽象与物理存储层相对 独立,以及基于SQL语言的复杂査询支持。在RDBMS中,可以非常容易 地建立“二级索引secondary index),执行复杂的内连接和外连接,执行 计数、求和、排序、分组等操作,或对表、行和列中的数据进行分页存放。

对于大多数中小规模的应用,如MySQLPostgreSQL之类现有开源 RDBMS解决方案所提供的易用性、灵活性、产品成熟度以及强大、完整的 功能特性几乎是无可替代的。但是,如果要在数据规模和并发读写这两方 面中的任何一个(或全部)上进行大规模向上扩展(scale up),就会很快发现 RDBMS的易用性会让你损失不少性能,而如果要进行分布式处理,更是非常困难。RDBMS的扩展通常要求打破Codd的规则,如放松ACID的限制,使DBA的管理变得复杂,并同时放弃大多数关系型数据库引以为荣的易用性。


13.6.1成功的服务

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

(1) 服务首次提供公开访问。

将服务从本地工作站迁移到拥有良好模式定义的、共享的远程MySQL实例上。 

(2) 服务越来越受欢迎;数据库收到太多的读请求

memcached来缓存常用査询结果。这时读不再是严格意义上的ACID;缓存数据必须在某个时间到期。

(3) 对服务的使用继续增多;数据库收到太多的写请求。

通过购买一个16核、128 GB RAM、配备一组15k RPM硬盘驱动器的增强型服务器来垂直升级MySQL。非常昂贵。

(4) 新的特性增加了查询的复杂度;包含很多连接操作。

对数据进行反规范化以减少连接的次数。(这和DBA培训时所教的不一样!)

(5) 服务被广泛使用;所有的服务都变得非常慢。

停止使用任何服务器端计算(server-side computation)。

(6) 有些查询仍然太慢。

定期对最复杂的查询进行“预物化”prematerialize),并尝试在大多数情况下停止使用连接。

(7) 读性能尚可,但写仍然越来越慢。

放弃使用二级索引和触发器。(没有索引了?)

迄今为止,如何解决以上扩展问题并没有一个清晰的解决办法。无论怎 样,都需要开始横向进行扩展。可以尝试在大表上进行某种分区或查看一 些能提供多主控机的商业解决方案。


无数应用、行业以及网站都成功实现了 RDBMS的可伸缩性、容错和分布 式数据系统。它们都使用了前面提到的很多策略。但最终,你所拥有的已 经不再是一个真正的RDBMS。由于妥协和复杂性问题,系统放弃了很多易 用性特性。任何种类的从属复本或外部缓存都会对反规范化的数据引入弱 一致性(weak consistency)。连接和二级索引的低效意味着绝大多数査询成为 主键査找。而对于多写入机制(multiwriter)的设置很可能意味着根本没有实 际的连接,而分布式事务会成为一个噩梦。这时,要管理一个单独用于缓 存的集群,网络拓扑会变得异常复杂。即使有一个做了那么多妥协的系 统,你仍然情不自禁会担心主控机崩溃或担心在几个月后,数据或负载可 能会增长到当前的10倍。

13.6.2 HBase

让我们考虑HBase,它具有以下特性。

没有真正的索引行是顺序存储的,每行中的列也是,所以不存在 索引膨胀的问题,而且插入性能和表的大小无关。

自动分区在表增长的时候,表会自动分裂成区域,并分布到可用 的节点上。

线性扩展和对于新节点的自动处理增加一个节点,把它指向现有 集群并运行regionserver。区域自动重新进行平衡,负载均匀分布。

普通商用硬件支持集群可以用1000~5000美金的单个节点搭 建,而不需要使用单个得花5万美金的节点。RDBMS需要支持大量I/O,因此要求更昂贵的硬件。

容错大量节点意味着每个节点的重要性并不突出。不用担心单个节点失效。

批处理MapReduce集成功能使我们可以用全并行的分布式作业根据“数据的位置location awareness)来处理它们。

如果你没日没夜地担心数据库(正常运行时间、扩展性问题、速度),应该好 好考虑从RDBMS转向使用HBase。你应该使用一个针对扩展性问题的解决 方案,而不是性能越来越差却需要大量投入的曾经可用的方案。有了 HBase,软件是免费的,硬件是廉价的,而分布式处理则是与生俱来的。


13.6.3 实例HBase  Streamy.com 的使用

Streamy.com是一个实时新闻聚合器和社会化分享平台。它有很多功能特 性。我们最早是在PostgreSQL上开始的,实现很复杂。PostgreSQL是一个 很棒的产品,社区支持很好,代码很漂亮。我们尝试了教材中所有可以在扩展时提速的技巧,甚至为了适应我们的应用需求而修改了 PostgreSQL 一开始,我们利用了 RDBMS的所有优点。但是,我们逐渐一一放弃了这 些特性。最后,所有的团队都成了 DBA(需要在开发的同时考虑管理任务)。

我们的确解决了碰到的很多问题,但是有两个问题最终迫使我们必须寻求 RDBMS以外的解决办法。

Streamy从几千个RSS源爬取数据并对其中的上亿个信息项进行聚合。除了 必须存储这些信息项以外,我们应用中有一个复杂的查询需要从一个源的 集合中读取按时间排序的列表。在极端情况下,在单个査询中会牵涉数千 个源及其所有的信息项。

1. 超大规模的信息项表

一开始,只有一个信息项表,但大量的二级索引导致插入和更新非常慢。 我们开始把数据项分成几个一对一链接的表来存储其他信息,通过这种方 式把静态字段和动态字段区分开,根据字段査询方式来对字段分组,并据此进行反规范化。即使做了这些修改,单独的各个更新操作也需要重写整 个记录,所以要跟踪信息项的统计信息就很难做到可扩展。重写记录、更 新索引都是RDBMS的固有特性,它们是不能去除的。我们对表进行了分区,由于有时间属性可以作为自然分区的属性,所以这本身并不困难。但 应用的复杂性还是很快超出我们能够控制的范围。我们需要另外一种解决 办法!

2. 超大规模的排序合并

对按时间进行排序的列表进行“排序合并sort merge)Web 2.0应用中常 见的操作。相应的SQL査询示例可能如下所示:

SELECT id, stamp, type FROM streams
WHERE type IN ('typel','type2','type3'type4',...,'typeN')
ORDER BY stamp DESC LIMIT 10 OFFSET 0;


假设idstreams上的主键,在stamptype上有二级索引RDBMS 的査询规划器会像下面这样处理査询:

MERGE (
SELECT id, stamp, type FROM streams
WHERE type = 'typel' ORDER BY stamp DESC,
...,
SELECT id, stamp, type FROM streams
WHERE type = 'typeN' ORDER BY stamp DESC 
)ORDER BY stamp DESC LIMIT 10 OFFSET 0;

这里的问题是我们只要最前面的10ID。但査询规划器实际会物化整个合并操作,然后在再最后限定返回结果。简单地对每个type使用堆排序(heap sort)让你可以在有10个结果以后就进行“提早过滤early out)。在我们的 应用中,每个type可以有数万个ID,因此物化整个列表再进行排序极其 缓慢,而且没有必要。事实上,我们进一步写了一个定制的PL/Python脚本,用一系列的査询来进行堆排序。査询如下:

SELECT id, stamp, type FROM streams 
WHERE type = 'typeN'
ORDER BY stamp DESC LIMIT 1 OFFSET 0;

如果从typeN中获得了结果(在堆中第二个最近的项),我们继续执行下面的查询:

SELECT id, stamp, type FROM streams 
WHERE type = 'typeN'
ORDER BY stamp DESC LIMIT 1 OFFSET 0;

在几乎所有情况下,这都胜于直接用SQL实现和采用査询规划器的策略。 SQL最差的执行情况下,我们采用Python过程的方法比它们要快一个数 量级。我们发现,要想满足需求,需要不停尝试优于査询规划器的方法。

在这里,我们再一次觉得需要有其他解决办法。

3. 有了 HBase以后的日子

我们的基于RDBMS的系统始终能够正确地满足我们的需求:问题出在可扩展性上。如果重点关注扩展和性能问题而非正确性,最终少不了千方百计地找出捷径,并针对问题进行定制优化。一旦为了数据的问题开始实现 自己的解决方案,RDBMS的开销和复杂性就成为障碍,逻辑抽象和存储层的独立性与ACID要求会成为巨大的屏障,它们成为在开发可扩展系统时 无法承受的负担。HBase只是一个分布式、面向列、支持排序映射的存储系统。它唯一进行抽象的部分就是分布式处理,而这正是我们不想自己面对的。另一方面,我们的业务逻辑(business logic)是高度定制化和优化的。HBase并不试图解决我们的所有问题,这些部分我们自己能够处理得更好。我们只依靠HBase来解决存储的扩展,而不是业务逻辑。能够把精力集中在我们的应用和业务逻辑,而不需要关心数据的扩展问题,使我们完全得到了解放。

我们目前已经有包含数亿数据行和数万列的表。能够存储这样的数据让人感到兴奋,而不是恐惧。

13.6 Praxis

在这一小节,我们将讨论在应用中运行HBase#集群时用户常常遇到的一些问题。

13.7.1 版本

一直到HBase 0.20,HBase的版本都对应于Hadoop的版本。某个版本的HBase能够和任何“小版本minor version)相同的Hadoop共同运行。小版本就是两个小数点之间的数字(如HBase 0.20.5的小版本是20)。HBase0.20.5 可以和 Hadoop 0.20.2—起运行,但 HBase 0.19.5 不能和 Hadoop 0.20.0—起运行。

但从HBase 0.90开始,两者之间的联系不复存在。Hadoop的开发周期也变长了,和HBase的开发周期不再匹配。此外,改变版本号联系还有一个原因是想让一个HBase版本可以运行在多版本的Hadoop上。例如,HBase

0.90.x Hadoop 0.20.x 0.21 .x 上者[5能运行。

这意味着必须保证运行的HadoopHBase版本是兼容的。请检查下载版本 “要求”requirement)—节。如果运行不兼容的版本,幸运的话,系统会抛出异常,报告版本不兼容。如果两者相互之间根本不能交换版本信息,

你会发现HBase集群在启动后很快就挂在那里,停止运行。以上两种情况还会在系统升级时碰到。升级后,由于清除得不干净,仍然可以在类路径 上找到老版本HBaseHadoop

HBase使用HDFS的方式与MapReduce使用HDFS的方式截然不同。在MapReduce中,首先打开HDFS文件,然后map任务流式处理文件的内 容,最后关闭文件。在HBase中,数据文件在启动时就被打开,并在处理 过程中始终保持打开状态,这是为了节省每次访问操作打开文件所需的代价。所以,HBase更容易碰到MapReduce客户端不常碰到的问题:

1. 文件描述符用完 

由于我们在连接的集群上保持文件的打开状态,所以用不了太长时间就可 能达到系统和Hadoop设定的限制。例如,我们有一个由三个节点构成的集 群,每个节点上运行一个datanode实例和一个regionserver如果我们正在 运行一个加载任务,表中有100个区域和10个列族。我们允许每个列族平 均有两个“刷入文件”flush file)。通过计算,我们知道同时打开了 100x 10x2,即2000个文件。此外,还有各种外部扫描器和java库文件占用了 其他文件描述符。每个打开的文件在远程datanode上至少占用一个文件描 述符。一个进程默认的文件描述符限制是1024。当我们使用的描述符个数 超过文件系统的值,我们会在日志中看到“Too many open files”(打幵了太多文件)的错误信息。但在这之前,往往就已经能看出HBase的行为 不正常,而发生哪种错误行为是不一定的。要修正这个问题需要增加文件 描述符的参数值。可以通过査看regionserver日志的前几行来确定 运行中的HBase是否设置了足够多的文件描述符。日志中列出系统的重要 组件(如使用的JVM)以及环境设置(如描述符的ulimit值)。

2. datanode上的线程用完

和前面的情况类似,Hadoopdatanode上同时运行的线程数不能超过256 这一限制值。给定前面所描述的表的统计值,我们很容易看到很快就会达 到这一限制值,因为在本书写作时,在datanode中,每—个文件块的打开连接都会使用一个线程。如果査看datanode日志,会看到“xceiverCount 258 exceeds the limit of concurrent xcievers 256”xceiverCount258 超过了并 xcievers256限制)这样的出错信息。同样,很可能在你看到日志中的出错信息之前,HBase的行为就已经出错了。这时需要在HDFS中把 dfs.datanode.max.xcievers的值调高,并重启集群。

3. Sync

必须在有可用syncHDFS上使用HBase。否则会丢失数据。这意味着需 要在Hadoop 0.20.205.0或后续版本上运行HBase

13.7.3用户界面

HBase在主控机上运行了一个Web服务器,它能提供运行中集群的状态视图。在默认情况下,它监听60010端口。主界面显示了基本的属性(包括软件版本、集群负载、请求频率、集群表的列表)和加入的regiotiserver等。 在主界面上单击选中regionserver会把你带到那个regionserver上运行的 Web服务器,它列出了这个服务器上所有区域的列表及其他基本的属性值 (如使用的资源和请求频率)。

13.7.4度量

Hadoop有一个度量(metric)系统。可以用它每过一段时间获取系统重要组件 的信息,并输出到上下文(context),详情参见10.2.2节。启用Hadoop度量 系统,并把它捆绑入Ganglia或导出到JMX,就能得到集群上正在做和刚才做的事情的视图。HBase也有它自己的度量(比如请求频率、组件计数、资源使用情况等),可以通过Hadoop上下文获得它们。相关信息可参见 HBase conf 目录下的 hadoop-metrics-properties文件。

13.7.5 模式的设计

单元格是有版本的;数据行是有序的,只要列族存在,列便可以由客户端 随时添加;除了这三个特性以外HBase的表和RDBMS中的表是类似的。

虽然在为HBase设计模式时,需要考虑这呰不同点,但最重要的是要考虑 数据的访问方式。所有的数据都是通过主键进行访问的,所以在设计时, 最主要的问题是知道如何査询这些数据。在对HBase这样的面向列(族)的 存储设计模式时,另一件需要记住的是它可以以极小的开销管理较宽的稀疏表。

1.连接

HBase并没有内置对数据库连接的支持。但是“宽表wide table)使我们并 不需要让第一个表和第二个表或第三个表进行数据库连接。一个宽行有时 可以容下一个主键相关的所有数据。

2. 行键

应该把较多的精力用于设计行的键。在本章的气象数据示例中,复合的行键利用观测站作为前缀,对同一个观测站的气温数据进行分组。反向时间戳后缀使我们可以扫描以及按时间序读到从最近到最远的气温数据。一个 精心设计的复合键可以用来对数据进行聚类,以配合数据的访问方式。

设计复合键时,可能需要用0来填充数据,使行键可以正确排序。否则,会碰到由于只考虑字节序而导致10排在2之前的情况(02排在10之前)。

如果键是整数,则应该使用二进制形式,而不是把数字持久化成字符串类型,字符串会占用更多的空间。

13.7.6计数器

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

13.7.7  批量加载

HBase有一个高效的“批量加载”(bulk loading)工具。它从MapReduce把以内部格式表示的数据直接写入文件系统,从而实现批量加载。顺着这条路,我们加载HBase实例的速度比用HBase客户端API写入数据的方式至少快一个数量级。这个工具的相关介绍可访问http://hbase.apache.org/docs/current/bulk-loads.html。它还支持向使用中的表批量加载数据。

转载请注明:全栈大数据 » 第十三章 关于HBase

喜欢 (1)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址