Apache HBase 2015 年发展回顾与未来展望

发布时间:2019-12-21 19:06    浏览次数 :

[返回]

图片 1

HBase那些事

@(大数据工程学院)[HBase, Hadoop, 优化, HadoopChen, hbase]

[TOC]

编者按:高可用架构推出 2015 年度回顾系列文章,分享在架构领域具有典型意义的年度案例,本文由张虔熙分享。转载请注明来自高可用架构公众号「ArchNotes」。引用张虔熙,Hulu 网,专注于分布式存储和计算,HBase contributor。HBase 2015 年技术发展在2015年,HBase 迎来了一个里程碑——HBase 1.0 release,这也代表着 HBase 走向了稳定。New Interface旧 的 HBase 接口逻辑与传统 JDBC 方式很不相同,新的接口与传统 JDBC 的逻辑更加相像,具有更加清晰的 Connection 管理方式。同时,在旧的接口中,客户端何时将 Put 写到服务端也需要设置,一个 Put 马上写到服务端,还是攒到一批写到服务端,新用户往往对此不太清楚。在新的接口中,引入了 BufferedMutator,可以提供更加高效清晰的写操作。HBase 0.98 与 HBase 1.0 接口名称对比举一个例子,旧的 API 写入操作的代码:新的 API 写入操作的代码:可以看到,在操作前,首先建立连接,然后拿到一个对应表的句柄,之后再进行一系列操作。以上两个是同步写操作。下面看一下批量异步写入接口:代码有相应的注释,可见新的接口显得更加清晰。多个 Region 副本如图所示,在HBase中,Table被横向划分为Region,它是一段数据的管理者,Region 被分发到RegionServer 上进行管理,一个Region只被一个RegionServer管理,它的数据存储在HDFS上,是可以有多个副本的。也就是说:管理者 (Region) 只有一个,数据有多个副本。HBase的以前实现中,当一台RegionServer 不可用时,需要数十秒甚至数分钟才可以完成发现和恢复工作,在这段时间内,这台RegionServer上的Region 是不可用的。当一个Region不可用时,它需要一段时间才可以被其他RegionServer 接管。在最新的实现中,一个 Region 可以有多个副本,分布在多个 RegionServer 上。特点:有一个主 Region,多个从 Region。只有主 Region 接收写请求,并把数据持久化到 HDFS 上。从 Region 从 HDFS 中读取数据并服务读请求。从 Region 可能会读到脏数据。读操作可以只读主,或者既可以读主又可读从。这样在主 Region 不可用时,用户仍可以读从 Region 的数据。目前社区在进行的开发:主 Region 异步同步数据到从 Region,从而减少从 Region 缺少的数据量。Family 粒度的 Flush我们先看一下 HBase 的写流程图数据从客户端写到 RegionServer 上的 Region 后,先写入到内存中,积攒到阈值后写入磁盘,即 LSM-tree 架构。在 以前的实现中,服务端数据从内存刷写到 HDFS 上是 Region 粒度的,Region 下面所有的 Family 都会被 Flush。在很多应用场景中,HBase 中存储的是稀疏数据,在写入一行的数据中,有的 Family 具有值,有的为空,而且不同 Family 中存储的数据大小本身就不同,所以当大的 Family 到达阈值需要刷写数据时,小的 Family 也会跟着刷写,这样会导致很多小文件的产生,影响性能。在新的实现中,提供了更小粒度的 Flush——Family 级别。它的特点是:更加合理的使用内存的写延迟和聚合功能减少 Compaction 的磁盘IO提高读性能RPC 读写队列分离之 前的实现中,RegionServer 上所有操作共享队列,各种操作互相影响。比如Scan 和 Get,在 RPC Call Queue 中,如果一个大的 Scan 请求排列在 Get 之前,那么 Get 就需要等待之前的 Scan 完成才可以执行,延迟较大。在现在的实现中,RPC 可以具有多个 Call Queue,同时将它们分配给不同的操作使用,从而实现各种 Put、Scan 和 Get 等操作的隔离。具体配置的参数如下:hbase.ipc.server.callqueue.handler.factorhbase.ipc.server.callqueue.read.ratiohbase.ipc.server.callqueue.scan.ratio在线调整配置 HBASE-12147之前的实现中,每次修改配置后都需要重启集群现在,调整配置后不再需要重启,但是目前只支持一部分配置的在线调整,如 Load Balance 和 Compaction。Hadoop 也已经实现了此功能。目前社区的工作方向和趋势:提高可用性很多应用都要求存储具有高可用性,目前 HBase 实现的还不够优秀,Facebook 的 HydraHBase 是 Facebook 内部维护的 HBase 版本,它使用 Raft 协议管理 Region Server,从而实现高可靠,它的可用率达到 99.999%,Facebook 声称 HydraBase 能将 Facebook 全年的宕机时间缩减到不到 5 分钟。Cloudera Kudu 使用 Raft 协议管理协调 Tablet,从而也可以达到很高的可用率。HBase 与 HydraHBase 对比:HBaseHydraBase对于 HDFS 多存储介质的使用随着 HDFS 对内存、SSD 的支持和使用,HBase 也会充分使用它们带来的高性能。比如把 WAL 和更多的热数据放到 HDFS 的内存或者 SSD上(三副本)减少对 ZooKeeper 的使用Zookeeper 的抖动会对 HBase 造成影响,目前已经完成对 Master 上的 Assignment Manager 的改造,使它不再依赖 ZooKeeper。堆外内存的使用Java 管理大内存的方式还不高效,HBase 可以把 Cache 放在堆外,读取的时候不再拉到堆内中,以减少 GC 的影响。Q A1、HBase 集群是不是尽量要读写分离,我们的集群,随机写入很大,也有随机读,现在碰到随机读请求很不稳定的情况,希望有经验分享一下HBase 可以支持高吞吐的写请求。对于随机读,如果写操作很多,会造成很多文件来不及 Compact,这会影响随机读的性能。同时,如果 JVM 参数没有经过调优,忙碌的 HBase 集群会有 GC 问题,也会影响随机读的性能。建议可以先调优 JVM 参数和 Cache,也可以引入 BloomFilter 等来优化查询。如果对随机读延迟要求较高,可以考虑分离读写。2、对于HBASE脏读问题,如果只读从region,是不是就可以避免了?不 能,因为从 Region 的数据就是过时的,主 Region 才是最新的数据。目前 HBase 的实现中,只有读主 Region 才可以获得最新数据,当主 Region 不可用时,如果可以忍受 stale 的数据,则可以读从 Region 来保证可用性。目前高可用实现的还不太好,这也是社区努力的方向之一。3、修改 HBase 配置文件,但不重启集群是怎么实现的?Hadoop 实现了一个动态载入配置的框架,修改配置后,激发服务端重新获取配置。具体可见 Hadoop-70014、HBase 历史数据有好的处理办法吗?设定多少天之前的数据删除或者只对对历史数据进行压缩?可以设置 TTL 来淘汰历史数据,设置的时间根据具体应用来定。HBase 可以支持 Family 级别设置压缩,原生 HBase 还不能对于一部分行做压缩,可以考虑分表或者其他上层实现。如果历史数据不再需要,可以考虑设置 TTL。5、写 MapReduce job 从 HBase 中导出某张表的所有数据,默认是几个 region 产生几个 mapper,有什么可以优化提速的办法?可以让多个 mapper 读取一个 region 中的数据,这时候你需要定制一下 TableInputFormat6、0.98 版本或以前的 HBase 有什么好的读写分离方案?snapshot 是不是是一种方法?从 RPC 层面上讲,snapshot 不算读写分离方案,因为所有的读写都进入同一个 Call Queue。从 MVCC 和锁级别等其他方面来看,snapshot 是一种方案。7、HBase multitenant 方面有解决方案没有?社区已经有相应的 issue,在明年发布的 2.0 版本中会发布。8、HBase 不同集群之间的数据同步在 1.0 版本之后有没有更好的解决方案?目前的解决方案仍然是 copytable + replication。目前社区已经可以解决 bulk load 的数据的同步。9、基于 HBase 缓存怎么设计比较好,Hulu 是怎么做的?我的项目里用了 Cloudera 自带的 Solr,发现服务器 memory CPU 开销太大。HBase 的随机读性能不足为在线服务提供缓存服务,可以考虑使用 Redis 或者 Memcache。Solr 应该是做全文索引服务,这应该和 Solr 的实现相关。如果没有设置把 HBase 的表放到内存,HBase 不会消耗很大内存。对于忙碌的 HBase 集群,还是比较消耗 CPU 。10、社区版 Hadoop 2.6 没有对应的 HBase 版本支持,可以用刚才讲的 HydraBase 替代 HBase 吗?HBase 1.0 应该是可以运行在 Hadoop 2.6 之上,从个人角度来看,HydraBase可以替代 HBase,Facebook 就是这么实现的,不过 HydraBase 还没有开源。从架构上来讲,HydraBase 使用 Raft 协议管理 RegionServer,写性能可能不如原生 HBase。11、请问你们是否在生产环境种使用 HBase + Phoenix 的组合来提供复杂快速查询?如果使用了,并发查询的性能如何?Hulu 内部还没有使用 Phoenix,以前我个人使用过 Phoenix,当时 Phoenix 对于大量并发查询支持的不好,尤其是使用了索引的复杂查询。但是 Phoenix 社区发展很快,现在的情况应该会有好转。12、Off-heap 做二级 cache 能否提高随机读速度?可以 。 bucket cache 实现的比较好。目前社区仍在继续优化。对于随机读,还可以增加 BloomFilter 增强性能。13、HBase 的 Rowkey 如何设计才能既保持无热点又能有序便于 scan?可 以考虑 salt 的方式,在写入的时候为 Rowkey 加随机前缀,比如前缀范围 001 - 100,那么我可以随机为 Rowkey 加上这些前缀来消除热点,在 scan 的时候需要加上所有的前缀来 scan,不过这样一个 scan 就要转化为并发的 100 个 scan。

