物联网架构之HBase
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:Linux运维老纪的首页,持续学习,不断总结,共同进步,活到老学到老
导航剑指大厂系列:全面总结 运维核心技术:系统基础、数据库、网路技术、系统安全、自动化运维、容器技术、监控工具、脚本编程、云服务等。
常用运维工具系列:常用的运维开发工具, zabbix、nagios、docker、k8s、puppet、ansible等
数据库系列:详细总结了常用数据库 mysql、Redis、MongoDB、oracle 技术点,以及工作中遇到的 mysql 问题等
懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作
数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨
1、概述:Hbase的优缺点和适用场景
优点:HBase 底层基于HDFS存储,高可用、高扩展、强一致性,支持上亿级别数据
10亿数据 性能测试结果
1. 写性能:集群吞吐量最大可以达到70000+ ops/sec,延迟在几个毫秒左右。网络带宽是主要瓶颈,如果将千兆网卡换成万兆网卡,吞吐量还可以继续增加,甚至达到目前吞吐量的两倍。
2. 读性能:很多人对HBase的印象可能都是写性能很好、读性能很差,但实际上HBase的读性能远远超过大家的预期。集群吞吐量最大可以达到26000+,单台吞吐量可以达到8000+左右,延迟在几毫秒~20毫秒左右。IO和CPU是主要瓶颈。
3. Range 扫描性能:集群吞吐量最大可以达到14000左右,系统平均延迟在几毫秒~60毫秒之间(线程数越多,延迟越大);其中IO和网络带宽是主要瓶颈。
缺点:
占用内存很大,且鉴于建立在为批量分析而优化的HDFS上,导致读取性能不高
2 Hbase 简介
HBase是一个开源的、分布式的、可伸缩的、非关系型数据库,设计用来存储大量的数据表,这些数据表由行和列组成,非常适合存储非结构化和半结构化数据。HBase的名称来源于它是Hadoop Database的一个变种,用于构建在Hadoop Distributed File System (HDFS)之上,利用MapReduce进行数据处理。HBase的设计模仿了Google的BigTable系统,提供了高可靠性、高性能的数据存储服务。
HBase的主要特点包括:
- 可伸缩性:HBase能够处理PB级别的数据,并且能够支持数百万的并发用户访问。
- 高可靠性:通过复制和分布式存储机制,确保数据的可靠性和容错性。
- 列族和列动态变化:允许用户在不改变表结构的情况下添加新的列,使得数据模型非常灵活。
- 支持多种访问接口:提供了命令行工具、Thrift Gateway、REST Gateway等多种访问接口,支持多种编程语言和系统进行数据交互。
- 与Hadoop生态系统集成:HBase与Hadoop的其他组件(如Pig、Hive等)集成,提供了丰富的数据处理和分析能力。
HBase的数据模型基于行键(RowKey)和列族(Column Family),每行数据由多个列组成,每列都可以存储多个版本的数据,这些数据通过时间戳进行索引。HBase的架构设计使得它能够处理大规模的数据存储和快速的数据检索需求。
此外,HBase还支持数据的版本控制,每个cell可以保存多个数据版本,这些版本通过时间戳进行索引,提供了数据的历史版本查询功能。HBase的这种设计使得它非常适合用于需要频繁读写大量数据的场景,如大数据分析、日志数据分析等
2.1 HBase 特点
- 海量存储
HBase适合存储 PB 级别的海量数据,能在几十到百毫秒内返回数据。
- 列式存储
HBase是根据列族
来存储数据的。列族下面可以有非常多的列,在创建表的时候列族就必须指定。
- 高并发
在并发的情况下,HBase的单个IO延迟下降并不多,能获得高并发、低延迟的服务。
- 稀疏性
HBase的列具有灵活性,在列族中,你可以指定任意多的列,在列数据为空的情况下,是不会占用存储空间的。
- 极易扩展
- 基于 RegionServer 的扩展,通过横向添加 RegionSever 的机器,进行水平扩展,提升 HBase 上层的处理能力,提升HBase服务更多 Region 的能力。
- 基于存储的扩展(HDFS)。
2.2 HBase 逻辑结构
逻辑思维层面 HBase的存储模型如下:
- Table(表):
表由一个或者多个列族
构成。数据的属性如name、age、TTL(超时时间)等都在列族里边定义。定义完列族的表是个空表,只有添加了数据行以后,表才有数据。
- Column (列):
HBase 中的每个列都由 Column Family(列族) 和 Column Qualifier(列限定符)进行限定,例如 info:name、info:age。建表时只需指明列族,而列限定符无需预先定义。
- Column Family(列族):
- 多个列
组合
成一个列族。建表时不用创建列,在 HBase 中列是可增减变化
的!唯一要确定的是列族
,表有几个列族在开始创建时就定好的。表的很多属性,比如数据过期时间、数据块缓存以及是否使用压缩等都是定义在列族上的。 - HBase 会把相同列族的几个列数据尽量放在同一台机器上。
- Row(行):
一行包含多个列,这些列通过列族来分类。行中的数据所属的列族从该表所定义的列族中选取。由于HBase是一个面向列存储的数据库,所以一个行中的数据可以分布在不同的服务器上
。
- RowKey(行键):
RowKey 类似 MySQL 中的主键,在 HBase 中 RowKey 必须有且 RowKey 是按照字典排序的,如果用户不指定 RowKey 系统会自动生成不重复字符串。查询数据时只能根据 RowKey 进行检索,所以 Table 的 RowKey 设计十分重要。
- Region(区域):
- Region 就是若干行数据的集合。HBase 中的 Region 会根据数据量的大小动态分裂,Region是基于HDFS实现的,关于Region的存取操作都是调用HDFS客户端完成的。同一个行键的 Region 不会被拆分到多个 Region 服务器上。
- Region 有一点像关系型数据的分区,数据存放在Region中,当然Region下面还有很多结构,确切来说数据存放在MemStore和HFile中。访问HBase 时先去HBase 系统表查找定位这条记录属于哪个Region ,然后定位到这个Region 属于哪个服务器,然后就到哪个服务器里面查找对应Region 中的数据。
- RegionServer:
RegionServer 就是存放Region的容器,直观上说就是服务器上的一个服务。负责管理维护 Region。
2.3 HBase 物理存储
以上只是一个基本的逻辑结构,底层的物理存储结构才是重中之重的内容,看下图
- NameSpace:
命名空间,类似关系型数据库 DatabBase 概念,每个命名空间下有多个表。HBase有两个自带的命名空间,分别是hbase
和default
,hbase 中存放的是 HBase 内置的表,default 表是用户默认使用的命名空间。
- TimeStamp:
时间戳,用于标识数据的不同版本(version),每条数据写入时如果不指定时间戳,系统会自动添加为其写入 HBase 的时间。并且读取数据的时候一般只拿出数据的Type符合,时间戳最新的数据。之所以按照Type取数据是因为HBase的底层HDFS支持增删查,但不支持改
。
- Cell:
单元格,由 {rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。cell 中的数据是没有
类型的,全部是字节码
形式存储。
3 HBase 底层架构
3.1 Client
Client 包含了访问 Hbase 的接口,另外 Client 还维护了对应的 cache 来加速 Hbase 的访问,比如缓存元数据的信息。
3.2 Zookeeper
HBase 通过 Zookeeper 来做 Master 的高可用、RegionServer 的监控、元数据的入口以及集群配置的维护等工作。Zookeeper 职责如下:
- 通过Zoopkeeper来保证集群中只有1个Master 在运行,如果Master 发生异常会通过竞争机制产生新的Master 来提供服务。
- 通过 Zoopkeeper 来监控 RegionServer 的状态,当RegionSevrer有异常的时候,通过回调的形式通知MasterRegionServer上下线的信息。
- 通过 Zoopkeeper 存储元数据 hbase:meata 的统一入口地址。
3.3 Master
Master 在 HBase 中的地位比其他类型的集群弱很多!数据的读写操作与他没有关系,它挂了之后,集群照样运行。但是Master 也不能宕机太久,有很多必要的操作,比如创建表、修改列族配置等DDL跟Region的分割与合并都需要它的操作。
- 负责启动的时候分配Region到具体的 RegionServer。
- 发现失效的 Region,并将失效的 Region 分配到正常的 RegionServer 上。
- 管理HRegion服务器的负载均衡,调整HRegion分布。
- 在HRegion分裂后,负责新HRegion的分配。
HBase 中可以启动多个Master,通过 Zookeeper 的 Master Election 机制保证总有一个 Master 运行。
3.4 RegionServer
HregionServer 直接对接用户的读写请求,是真正的干活的节点。它的功能概括如下:
- 管理Master为其分配的Region。
- 处理来自客户端的读写请求。
- 负责和底层HDFS的交互,存储数据到HDFS。
- 负责Region变大以后的拆分。
- 负责StoreFile的合并工作。
- ZooKeeper 会监控 RegionServer 的上下线情况,当 ZK 发现某个 HRegionServer 宕机之后会通知 Master 进行失效备援。下线的 RegionServer 所负责的 Region 暂时停止对外提供服务,Master 会将该 RegionServer 所负责的 Region 转移到其他 RegionServer 上,并且会对 下线RegionServer 上存在 MemStore 中还未持久化到磁盘中的数据由 WAL重播进行恢复。
3.5 WAL
WAL (Write-Ahead-Log) 预写日志是 HBase 的 RegionServer 在处理数据插入和删除的过程中用来记录操作内容的一种日志。每次Put、Delete等一条记录时,首先将其数据写入到 RegionServer 对应的HLog文件中去。只有当WAL日志写入成功的时候,客户端才会被告诉提交数据成功。如果写WAL失败会告知客户端提交失败,这其实就是数据落地的过程。
WAL是保存在HDFS上的持久化文件。数据到达 Region 时先写入WAL,然后被加载到MemStore中。这样就算Region宕机了,操作没来得及执行持久化,也可以再重启的时候从WAL加载操作并执行。跟Redis的AOF类似。
- 在一个 RegionServer 上的所有 Region 都共享一个 HLog,一次数据的提交先写入WAL,写入成功后,再写入MenStore之中。当MenStore的值达到一定的时候,就会形成一个个StoreFile。
- WAL 默认是开启 的,也可以手动关闭它,这样增删改操作会快一点。但是这样做牺牲的是数据的安全性。如果不想关闭WAL,又不想每次都耗费那么大的资源,每次改动都调用HDFS客户端,可以选择异步的方式写入WAL(默认间隔1秒写入)
- 如果你学过 Hadoop 中的 Shuffle(edits文件) 机制的就可以猜测到 HBase 中的 WAL 也是一个滚动的日志数据结构,一个WAL实例包含多个WAL文件,WAL被触发滚动的条件如下。
- WAL的大小超过了一定的阈值。
- WAL文件所在的HDFS文件块快要满了。
- WAL归档和删除。
3.5 Region
每一个 Region 都有起始 RowKey 和结束 RowKey,代表了存储的Row的范围。从大图中可知一个Region有多个Store,一个Store就是对应一个列族的数据,Store 由 MemStore 和 HFile 组成的。
3.6 Store
Store 由 MemStore 跟 HFile 两个重要的部分。
3.6.1 MemStore
每个 Store 都有一个 MemStore 实例,数据写入到 WAL 之后就会被放入 MemStore 中。MemStore是内存的存储对象,当 MemStore 的大小达到一个阀值(默认64MB)时,MemStore 会被 flush到文件,即生成一个快照。目前HBase 会有一个线程来负责MemStore 的flush操作。
3.6.2 StoreFile
MemStore 内存中的数据写到文件后就是StoreFile,StoreFile底层是以 HFile 的格式保存。HBase以Store的大小来判断是否需要切分Region。
3.6.3 HFile
在Store中有多个HFile,每次刷写都会形成一个HFile文件落盘在HDFS上。HFile文件也会动态合并,它是数据存储的实体。
这里提出一点疑问:操作到达Region时,数据进入HFile之前就已经被持久化到WAL了,而WAL就是在HDFS上的,为什么还要从WAL加载到MemStore中,再刷写成HFile呢?
- 由于HDFS支持文件创建、追加、删除,但不能修改!但对数据库来说,数据的顺序非常重要!
- 第一次WAL的持久化是为了保证数据的安全性,无序的。
- 再读取到MemStore中,是为了排序后存储。
- 所以MemStore的意义在于维持数据按照RowKey的字典序排列,而不是做一个缓存提高写入效率。
3.7 HDFS
HDFS 为 HBase 提供最终的底层数据存储服务,HBase 底层用HFile格式 (跟hadoop底层的数据存储格式类似) 将数据存储到HDFS中,同时为HBase提供高可用(Hlog存储在HDFS)的支持,具体功能概括如下:
- 提供元数据和表数据的底层分布式存储服务
- 数据多副本,保证的高可靠和高可用性
4 HBase 读写
在HBase集群中如果我们做 DML 操作是不需要关心 HMaster 的,只需要从 ZooKeeper 中获得hbase:meta 数据地址,然后从RegionServer中增删查数据即可。
4.1 HBase 写流程
- Client 先访问 zookeeper,访问 /hbase/meta-region-server 获取 hbase:meta 表位于哪个 Region Server。
- 访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 Region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
- 与目标 Region Server 进行通讯。
- 将数据顺序写入(追加)到 WAL。
- 将数据写入对应的 MemStore,数据会在 MemStore 进行排序。
- 向客户端发送 ack,此处可看到数据不是必须落盘的。
- 等达到 MemStore 的刷写时机后,将数据刷写到 HFile
- 在web页面查看的时候会随机的给每一个Region生成一个随机编号。
4.2 HBase 读流程
- Client 先访问 ZooKeeper,获取 hbase:meta 表位于哪个 Region Server。
- 访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey, 查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以 及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
- 与目标 Region Server 进行通讯。
- 分别在 Block Cache(读缓存),MemStore 和 Store File(HFile)中查询目标数据,并将 查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。
- 将从文件HFile中查询到的数据块(Block,HFile 数据存储单元,默认大小为 64KB)缓存到 Block Cache。
- 将合并后的最终结果,然后返回时间最新的数据返回给客户端。
4.2.1 Block Cache
HBase 在实现中提供了两种缓存结构 MemStore(写缓存) 和 BlockCache(读缓存)。写缓存前面说过不再重复。
- HBase 会将一次文件查找的 Block块 缓存到 Cache中,以便后续同一请求或者邻近数据查找请求,可以直接从内存中获取,避免昂贵的IO操作。
- BlockCache是Region Server级别的,
- 一个Region Server只有一个Block Cache,在 Region Server 启动的时候完成 Block Cache 的初始化工作。
- HBase对Block Cache的管理分为如下三种。
- LRUBlockCache 是最初的实现方案,也是默认的实现方案,将所有数据都放入JVM Heap中,交给JVM进行管理。
- SlabCache 实现的是堆外内存存储,不再由JVM管理数据内存。一般跟第一个组合使用,单它没有改善 GC 弊端,引入了堆外内存利用率低。
- BucketCache 缓存淘汰不再由JVM管理 降低了Full GC 发生的频率。
重点:
读数据时不要理解
为先从 MemStore 中读取,读不到再读 BlockCache 中,还读不到再从HFile中读取,然后将数据写入到 BlockCache 中。因为如果人为设置导致磁盘数据new,内存数据old。你读取的时候会出错的!
结论:
HBase 把磁盘跟内存数据一起读,然后把磁盘数据放到 BlockCache中,BlockCache 是磁盘数据的缓存。HBase 是个读比写慢的工具。
4.3 HBase 为什么写比读快
- HBase 能提供实时计算服务主要原因是由其架构和底层的数据结构决定的,即由LSM-Tree(Log-Structured Merge-Tree) + HTable(Region分区) + Cache决定的。
- HBase 写入速度快是因为数据并不是真的立即落盘,而是先写入内存,随后异步刷入HFile。所以在客户端看来,写入速度很快。
- HBase 存储到内存中的数据是有序的,内存数据刷写到HFile时也是有序的。并且多个有序的HFile还会进行归并排序生成更大的有序HFile。性能测试发现顺序读写磁盘速度比随机读写磁盘快至少三个数量级!
- 读取速度快是因为它使用了LSM树型结构,因为磁盘寻址耗时远远大于磁盘顺序读取的时间,HBase的架构设计导致我们可以将磁盘寻址次数控制在性能允许范围内。
- LSM 树原理把一棵大树拆分成N棵小树,它首先写入内存中,随着小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树定期可以做merge操作来合并成一棵大树,以优化读性能。
4.3.1查询举例
- 根据RowKey能快速找到行所在的Region,假设有10亿条记录,占空间1TB。分列成了500个Region,那读取2G的记录,就能找到对应记录。
- 数据是按照列族存储的,假设分为3个列族,每个列族就是666M, 如果要查询的东西在其中1个列族上,1个列族包含1个或者多个 HStoreFile,假设一个HStoreFile是128M, 该列族包含5个HStoreFile在磁盘上. 剩下的在内存中。
- 内存跟磁盘中数据是排好序的,你要的记录有可能在最前面,也有可能在最后面,假设在中间,我们只需遍历2.5个HStoreFile共300M。
- 每个HStoreFile(HFile的封装),是以键值对(KV)方式存储,只要遍历一个个数据块中的key的位置,并判断符合条件可以了。一般key是有限的长度,假设KV比是1:19,最终只需要15M就可获取的对应的记录,按照磁盘的访问100M/S,只需0.15秒。加上Block Cache 会取得更高的效率。
- 大致理解读写思路后你会发现如果你在读写时设计的足够巧妙当然读写速度快的很咯。
5 HBase Flush
5.1 Flush
对于用户来说数据写到 MemStore 中就算OK,但对于底层代码来说只有数据刷到硬盘中才算彻底搞定了!因为数据是要写入到WAL(Hlog)中再写入到MemStore中的,flush有如下几个时机。
- 当 WAL 文件的数量超过设定值时 Region 会按照时间顺序依次进行刷写,直到 WAL 文件数量小于设定值。
- 当Region Server 中 MemStore 的总大小达到堆内存40%时,Region 会按照其所有 MemStore 的大小顺序(由大到小)依次进行阻塞刷写。直到Region Server中所有 MemStore 的总大小减小到上述值以下。当阻塞刷写到上个参数的0.95倍时,客户端可以继续写。
- 当某个 MemStore 的大小达到了128M时,其所在 Region 的所有 MemStore 都会阻塞刷写。
- 到达自动刷写的时间也会触发 MemStore 的 flush。自动刷新的时间间隔默认1小时。
5.2 StoreFile Compaction
由于 MemStore 每次刷写都会生成一个新的 HFile,且同一个字段的不同版本(timestamp) 和不同类型(Put/Delete)有可能会分布在不同的 HFile 中,因此查询时需要遍历所有的 HFile。为了减少 HFile 的个数跟清理掉过期和删除的数据,会进行 StoreFile Compaction。
Compaction 分为两种,分别是 Minor Compaction 和 Major Compaction。
- Minor Compaction会将临近的若干个较小的 HFile 合并成一个较大的 HFile,但不会清理过期和删除的数据。
- Major Compaction 会将一个 Store 下的所有的 HFile 合并成一个大 HFile,并且会清理掉过期和删除的数据。
5.3 Region Split
每个 Table 起初只有一个 Region,随着不断写数据 Region 会自动进行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但出于负载均衡的考虑, HMaster 有可能会将某个 Region 转移给其他的 Region Server。
Region Split 时机:
- 0.94 版本之前:
当 1 个 Region 中的某个 Store 下所有 StoreFile 的总大小超过 hbase.hregion.max.filesize(默认10G), 该 Region 就会进行拆分。
- 0.94 版本之后:
当 1 个 Region 中的某个 Store 下所有 StoreFile 的总大小超过 Min(R^2 * “hbase.hregion.memstore.flush.size=128M”,hbase.hregion.max.filesize"),该 Region 就会进行拆分,其 中 R 为当前 Region Server 中属于该 Table 的个数。
举例:
- 第一次的阈值是128,切分后结果64 , 64。
- 第二次阈值512M,64,512 ⇒ 54 + 256 + 256
- 最后会形成一个 64M…10G 的这样Region队列,会产生数据倾斜问题。
- 解决方法:提前做好Region组的规划,0-1k,1k-2k,2k-3k这样的。
官方不建议用多个列族,比如有CF1,CF2,CF3,但是 CF1数据很多而CF2跟CF3数据很少,那么当触发了region切分的时候,会把CF2跟CF3分成若干小份,不利于系统维护。
6 HBase 常见面试题
6.1 Hbase 中 RowKey 的设计原则
- RowKey 长度原则
二进制码流RowKey 最大长度 64Kb,实际应用中一般为 10-100bytes,以 byte[] 形式保存,一般设计定长。建议越短越好,因为HFile是按照KV存储的Key太大浪费空间。
- RowKey 散列原则
RowKey 在设计时候要尽可能的实现可以将数据均衡的分布在每个 RegionServer 上。
- RowKey 唯一原则
RowKey 必须在设计上保证其唯一性,RowKey 是按照字典顺序排序存储的,因此设计 RowKey 时可以将将经常读取的数据存储到一块。
6.2 HBase 在大数据体系位置
其实就简单的把HBase当成大数据体系下的DataBase来用就行,任何可以分析HBase的引擎比如MR、Hive、Spark等框架连接上HBase都可以实现控制。比如你可以把Hive跟HBase进行关联,Hive中数据不再由HDFS存储而是存储到HBase中,并且关联后Hive中添加数据在HBase中可看到,HBase中添加数据Hive也可看到。
6.3 HBase 跟关系型数据库区别
指标 | 传统关系数据库 | HBase |
---|---|---|
数据类型 | 有丰富的数据类型 | 字符串 |
数据操作 | 丰富操作,复杂联表查询 | 简单CRUD |
存储模式 | 基于行存储 | 基于列存储 |
数据索引 | 复杂的多个索引 | 只有RowKey索引 |
数据维护 | 新覆盖旧 | 多版本 |
可伸缩性 | 难实现横向扩展 | 性能动态伸缩 |