阅读 396

HBase:基础篇

HBase 概述

HBase(Hadoop Database)是一个高可靠、高性能、面向列、可伸缩的KV分布式数据库,利用HBase技术可在廉价PC上搭建起大规模结构化存储集群。

HBase参考Google的BigTable建模,使用类似GFS的HDFS作为底层文件存储系统,在其上可以运行MapReduce批量处理数据,使用ZooKeeper作为协同服务组件。

发展历史

google“三篇论文”——GFS、MapReduce、BigTable。

  • GFS: (The Google File System):揭示了如何在大量廉价机器基础上存储海量数据。

  • MapReduce(Simplef iedData Processing on Large Clusters):论文论述了如何在大量廉价机器的基础上稳定地实现超大规模的并行数据处理。

  • BigTable: A Distributed StorageSystem for Structured Data 》,用于解决Google内部海量结构化数据的存储以及高效读写问题。

Apache HBase最初是Powerset公司为了处理自然语言搜索产生的海量数据而开展的项目,由Chad Walters和Jim Kellerman两人发起,经过两年的发展之后被Apache基金会收录为顶级项目,同时成为非常活跃、影响重大的项目。

2015年2月社区发布了1.0.0版本,规范了Hbase的版本号,此后的版本号都统一遵循semanticversioning语义,如下图:

可以理解为MAJOR、MINOR相同的情况下,PATCH版本越大,系统越可靠。

Hbase优缺点

优点

  • 容量巨大:HBase的单表可以支持千亿行、百万列的数据规模,数据容量可以达到TB甚至PB级别。传统的关系型数据库,如Oracle和MySQL等,如果单表记录条数超过亿行,读写性能都会急剧下降,在HBase中并不会出现这样的问题。

  • 良好的可扩展性:HBase集群可以非常方便地实现集群容量扩展,主要包括数据存储节点扩展以及读写服务节点扩展。HBase底层数据存储依赖于HDFS系统,HDFS可以通过简单地增加DataNode实现扩展,HBase读写服务节点也一样,可以通过简单的增加RegionServer节点实现计算层的扩展。

  • 稀疏性:HBase支持大量稀疏存储,即允许大量列值为空,并不占用任何存储空间。这与传统数据库不同,传统数据库对于空值的处理要占用一定的存储空间,这会造成一定程度的存储空间浪费。因此可以使用HBase存储多至上百万列的数据,即使表中存在大量的空值,也不需要任何额外空间。

  • 高性能:HBase目前主要擅长于OLTP场景,数据写操作性能强劲,对于随机单点读以及小范围的扫描读,其性能也能够得到保证。对于大范围的扫描读可以使用MapReduce提供的API,以便实现更高效的并行扫描。

  • 多版本:HBase支持多版本特性,即一个KV可以同时保留多个版本,用户可以根据需要选择最新版本或者某个历史版本。

  • Hadoop原生支持:HBase是Hadoop生态中的核心成员之一,很多生态组件都可以与其直接对接。

缺点

  • HBase本身不支持很复杂的聚合运算(如Join、GroupBy等)。如果业务中需要使用聚合运算,可以在HBase之上架设Phoenix组件或者Spark组件,前者主要应用于小规模聚合的OLTP场景,后者应用于大规模聚合的OLAP场景。

  • HBase本身并没有实现二级索引功能,所以不支持二级索引查找。好在针对HBase实现的第三方二级索引方案非常丰富,比如目前比较普遍的使用Phoenix提供的二级索引功能。

  • HBase原生不支持全局跨行事务,只支持单行事务模型。同样,可以使用Phoenix提供的全局事务模型组件来弥补HBase的这个缺陷。

数据模型

逻辑视图

在具体了解逻辑视图之前有必要先看看HBase中的基本概念。

  • table:一个表包含多行数据。

  • RowKey:表中每条记录的主键;

  • Column Family:列族,将表进行横向切割,后面简称CF;

  • Column:属于某一个列族,可动态添加列;

  • Version Number:类型为Long,默认值是系统时间戳,可由用户自定义;

  • Value:真实的数据。

  • Region:一段数据的集合;

HBase逻辑视图

物理视图

image.png

image.png

HBase 原理

LSM

TTDO

整体架构

HBase客户端

  1. HBase客户端(Client)提供了Shell命令行接口、原生Java API编程接口、Thrift/REST API编程接口以及MapReduce编程接口
  2. HBase客户端支持所有常见的DML操作以及DDL操作,即数据的增删改查和表的日常维护等。其中Thrift/REST API主要用于支持非Java的上层业务需求,MapReduce接口主要用于批量数据导入以及批量数据读取。