HBase特性

HBase是什么

HBase(Hadoop Database),一个高可靠性、高性能、面向列、可伸缩、 实时读写的分布式数据库。

选择特性

  • 列式储存:方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列查询有非常大的IO优势;
  • KV储存:可以通过key快速查询到其value,任意的value格式;
  • 海量数据:PB级;
  • 低延时:毫秒级别;
  • 高吞吐量:百万查询/每秒;
  • 主键索引:基于RowKey的索引,快速检索KV值;
  • Hadoop集成:文件存储于HDFS之上,高效集成Hive、Spark;
  • BigTable:HBase是Google的BigTable架构的一个开源实现;
  • CRUD: 支持增删查改操作;
  • 支持Row级别事务:HBase本身均是基于Row级别的操作,所以行锁即可以保证ACID;
  • 稀疏存储: 节省空间、提高查询效率、便于压缩;
  • 支持非(半)结构化:列式储存和KV结构;
  • 容错性:分布式架构,高效错误转移;
  • 硬件成本低廉:类似于 Hadoop 的分布式集群,硬件成本低廉;
  • 第三方SQL支持:phoenix、hive、sparkSql等等;
  • 开源:基于java开发的开源软件,社区活跃;

HBase环境搭建

CDH

CM安装HBase比较简单,参照安装步骤即可:

图片 2

image.png

分布式环境

前提条件

  1. Hadoop集群:hadoop01,hadoop02, hadoop03
  2. 用户互信
  3. HBase安装包
  4. JDK
  5. Zookeeper

安装部署

  1. 解压安装包:sudo tar -zxf hbase-1.2.4-bin.tar.gz -C /usr/local/hbase/
  2. 配置环境变量:vi /etc/profile ; export HBASE_HOME=/usr/local/hbase/
  3. 修改配置文件:$HBASE_HOME/conf/hbase-site.xml
<configuration>
  <property>
    <name>hbase.rootdir</name>
       <value>hdfs://nameservice1/hbase</value>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
  </property>
</configuration>
  1. 修改启动脚本:$HBASE_HOME/conf/hbase-env.sh; export JAVA_HOME=/usr/java/jdk/
  2. 修改HMaster配置:$HBASE_HOME/conf/regionservers
hadoop01
hadoop02
hadoop03
  1. 新增备用HMater: $HBASE_HOME/conf/backup-masters
hadoop02
  1. 启动集群:$HBASE_HOME/bin/start-hbase.sh
  2. 验证环境:
create 'hello', 'cf'
put 'hello', 'one', 'cf:a', 'b'
get 'hello', 'one'

HBase架构

看图说话

HBase采用Master/Slave架构搭建集群,它隶属于Hadoop生态系统,由一下类型节点组成:HMaster节点、HRegionServer节点、ZooKeeper集群,而在底层,它将数据存储于HDFS中,因而涉及到HDFS的NameNode、DataNode等,总体结构如下:

图片 3

image.png

  • HMaster :
  1. 管理HRegionServer,实现其负载均衡;
  2. 管理和分配HRegion;实现DDL操作;
  3. 管理namespace和table的元数据;权限控制;
  • HRegionServer :
  1. 存放和管理本地HRegion;
  2. 读写HDFS,管理Table中的数据;
  3. Client直接通过HRegionServer读写数据;
  • ZooKeeper :
  1. 存放整个 HBase集群的元数据以及集群的状态信息;
  2. 实现HMaster主从节点的failover。
  • HRegion:
  1. HBase表数据按行切分成多个HRegion;
  2. HRegion按照列簇切分成多个Store;
  3. 每个Store由一个MemStore和多个StoreFile(HFile)组成;
  4. 每个HRegion还包含一个HLog文件,用于数据恢复;
  • Hadoop:
  1. RegionServer通过DFS Client读写HDFS数据;
  2. RS和DN尽量保证在统一节点,确保数据本地化,提升读写性能;
  3. MemStore满足一定条件下会Flush到HDFS上的HFile文件。

