Apache大数据组件

Catalogue
  1. 1. 概述
  2. 2. HDFS
    1. 2.1. 组成组件
    2. 2.2. 不同版本的比较
      1. 2.2.1. Hadoop 1.x
        1. 2.2.1.1. 单点故障问题
        2. 2.2.1.2. SecondaryNameNode
      2. 2.2.2. Hadoop 2.x
        1. 2.2.2.1. HA
    3. 2.3. HDFS文件读写
      1. 2.3.1. hdfs文件读取过程
      2. 2.3.2. hdfs文件写入过程
    4. 2.4. 不适合的场景
  3. 3. Kafka
    1. 3.1. 基本概念
    2. 3.2. HA
    3. 3.3. 应用场景
    4. 3.4. 消费模式
    5. 3.5. 消息顺序性
      1. 3.5.1. Partition顺序性
      2. 3.5.2. Partition Offset
    6. 3.6. Rebalance
    7. 3.7. Push vs Pull
    8. 3.8. 客户端API
    9. 3.9. 磁盘存储特点
  4. 4. zookeeper
    1. 4.1. 配置
    2. 4.2. 数据模型
    3. 4.3. zookeeper结构
    4. 4.4. 选取Leader的过程
    5. 4.5. 应用
  5. 5. HBase
    1. 5.1. HBase主要优点
    2. 5.2. 列式数据库特点
    3. 5.3. 物理模型
    4. 5.4. HBase的compact
    5. 5.5. 读写性能讨论
    6. 5.6. 与zookeeper的关系
    7. 5.7. 调优
    8. 5.8. rowkey设计原则
    9. 5.9. hbase shell操作
      1. 5.9.1. delete操作
      2. 5.9.2. 创建表
      3. 5.9.3. 添加数据
      4. 5.9.4. 查找数据
      5. 5.9.5. 扫描表数据
      6. 5.9.6. 删除表
  6. 6. Lambda架构

概述

大数据的处理模式大体可以分为批处理(也可称为离线计算)、流式计算(数据实时性高)、在线处理(即时响应)和交互式分析(允许分钟级)四种。

  • 大数据的4V属性
    数量(Volume),多样性(Variety),速度(Velocity),真实性(Veracity)

HDFS

组成组件

HDFS是以master/slave模式运行的,其中NameNode、SecondaryNameNode 通常运行在master节点,DataNode运行slave节点。

  • NameNode
    管理文件系统的Namespace,维护文件系统树以及树中所有文件和文件夹的元数据(metadata);处理客户端请求。
  • SecondaryNameNode
    相当于一个Checkpoint Node,监控HDFS状态的辅助后台程序,与NameNode进行通信,以便定期地保存HDFS元数据的快照。
  • DataNode
    文件系统的工作节点,当需要通过客户端读/写某个数据时,先由NameNode告诉客户端去哪个DataNode进行具体的读/写操作,然后,客户端直接与这个DataNode服务器上的后台程序进行通信,并且对相关的数据块进行读/写操作。

不同版本的比较

Hadoop 1.x

单点故障问题

由于只有一个NameNode,因此会出现单点故障问题。有多种方式可以解决:

  • 将hadoop元数据写入到本地文件系统的同时再实时同步到一个远程挂载的网络文件系统(NFS)(在2.X中同样适用);
  • 运行一个secondaryNameNode进行冷备份,它的作用是与NameNode进行交互,定期将编辑日志文件(edit logs)合并为命名空间镜像,当NameNode发生故障时它会通过自己合并的命名空间镜像副本来恢复。
    • secondaryNameNode保存的状态总是滞后于NameNode,所以这种方式难免会导致丢失部分数据。
    • NameNode中存放的元信息文件是fsimage,操作期间所有对元信息的操作都保存在内存中并被持久化到另一个文件edit logs中。edit logs文件和fsimage文件会被SecondaryNameNode周期性的合并。
      注:http://blog.csdn.net/xh16319/article/details/31375197
      • fsimage :保存的是上个检查点的HDFS的元信息;
      • edits :保存的是从上个检查点开始发生的HDFS元信息状态改变信息。
    • 按照HDFS的设计,NameNode不会主动发起任何请求,只会被动接受来自客户端或DataNode的请求。

SecondaryNameNode