HBase客户端访问数据行之前,首先需要通过元数据表定位目标数据所在RegionServer,之后才会发送请求到该RegionServer。同时这些元数据会被缓存在客户端本地,以方便之后的请求访问。如果集群RegionServer发生宕机或者执行了负载均衡等,从而导致数据分片发生迁移,客户端需要重新请求最新的元数据并缓存在本地。

ZooKeeper

  1. 实现Master高可用:通常情况下系统中只有一个Master工作,一旦ActiveMaster由于异常宕机,ZooKeeper会检测到该宕机事件,并通过一定机制选举出新的Master,保证系统正常运转。

  2. 管理系统核心元数据:比如,管理当前系统中正常工作的RegionServer集合,保存系统元数据表hbase:meta所在的RegionServer地址等。

  3. 参与RegionServer宕机恢复:ZooKeeper通过心跳可以感知到RegionServer是否宕机,并在宕机后通知Master进行宕机处理。

  4. 实现分布式表锁:HBase中对一张表进行各种管理操作(比如alter操作)需要先加表锁,防止其他用户对同一张表进行管理操作,造成表状态不一致。和其他RDBMS表不同,HBase中的表通常都是分布式存储,ZooKeeper可以通过特定机制实现分布式表锁。

Master

  1. 处理用户的各种管理请求,包括建表、修改表、权限操作、切分表、合并数据分片以及Compaction等。
  2. 管理集群中所有RegionServer,包括RegionServer中Region的负载均衡、RegionServer的宕机恢复以及Region的迁移等。
  3. 清理过期日志以及文件,Master会每隔一段时间检查HDFS中HLog是否过期、HFile是否已经被删除,并在过期之后将其删除。

RegionServer

RegionServer主要用来响应用户的IO请求,是HBase中最核心的模块,由WAL(HLog)、BlockCache以及多个Region构成。

  • WAL(HLog):HLog在HBase中有两个核心作用——其一,用于实现数据的高可靠性,HBase数据随机写入时,并非直接写入HFile数据文件,而是先写入缓存,再异步刷新落盘。为了防止缓存数据丢失,数据写入缓存之前需要首先顺序写入HLog,这样,即使缓存数据丢失,仍然可以通过HLog日志恢复;其二,用于实现HBase集群间主从复制,通过回放主集群推送过来的HLog日志实现主从复制。

  • BlockCache:HBase系统中的读缓存。客户端从磁盘读取数据之后通常会将数据缓存到系统内存中,后续访问同一行数据可以直接从内存中获取而不需要访问磁盘。

  • Region:数据表的一个分片,当数据表大小超过一定阈值就会“水平切分”,分裂为两个Region。Region是集群负载均衡的基本单位。通常一张表的Region会分布在整个集群的多台RegionServer上,一个RegionServer上会管理多个Region,当然,这些Region一般来自不同的数据表。

  • Store:一个Region由一个或者多个Store构成,Store的个数取决于表中列簇(columnfamily)的个数,多少个列簇就有多少个Store。HBase中,每个列簇的数据都集中存放在一起形成一个存储单元Store,因此建议将具有相同IO特性的数据设置在同一个列簇中。

  • MemStore & HFile:MemStore称为写缓存,用户写入数据时首先会写到MemStore,当MemStore写满之后(缓存数据超过阈值,默认128M)系统会异步地将数据f lush成一个HFile文件。显然,随着数据不断写入,HFile文件会越来越多,当HFile文件数超过一定阈值之后系统将会执行Compact操作,将这些小文件通过一定策略合并成一个或多个大文件。

HDFS

HBase底层依赖HDFS组件存储实际数据,包括用户数据文件、HLog日志文件等最终都会写入HDFS落盘。HDFS是Hadoop生态圈内最成熟的组件之一,数据默认三副本存储策略可以有效保证数据的高可靠性。HBase内部封装了一个名为DFSClient的HDFS客户端组件,负责对HDFS的实际数据进行读写访问。

HBase 读写流程

HBase Region查找

  • 客户端根据写入的表以及rowkey在元数据缓存中查找,如果能够查找出该rowkey所在的RegionServer以及Region,就可以直接发送写入请求(携带Region信息)到目标RegionServer。

  • 如果客户端缓存中没有查到对应的rowkey信息,需要首先到ZooKeeper上/hbase-root/meta-region-server节点查找Hbase元数据表所在的RegionServer。向hbase:meta所在的RegionServer发送查询请求,在元数据表中查找rowkey所在的RegionServer以及Region信息。客户端接收到返回结果之后会将结果缓存到本地,以备下次使用。

  • 客户端根据rowkey相关元数据信息将写入请求发送给目标RegionServer,Region Server接收到请求之后会解析出具体的Region信息,查到对应的Region对象,并将数据写入目标Region的MemStore中。