HBaseTable

HBase表主要包含namespace(命名空间)、tableName(表名)、rowKey(主键)、columnFamily(列簇)、qualifier(列)、cell(值)、timeStamp(版本),用结构化的形式展现如下:

NameSpace TableName RowKey CF1:Name CF2:Age
Default HelloWorld 001 Allen 28
Default HelloWorld 002 Curry 26
Default HelloWorld 003 Brant 20
Default HelloWorld 004 Sean 31
  • 存储:表HelloWorld数据量较小,暂时会保存在一个RegionServer的一个Region内;
  • Split:当Region达到最大Region限制(假定256M)后,则会Split成两个Region,这里假定001和002在RegionA,003和004在RegionB:

图片 4

image.png

读流程

HBase表数据所在Region,Region所在RegionServer,均存放在HBase表hbase:meta,由于元数据较小,一个2G的HRegion大概可以支持4PB的数据,所以meta表是不可Split的。Meta表本身是一个HBase表,该表所在RS的地址存放在ZK,于是读数据的流程如下:

  1. 需求:从HellWorld表读取002这条记录;
  2. Client从ZK读取meta表所在RegionServer地址信息;
  3. Client和Meta表所在RS建立连接,获取HelloWorld表当中RowKey=002的记录所在的RS地址;并将该地址缓存在客户端;
  4. Client和002所在RS建立连接,申请查询002记录数据;
  5. 002所在RS根据RowKey获取Region信息,并初始化Scaner,Scaner优先扫描BlockCache,然后扫描MemStore,然后扫描StoreFile(HFile),直到找到002记录为止;
  6. RS返回结果数据给Client。

写流程

  1. 需求:写入005到HelloWorld表;
  2. Client从ZK去读Meta表所在RS地址;
  3. Client读取Meta表数据,确认005应该写入RS地址;
  4. Client将005数据写入RS02的HLog,用于灾备恢复,然后写入RegionB的memStore;此刻写操作已完成,反馈给client;
  5. 当memStore满足一定条件下会Flush到hdfs,hdfs再根据写策略复制副本。

Split和Compaction

HBase扩展和负载均衡的基本单位是Region。Region从本质上说是行的集合。当Region的大小达到一定的阈值,该Region会自动分裂(split),或者当该Region的HFile数太多,也会自动合并(compaction)。

对于一张表(HTable)而言,初始时只会有一个Region。表的数据量不断增加,系统会监控此表以确保数据量不会超过一个配置的阈值。如果系统发现表容量超过了限制,该Region会被一分为二。分裂主要看行键(row key),从Region正中的键开始分裂,并创建容量大致相等的两个Region。

根据上述写流程会发现HBase是一种Log-Structured Merge Tree架构模式,用户数据写入先写WAL,再写缓存,满足一定条件后缓存数据会执行flush操作真正落盘,形成一个数据文件HFile。随着数据写入不断增多,flush次数也会不断增多,进而HFile数据文件就会越来越多。然而,太多数据文件会导致数据查询IO次数增多,因此HBase尝试着不断对这些文件进行合并,这个合并过程称为Compaction。

BlockCache

上述读流程中RS会优先扫描BlockCache,BlockCache是一个读缓存,即“引用局部性”原理(也应用于CPU,分空间局部性和时间局部性,空间局部性是指CPU在某一时刻需要某个数据,那么有很大的概率在一下时刻它需要的数据在其附近;时间局部性是指某个数据在被访问过一次后,它有很大的概率在不久的将来会被再次的访问),将数据预读取到内存中,以提升读的性能。

HBase数据按照block块存储,默认是64K,HBase中Block分为四种类型:

  • Data Block用于存储实际数据,通常情况下每个Data Block可以存放多条KeyValue数据对;
  • Index Block通过存储索引数据加快数据查找;
  • Bloom Block通过一定算法可以过滤掉部分一定不存在待查KeyValue的数据文件,减少不必要的IO操作;
  • Meta Block主要存储整个HFile的元数据。