合并流程:

  • 首先,secondaryNameNode定时到NameNode去获取edit logs,并更新到fsimage上。[笔者注:Secondary NameNode自己的fsimage]
  • 一旦它有了新的fsimage文件,它将其拷贝回NameNode中。
  • NameNode在下次重启时会使用这个新的fsimage文件,从而减少重启的时间。

此处输入图片的描述

Hadoop 2.x

HA

在2.X中,HDFS的变化,主要体现在增强了NameNode的水平扩展及可用性,可以同时部署多个NameNode,这些NameNodes之间是相互独立,也就是说他们不需要相互协调,DataNode同时在所有NameNodes注册,做为他们共有的存储节点,并定时向所有的这些NameNodes发送心跳块使用情况的报告,并处理所有NameNodes向其发送的指令。
多个NameNode之间共享数据,可以通过Nnetwork File System或者Quorum Journal Node。

  • NFS方案;
  • 基于Paxos的QJM(Quorum Journal Node)方案,它的基本原理就是用2N+1台JournalNode存储EditLog,每次写数据操作有大多数(>=N+1)返回成功时即认为该次写成功。在HDFS运行时,同一时刻只有一个NameNode处于active状态,另一个处于standby状态。standbyNamenode作为“休眠方”,只进行数据同步,维护着数据状态,随时准备切换。

附:HDFS中的沟通协议


此处输入图片的描述

HDFS文件读写

hdfs文件读取过程

  • 客户端发起读请求;
  • 客户端与NameNode得到文件的块及位置信息列表;
  • 客户端直接和DataNoie交互读取数据;
  • 读取完成关闭连接;

hdfs文件写入过程

hdfs有一个DistributedFileSystem实例,客户端通过调用这个实例的create()方法就可以创建文件:

  • DistributedFileSystem会发送给NameNode一个RPC调用,在文件系统的命名空间创建一个新文件,在创建文件前NameNode会做一些检查,如文件是否存在,客户端是否有创建权限等,若检查通过,NameNode会为创建文件写一条记录到本地磁盘的EditLog,若不通过会向客户端抛出IOException。创建成功之后DistributedFileSystem会返回一个FSDataOutputStream对象,客户端由此开始写入数据。
  • 客户端在向NameNode请求之前先写入文件数据到本地文件系统的一个临时文件;
  • 待临时文件达到块大小时开始向NameNode请求DataNode信息;
  • NameNode在文件系统中创建文件并返回给客户端一个数据块及其对应DataNode的地址列表(列表中包含副本存放的地址);
  • 客户端通过上一步得到的信息把创建临时文件块flush到列表中的第一个DataNode;
  • 当文件关闭,NameNode会提交这次文件创建,此时,文件在文件系统中可见。

不适合的场景

  • 大量小文件:文件的元数据(命名空间信息,块信息等)都存储在NameNode内存中,大量小文件会占用大量内存。
  • 低延迟数据访问:hdfs是专门针对高数据吞吐量而设计的
  • 多用户写入,任意修改文件,HDFS设计上是为了适合一次写入,多次使用

Kafka

基本概念

  1. topic:消息存放的目录即主题
  2. Producer:生产消息到topic的一方
  3. Consumer:订阅topic消费消息的一方
  4. Broker:Kafka的服务实例就是一个broker

HA

  • Partition分布在集群的每一台server上,而每一个Partition在集群中都可以有多个备份,这个备份数量是可配置的。
  • 每个Partition都有一个leader server,而其他备份的server都称为followers,只有leader服务器才会处理这个Partition上所有的读写请求,而其它followers则被动地复制leader上的数据。如果一个leader挂掉了,followers中的一个服务器则会自动升级为leader。同一个机器可以有多个Partition。

应用场景

  • Kafka基本应用图如下所示:


    此处输入图片的描述
  • 使用场景
    负责消费Partition的每个消费者都是一个消费进程,而且消费者本身也可以是多线程的应用程序,因为一个Partition只能属于一个消费者线程,所以存在如下几种不同的场景:

    • 线程数量多于Partition的数量,有部分线程无法消费该topic下任何一条消息
    • 线程数量少于Partition的数量,有一些线程会消费多个Partition的数据(这是最好的场景)
    • 线程数量等于Partition的数量,则正好一个线程消费一个Partition的数据

消费模式

消息传递通常由两种模式,queuing(队列)和publish-subscribe (发布-订阅)

  • queuing: 每个Consumer从消息队列中取走一个消息
  • pub-scrib: 消息被广播到每个Consumer