写流程

1)客户端处理阶段:客户端将用户的写入请求进行预处理,并根据集群元数据定位写入数据所在的RegionServer,将请求发送给对应的RegionServer。

2)Region写入阶段:RegionServer接收到写入请求之后将数据解析出来,首先写入WAL,再写入对应Region列簇的MemStore。

3)MemStore Flush阶段:当Region中MemStore容量超过一定阈值,系统会异步执行flush操作,将内存中的数据写入文件,形成HFile。

读流程

和写流程相比,HBase读数据的流程更加复杂。主要基于两个方面的原因:

  • HBase一次范围查询可能会涉及多个Region、多块缓存甚至多个数据存储文件;

  • HBase中更新操作以及删除操作的实现都很简单,更新操作并没有更新原有数据,而是使用时间戳属性实现了多版本;删除操作也并没有真正删除原有数据,只是插入了一条标记为"deleted"标签的数据,而真正的数据删除发生在系统异步执行Major Compact的时候。

很显然,这种实现思路大大简化了数据更新、删除流程,但是对于数据读取来说却意味着套上了层层枷锁:读取过程需要根据版本进行过滤,对已经标记删除的数据也要进行过滤。

HBase Compaction

image.png

HBase根据合并规模将Compaction分为两类:Minor Compaction和MajorCompaction。

  • Minor Compaction是指选取部分小的、相邻的HFile,将它们合并成一个更大的HFile。

  • Major Compaction是指将一个Store中所有的HFile合并成一个HFile,这个过程还会完全清理三类无意义数据:被删除的数据、TTL过期数据、版本号超过设定版本号的数据。

一般情况下,Major Compaction持续时间会比较长,整个过程会消耗大量系统资源,对上层业务有比较大的影响。因此线上部分数据量较大的业务通常推荐关闭自动触发Major Compaction功能,改为在业务低峰期手动触发(或设置策略自动在低峰期触发)

Compaction核心作用如下:

  1. 合并小文件,减少文件数,稳定随机读延迟。

  2. 提高数据的本地化率。

  3. 清除无效数据,减少数据存储量。

HBase中触发Compaction的时机有很多,最常见的时机有如下三种:MemStoreFlush、后台线程周期性检查以及手动触发。

HBase热点问题

热点是指发生在大量的Client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。 大量访问会使热点Region所在的单个机器超出自身承受能力,引起性能下降甚至Region不可用,也会影响同一个RegionServer上的其他Region。

产生原因

  1. HBase创建表默认只有一个分区
  2. RowKey设计不合理

解决方案

  1. HBase创建表时指定分区
  2. 合理设计RowKey

预分区

默认情况下,在创建 HBase 表的时候会自动创建一个 region 分区,当导入数据的时候,所有的 HBase 客户端都向这一个 region 写数据,直到这个 region 足够大了才进行切分。还有一种是可以加快批量写入速度的方法,就是通过预先创建一些空的 regions,这样当数据写入 HBase 时,会按照 region 分区情况,在集群内做数据的负载均衡。

RowKey设计

设计原则

  1. 唯一性

    必须在设计上保证其唯一性。RowKey是按照字典顺序排序存储的,因此,设计RowKey 的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问 的数据放到一块。

  2. 长度原则

    RowKey 是一个二进制码流,RowKey的长度被很多开发者建议说设计在10~100 个字节,不过建议是越短越好,不要超过16个字节。

    原因如下:

    • 数据的持久化文件HFile中是按照KeyValue存储的,如果RowKey过长比如100 个字节,1000万列数据光RowKey就要占用100*1000 万=10亿个字节,将近1G数据,这会极大影响HFile的存储效率;

    • MemStore将缓存部分数据到内存,如果RowKey字段过长内存的有效利用率会降低, 系统将无法缓存更多的数据,这会降低检索效率。因此RowKey的字节长度越短越好。

    • 目前操作系统是都是 64 位系统,内存 8 字节对齐。控制在 16 个字节,8 字节的整数倍利用操作系统的最佳特性。

  3. 散列原则

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

散列常见设计方法

Salting(加盐)

image.png

Hashing(哈希)

image.png

Reversing(反转)

image.png

反转可以使得RowKey中经常改变的部分(最没有意义的部分)放在前面,这样可以有效的随机rowkey,但是牺牲RowKey的有序性。反转RowKey的例子以手机号为RowKey,可以将手机号反转后的字符串作为RowKey,这样的就避免了以手机号那样比较固定开头导致热点问题。

文章分类
后端
文章标签