HBase中提供两种BlockCache的实现:默认on-heap LruBlockCache和BucketCache(通常是off-heap)。通常BucketCache的性能要差于LruBlockCache,然而由于GC的影响,LruBlockCache的延迟会变的不稳定,而BucketCache由于是自己管理BlockCache,而不需要GC,因而它的延迟通常比较稳定,这也是有些时候需要选用BucketCache的原因。

  • LruBlockCache :HBase默认的BlockCache实现方案。Block数据块都存储在 JVM heap内,由JVM进行垃圾回收管理。它将内存从逻辑上分为了三块:single-access区、mutil-access区、in-memory区,分别占到整个BlockCache大小的25%、50%、25%。一次随机读中,一个Block块从HDFS中加载出来之后首先放入signle区,后续如果有多次请求访问到这块数据的话,就会将这块数据移到mutil-access区。而in-memory区表示数据可以常驻内存,一般用来存放访问频繁、数据量小的数据,比如元数据,用户也可以在建表的时候通过设置列族属性IN-MEMORY= true将此列族放入in-memory区。很显然,这种设计策略类似于JVM中young区、old区以及perm区。无论哪个区,系统都会采用严格的Least-Recently-Used算法,当BlockCache总量达到一定阈值之后就会启动淘汰机制,最少使用的Block会被置换出来,为新加载的Block预留空间。

图片 5

image.png

  • BucketCache :很明显LruBlockCache的缺陷是GC,当吞吐量徒增,GC不及时则会造成RS因为OOM停止工作;于是BucketCache引入了堆外内存来缓存Block数据,当然BucketCache通过配置可以工作在三种模式下:heap,offheap和file。无论工作在那种模式下,BucketCache都会申请许多带有固定大小标签的Bucket,一种Bucket存储一种指定BlockSize的数据块,BucketCache会在初始化的时候申请14个不同大小的Bucket,而且即使在某一种Bucket空间不足的情况下,系统也会从其他Bucket空间借用内存使用,不会出现内存使用率低的情况。接下来再来看看不同工作模式,heap模式表示这些Bucket是从JVM Heap中申请,offheap模式使用DirectByteBuffer技术实现堆外内存存储管理,而file模式使用类似SSD的高速缓存文件存储数据块。

图片 6

image.png

  • CombinedBlockCache :实际实现中,HBase将BucketCache和LRUBlockCache搭配使用,称为CombinedBlockCache。和DoubleBlockCache不同,系统在LRUBlockCache中主要存储Index Block和Bloom Block,而将Data Block存储在BucketCache中。因此一次随机读需要首先在LRUBlockCache中查到对应的Index Block,然后再到BucketCache查找对应数据块。BucketCache通过更加合理的设计极大降低了JVM GC对业务请求的实际影响。

图片 7

image.png

MemStore

HBase写入数据会先写WAL,再写缓存,WAL是用于RS故障后的数据恢复,而缓存MemStore则是为了提高写入数据的性能。但MemStore是基于内存,一方面空间有限,一方面数据容易丢失,所以RegionServer会在满足一定条件下讲MemStore数据Flush到HDFS,条件如下:

  1. Memstore级别限制:当Region中任意一个MemStore的大小达到了上限(hbase.hregion.memstore.flush.size,默认128MB),会触发Memstore刷新。

  2. Region级别限制:当Region中所有Memstore的大小总和达到了上限(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size,默认 2* 128M = 256M),会触发memstore刷新。

  3. Region Server级别限制:当一个Region Server中所有Memstore的大小总和达到了上限(hbase.regionserver.global.memstore.upperLimit * hbase_heapsize,默认 40%的JVM内存使用量),会触发部分Memstore刷新。Flush顺序是按照Memstore由大到小执行,先Flush Memstore最大的Region,再执行次大的,直至总体Memstore内存使用量低于阈值(hbase.regionserver.global.memstore.lowerLimit * hbase_heapsize,默认 38%的JVM内存使用量);HBase1.0后使用新参数hbase.regionserver.global.memstore.size。

  4. 当一个Region Server中HLog数量达到上限(可通过参数hbase.regionserver.maxlogs配置)时,系统会选取最早的一个 HLog对应的一个或多个Region进行flush

  5. HBase定期刷新Memstore:默认周期为1小时,确保Memstore不会长时间没有持久化。为避免所有的MemStore在同一时间都进行flush导致的问题,定期的flush操作有20000左右的随机延时。

  6. 手动执行flush:用户可以通过shell命令 flush ‘tablename’或者flush ‘region name’分别对一个表或者一个Region进行flush。