此处输入图片的描述

实际上,kafka通过提供了一个对Consumer的抽象来同时实现这两种模式——ConsumerGroup。

当同一个 Topic (一个producer一个topic)的 consumer 配置 group.id 相同时,即为 queue模式。否则用不同的group.id时,则以public-subscribe模式工作。

消息顺序性

Partition顺序性

消息在一个Partition中的顺序是有序的,但是Kafka只保证消息在一个Partition中有序,如果要想使整个topic中的消息有序,那么一个topic仅设置一个Partition即可,或者在producer端控制每个partition消息应有的顺序。

Partition Offset

  • 生产者的提交日志采用递增的offset连同消息内容一起写入到本地日志文件,生产者客户端本身不需要保存offset相关的状态。
  • 消费者进程则要保存消费消息的offset,因此它是有状态的,这样消费者才能将消息的消费进度保存到ZK或者其他存储系统中。在顺序读取过程中,通过offset记录每条日志对于每个组的当前消费进度。

Rebalance

  • 一个消费组有多个消费者,因此消费组需要维护所有的消费者,如果一个消费者宕掉了,分配给这个消费者的Partition需要被重新分配给相同组的其他消费者;
  • 如果一个消费者加入了同一个组,之前分配给其他消费组的Partition需要分配给新加入的消费者。
    • 实际上一旦有消费者加入或退出消费组,导致消费组成员列表发生变化,即使Kafka集群的Partition没有变化,消费组中所有的消费者也都要触发重新rebalance的工作。当然如果集群的Partition发生变化,即使消费组成员没有变化,所有的消费者也都要重新rebalance。
    • 消费者消费消息时需要定时地将最新的消费进度保存到ZooKeeper中,当发生rebalance时,新的消费者拥有的新的Partition都可以从ZooKeeper中读取出来并恢复到最近的状态。

      此处输入图片的描述

Push vs Pull

kafka的consumer之所以没有采用push模式,是因为push模式很难适应消费者速率不同的消费者而且很难实现消息的回放功能,因为消息发送速率是由broker决定的。push模式的目标就是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞,而pull模式则可以根据consumer的消费能力以适当的速率消费message。
http://kafka.apache.org/documentation.html#design_pull

客户端API

在0.9.0版本中:This new unified consumer API removes the distinction between the 0.8 high-level and low-level consumer APIs。https://kafka.apache.org/090/documentation.html#newconsumerapi。但还是可以用0.8之前的low level API,在0.10之后则官方doc连low level提也不提。可见high level API将渐渐成为开发者唯一操作接口。

  • Hight Level Consumer高级API提供了一个从Kafka消费数据的高层抽象,消费者客户端代码不需要管理offset的提交,并且采用了消费组的自动负载均衡功能,确保消费者的增减不会影响消息的消费;
  • Low Level Consumer低级API通常针对特殊的消费逻辑(比如消费者只想要消费某些特定的Partition),低级API的客户端代码需要自己实现一些和Kafka服务端相关的底层逻辑,比如选择Partition的Leader,处理Leader的故障转移等。

磁盘存储特点

Kafka使用磁盘进行数据的存储,默认有效期为7天,而不是采用内存,主要好处有:

  • 磁盘缓存由Linux系统维护,减少了程序员的不少工作。
  • 磁盘顺序读写速度超过内存随机读写。
  • JVM的GC效率低,内存占用大,使用磁盘可以避免这一问题。
  • 系统冷启动后,磁盘缓存依然可用。
    http://blog.csdn.net/endlu/article/details/51392905

zookeeper

ZooKeeper是以Fast Paxos算法为基础,实现同步服务,配置维护和命名服务等分布式应用。对于Zookeeper集群而言,设定有2n+1台server,只要有n+1台依然能运行,就可以使用,继续提供服务。

配置

1
2
3
4
# 集群配置,3台机器,2888为Leader服务端口,3888为选举时所用的端口
server.1=cu01:2888:3888
server.2=cu02:2888:3888
server.3=cu03:2888:3888

数据模型

zookeeper 会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统:


此处输入图片的描述
  • 概念
    • znode: 每个目录项称为znode;
    • zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了;
    • znode 是有版本的,每个znode中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据。
  • 四种形式的目录节点(String create()的createMode选项)
    • PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;
    • PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已经存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;
    • EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;
    • EPHEMERAL_SEQUENTIAL:临时自动编号节点。

zookeeper结构

  • 角色
    • Leader
      作为整个ZooKeeper集群的主节点,负责响应所有对ZooKeeper
      状态变更的请求。它会将每个状态更新请求进行排序和编号,以便保证整个集群内部消息处理的FIFO。
    • Follower
      响应本服务器上的读请求外,follower还要处理leader的提议,并在leader提交该提议时在本地也进行提交。leader和follower构成ZooKeeper集群的法定人数,也就是说,只有他们才参与新leader的选举、响应leader的提议。
    • Observer
      observer服务器用于提高读取的吞吐量。Observer和Follower比较相似,只有一些小区别:首先observer不属于法定人数,即不参加选举也不响应提议;其次是observer不需要将事务持久化到磁盘,一旦observer被重启,需要从leader重新同步整个名字空间。
  • 节点状态
    每个集群中的节点都有一个状态LOOKING,FOLLOWING,LEADING,OBSERVING,每个节点启动的时候都是LOOKING状态,如果这个节点参与选举但最后不是leader,则状态是FOLLOWING,如果不参与选举则是OBSERVING,leader的状态是LEADING。

选取Leader的过程

  • 专有概念
    • zxid:每个ZooKeeper服务器保存在磁盘的事务id,在初始阶段,每台服务器的这个值都是自己的id(高32位是epoch,低32位用于递增计数);
    • epoch:逻辑时钟的值,每次选举leader这个值会加1;用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。
  • Zk的选举算法有两种
    一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。
    • Basic paxos:
      • 当前Server发起选举的线程担任选举线程,其主要功能是对投票结果进行统计,并选出推荐的Server;
      • 选举线程首先向所有Server发起一次询问(包括自己);
      • 选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
      • 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server。在收集齐的过程中,可能有个别server网络好,就更新了自己要选的那个server;
      • 如果此时获胜的Server获得n/2+1的Server票数,设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。
    • Fast Paxos:
      • 每个Server会发出一个投票。初始情况,将自己作为Leader服务器来进行投票。每次投票包含的基本的元素包括:所推举的服务器的myid和ZXID,以(myid,ZXID)的形式来表示。因为是初始化阶段,因此无论是Server1还是Server2,都会投给自己,即Server1的投票为(1,0),Server2的投票为(2,0),然后各自将这个投票发给集群中其他所有机器。
      • 接收来自各个服务器的投票
        每个服务器都会接收来自其他服务器的投票。集群中的每个服务器在接收到投票后,首先会判断该投票的有效性,包括检査是否是本轮投票、是否来自LOOKING 状态的服务器。
      • 处理投票
        在接收到来自其他服务器的投票后,服务器都需要将别人的投票和自己的投票进行PK:
      1. 优先检査Epoch,Epoch高的作为Leader。
      2. 在检査ZXID。ZXID比较大的服务器优先作为Leader。
      3. 如果Epoch、ZXID相同的话,那么就比较myid。myid比较大的服务器作为Leader服务器。
      • 对于Server1来说,它自己的投票是(1,0),而接收到的投票为(2,0)。首先会对比两者的Epoch,我们假设两个的Epoch相同,再比较ZXID,因为都是0,所以无法决定谁是Leader。接下来会对比两者的myid,很显然,Server1发现接收到的投票中的myid是2,大于自id,于是就会更新自己的投票为(2,0),然后重新将投票发出去。而对于Server2来说,不需要更新自己的投票信息。
      • 统计投票。
        毎次投票后,服务器都会统计所有投票,判断是否已经有过半的机器接收到相同的投票信息。有的话,设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。
  • 选完leader以后,zk就进入状态同步过程。
  1. leader等待server连接;
  2. Follower连接leader,将最大的zxid发送给leader;
  3. Leader根据follower的zxid确定同步点;
  4. 完成同步后通知follower 已经成为uptodate状态;
  5. Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。

应用

ZooKeeper主要是用来维护和监控一个目录节点树中存储的数据的状态,所有操作ZooKeeper和操作目录节点树大体一样,如

  • 创建一个目录节点;
  • 给某个目录节点设置数据;
  • 获取某个目录节点的所有子目录节点;
  • 给某个目录节点设置权限和监控这个目录节点的状态变化。

Zookeeper 从设计模式角度来看,是一个基于观察者模式设计的分布式服务管理框架,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应,从而实现集群中类似 Master/Slave 管理模式:

  • 统一命名服务(Name Service):分布式中统一命名;
  • 配置管理(Configuration Management):软件运行中可动态修改配置;
  • 集群管理(Group Membership):选出一个“总管”知道当前集群每台机器的服务状态,涉及zookeeper中Leader Election的功能;

    Zookeeper实现 Leader Election,也就是选出一个 Master Server。普通监听:每台 Server 创建一个 EPHEMERAL 目录节点,并调用父目录节点getChilden监听,此时不同的是它还是一个SEQUENTIAL 目录节点,所以它是个 EPHEMERAL_SEQUENTIAL目录节点。之所以它是 EPHEMERAL_SEQUENTIAL 目录节点,是因为可以给每台 Server 编号,可以选择当前最小编号的 Server 为 Master,假如这个最小编号的 Server 死去,由于是 EPHEMERAL 节点,死去的 Server 对应的节点也被删除,所以当前的节点列表中又出现一个最小编号的节点,我们就选择这个节点为当前 Master。这样就实现了动态选择 Master,避免了传统意义上单 Master 容易出现单点故障的问题。

  • 共享锁:要获得锁的server创建EPHEMERAL_SEQUENTIAL目录节点,对父节点getChilden判断当前server编号是否是同级最小编号,是就获得了锁,释放就删除;
  • 创建同步队列(用得较少):通过不断增加目录,增加时到没到数目,到了就创建一个监控标志位目录(xx/start),各个节点监听到有这个节点就触发监听程序,然后移除成员,不到就继续等待。

HBase

(注:Zookeeper的引入使得Master不再是单点故障)

HBase主要优点

  • 高效的储存空间利用率
    传统的行式数据库由于每个列的长度不一,为了预防更新的时候不至于出现一行数据(没值的字段需要补值)跳到另一个block上去,所以往往会预留一些空间。而面向列的数据库由于一开始就完全为分析而存在,不需要考虑少量的更新问题,所以数据完全是密集储存的。

  • 不可见索引
    在已经读取了可能的数据块之后,对于类似age < 65或job=’Axx’的,列式数据库并不需要扫描完整个block,因为数据已经排序了。如果读到第一个age=66或者Job=‘Bxx’的时候就会停止扫描了。这相当与行式数据库索引里的范围扫描。

  • 压缩算法
    列式数据库由于其每一列都是分开储存的。所以很容易针对每一列的特征运用不同的压缩算法。

  • 延迟物化
    列式数据库由于其特殊的执行引擎,在数据中间过程运算的时候一般不需要解压数据而是以指针代替运算,直到最后需要输出完整的数据时才解压缩。

列式数据库特点

  • 优点
    • 极高的装载速度(最高可以等于所有硬盘IO的总和,基本是极限了)
    • 适合大量的数据而不是小数据
    • 实时加载数据仅限于增加(删除和更新需要解压缩Block然后计算然后重新压缩储存)
    • 高效的压缩率,不仅节省储存空间也节省计算内存和CPU。
    • 非常适合做聚合操作。   
  • 缺点
    • 不适合扫描小量数据
    • 不适合随机的更新
    • 批量更新情况各异,有的优化的比较好的列式数据库(比如Vertica)表现比较好,有些没有针对更新的数据库表现比较差。 不适合做含有删除和更新的实时操作

物理模型

  • 每个column family存储在HDFS上的一个单独文件中,空值不会被保存;
  • Table在行的方向上分割为多个Region,Region按大小分割的,每个表开始只有一个region,随着数据增多,region不断增大,当增大到一个阀值的时候,region就会等分会两个新region,之后会有越来越多的region;
  • Region是Hbase中分布式存储和负载均衡的最小单元(最小存储单元是store——对应一个columnfamily),不同Region分布到不同RegionServer上。


    此处输入图片的描述
  • Master

    • 为Region server分配region;
    • 负责Region server的负载均衡;
    • 发现失效的Region server并重新分配其上的region;
    • 管理用户对table的增删改查操作;
  • 因此,无Master过程中:
    • 数据读取仍照常进行;
    • region切分、负载均衡等无法进行;

HBase的compact

HFile数量过多会降低读性能。为了避免对读性能的影响,可以对这些HFile(Store中的Storefile)进行compact操作,把多个HFile合并成一个HFile。compact操作需要对HBase的数据进行多次的重新读写,因此这个过程会产生大量的IO。因此compact操作的本质就是以IO操作换取后续的读性能的提高。

HBase的compact是针对HRegion的HStore进行操作的。compact操作分为major和minor两种:

  • major会把HStore所有的HFile都compact为一个HFile,并同时忽略标记为delete的KeyValue(被删除的KeyValue只有在compact过程中才真正被”删除”),可以想象major会产生大量的IO操作,对HBase的读写性能产生影响。
  • minor则只会选择数个HFile文件compact为一个HFile,minor的过程一般较快,而且IO相对较低。在日常任务时间,都会禁止major操作,只在空闲的时段定时执行。

    此处输入图片的描述

读写性能讨论

  • HBase的写入速度快是因为它其实并不是真的立即写入文件中,而是先写入内存,随后异步刷入HFile。所以在客户端看来,写入速度很快。另外,写入时候将随机写入转换成顺序写,数据写入速度也很稳定。因此,用户写操作只需要进入到内存即可立即返回,从而保证I/O高性能。
  • 读取速度快是因为它使用了LSM树型结构。LSM树原理把一棵大树拆分成N棵小树,它首先写入内存中,随着小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。LSM树实际上是牺牲了部分读性能,用来大幅提高写性能。
  • 磁盘的顺序读取速度很快,但是相比而言,寻找磁道的速度就要慢很多。HBase的存储结构导致它需要磁盘寻道时间在可预测范围内,并且读取与所要查询的rowkey连续的任意数量的记录都不会引发额外的寻道开销。比如有5个存储文件,那么假设要到HFile中读取数据,最多需要5次磁盘寻道就可以。
    HBase读取首先会在缓存(BlockCache)中查找,它采用了LRU(最近最少使用算法),如果缓存中没找到,会从内存中的MemStore中查找,只有这两个地方都找不到时,才会加载HFile中的内容。
    在HFile中保存的内容是有序的,当数据写入HFile后,内存中的数据会被丢弃。HFile文件为磁盘顺序读取做了优化,按页存储。因此,读取HFile速度也会很快,因为节省了寻道开销。
    而关系型数据库,即使有索引,也无法确定磁盘寻道次数。

与zookeeper的关系

-ROOT-和.META.是HBase的两张内置表,从存储结构和操作方法的角度来说,它们和其他HBase的表没有任何区别,你可以认为这就是两张普通的表,对于普通表的操作对它们都适用。它们与众不同的地方是HBase用它们来存贮一个重要的系统信息——Region的分布情况以及每个Region的详细信息


此处输入图片的描述

参考:http://blog.csdn.net/woshiwanxin102213/article/details/17584043

调优

更多:http://www.cnblogs.com/shitouer/archive/2012/08/07/2626377.html

hbase中hfile(storefile)的默认最大值(hbase.hregion.max.filesize)是256MB,而google的bigtable论文中对tablet的最大值也推荐为100-200MB,这个大小有什么秘密呢?
  众所周知hbase中数据一开始会写入memstore,当memstore满64MB以后,会flush到disk上而成为storefile。当storefile数量超过3时,会启动compaction过程将它们合并为一个storefile。这个过程中会删除一些timestamp过期的数据,比如update的数据。而当合并后的storefile大小大于hfile默认最大值时,会触发split动作,将它切分成两个region。
  链接中作者声明进行了持续insert压力测试,并设置了不同的hbase.hregion.max.filesize,根据结果得到如下结论:

  • 值越小,平均吞吐量越大,但吞吐量越不稳定;
  • 值越大,平均吞吐量越小,吞吐量不稳定的时间相对更小。

为什么会这样呢?推论如下:

  • 当hbase.hregion.max.filesize较小时,触发split的机率更大,而split的时候会将region offline,因此在split结束的时间前,访问该region的请求将被block住,客户端自我block的时间默认为1s。当大量的region同时发生split时,系统的整体访问服务将大受影响。因此容易出现吞吐量及响应时间的不稳定现象
  • 当hbase.hregion.max.filesize比较大时,单个region中触发split的机率较小,大量region同时触发split的机率也较小,因此吞吐量较之小hfile尺寸更加稳定些。但是由于长期得不到split,因此同一个region内发生多次compaction的机会增加了。compaction的原理是将原有数据读一遍并重写一遍到hdfs上,然后再删除原有数据。无疑这种行为会降低以io为瓶颈的系统的速度,因此平均吞吐量会受到一些影响而下降。

综合以上两种情况,hbase.hregion.max.filesize不宜过大或过小,256MB或许是一个更理想的经验参数。对于离线型的应用,调整为128MB会更加合适一些,而在线应用除非对split机制进行改造,否则不应该低于256MB。

rowkey设计原则

  • 长度原则
    Rowkey的长度被很多开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节。
    原因如下:

    • 数据的持久化文件HFile中是按照KeyValue存储的,如果Rowkey过长比如100个字节,1000万列数据光Rowkey就要占用100*1000万=10亿个字节,将近1G数据,这会极大影响HFile的存储效率;
    • MemStore将缓存部分数据到内存,如果Rowkey字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。因此Rowkey的字节长度越短越好。
    • 目前操作系统是都是64位系统,内存8字节对齐。控制在16个字节,8字节的整数倍利用操作系统的最佳特性。
  • 散列原则

    • 如果Rowkey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个Regionserver实现负载均衡的几率。
    • 如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个 RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer,降低查询效率。

hbase shell操作

delete操作

  • 每个记录有个保存于memstore的记录,在持久化前,记录新旧程度的”ts”
    1
    2
    3
    put 'key', 'value', ts=1
    put 'key', 'value', ts=2
    del 'key', 'value', ts=3

如上所示,进行两次put操作再加一次delete操作,合并后,就只有delete的操作了。

  • Hbase有一个TTL(time to live),可以标识数据的有效期,比如,可以把TTL设置成86400*1000,也就是说数据将于1天后过期。这是一个表级的设置,必须在建表时指定。本质上也是利用ts(timestamp)进行比较,当前ts与put之后的ts进行比较,若大于TTL则删除。注意,ts可人工改,要谨慎,不然太大就表示该记录长时间有效,影响正常操作。

创建表

1
2
# create [tableName],[family1],<family2>,...,<familyN >
create 't1','f1'

添加数据

1
put <table>,<rowkey>,<family:column>,<value>,<timestamp>

e.g.

1
hbase(main):003:0> put 't1','rowkey001','f1:col1','value01'

查找数据

1
get <table>,<rowkey>,[<family:column>,....]

e.g.

1
hbase(main)> get 't1','rowkey001', 'f1:col1'

扫描表数据

如下,获取前五条rowkey相对应的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
hbase(main):017:0> scan 'FeatureIndex0.01_v2',{LIMIT=>5}
ROW COLUMN+CELL
0@1|0@0 column=label:avg, timestamp=1483110072314, value=0.0554465387563
0@1|0@0 column=label:stddev, timestamp=1483110072314, value=0.228849788944
0@1|0@0 column=label:sum, timestamp=1483110072314, value=500383.0
0@1|0@1 column=label:avg, timestamp=1483110072312, value=0.0510849704785
0@1|0@1 column=label:stddev, timestamp=1483110072312, value=0.220171615499
0@1|0@1 column=label:sum, timestamp=1483110072312, value=10149.0
0@1|1@0 column=label:avg, timestamp=1483110072323, value=0.0442181954694
0@1|1@0 column=label:stddev, timestamp=1483110072323, value=0.20557989582
0@1|1@0 column=label:sum, timestamp=1483110072323, value=12721.0
100@101|0@0 column=label:avg, timestamp=1483110244726, value=0.0549538811079
100@101|0@0 column=label:stddev, timestamp=1483110244726, value=0.227890231369
100@101|0@0 column=label:sum, timestamp=1483110244726, value=519447.0
100@101|0@1 column=label:avg, timestamp=1483110244726, value=0.061603404489
100@101|0@1 column=label:stddev, timestamp=1483110244726, value=0.240437952631
100@101|0@1 column=label:sum, timestamp=1483110244726, value=1795.0
5 row(s) in 0.1300 seconds

删除表

1
2
truncate <table>
# 其具体过程是:disable table -> drop table -> create table

Lambda架构

1
2
3
batch view = function(all data)
realtime view = function(realtime view, new data)
query = function(batch view, realtime view)
1
2
batchview = function(all data);
query = function(batch view)。
Comments