介绍下HBase

HBase是一种主从分布式集群架构的列式数据库,支持海量数据的存储。

HBase优缺点

优点:高效查询,列式存储,可以水平拓展,访问速度快,海量数据存储 缺点:单行事务,不支持join,吞吐量小

介绍下HBase架构

image.png

HBase读写数据流程

读流程:

  1. 首先客户端访问zookeeper,通过zookeeper的root表找到管理元数据表的regionSever地址,zookeeper将regionServer的地址返回给客户端
  2. 客户端通过这些regionServer的地址,找到对应的regionServer,在这些regionServer中找到meta表,从meta表中查找需要获取的数据对应的regionServer地址
  3. 客户端通过元数据信息表得到的regionServer地址去访问对应的regionServer,然后在这些regionServer中访问对应的region,访问对应region中的memStore,如果memeStore没有数据,将去sotreFile里面找,storeFile里面没有,最后去HFile里面找,最终返回结果

写流程:

  1. 同样的,首先客户端访问zookeeper,通过zookeeper中的root表,获取存储元数据信息的regionServer地址(这里只返回一个)
  2. 通过zookeeper返回的regionServer地址,去访问regionServer,在该regionServer中找到对应的region,先写入HLog(WAL,预写日志),然后写入memStore,当HLog和memStore写入成功后,返回客户端写入成功。
  3. 后续写入磁盘还并未成功,这是异步操作。memStore最终会flush到storeFile中,这个时候就写入到了HDFS文件系统中了,storeFile最终会compact到HFile中,HFile最终可能会split一分为二。

HBase的读写缓存

HBase上RegionServer的cache主要分为两个部分,分别是memstore&blockcache,其中memstore主要用于写缓存,而blockcache用于读缓存。   当数据写入HBase时,会先写入memstore,RegionServer会给每个region提供一个memstore,memstore中的数据达到系统设置的阈值后,会触发flush将memstore中的数据刷写到磁盘。   客户的读请求会先到memstore中查数据,若查不到就到blockcache中查,再查不到就会从磁盘上读,并把读入的数据同时放入blockcahce。由于BlockCache采用的是LRU策略,因此BlockCache达到上限heapsize hfile.block.cache.size 0.85后,会启动淘汰机制,淘汰掉最老的一批数据。

在删除HBase中的一个数据的时候,它什么时候真正的进行删除呢?当你进行删除操作,它是立马就把数据删除掉了吗?

删除数据的时候并不会立马删除,而是给该数据打了一个标签,表示这个数据删除,它会在storeFile进行合并HFile的时候彻底删除

HBase中的二级索引

HBase本身只有rowkey这一个主键索引,要使用二级索引需要使用其他方案 基于Coprocessor方案 大体的思路:构建一份“索引”的映射关系,存储在另一张hbase表或者其他DB里面 Phoenix二级索引方案 非Coprocessor方案 选择不基于Coprocessor开发,自行在外部构建和维护索引关系也是另外一种方式。常见的是采用底层基于Apache Lucene的Elasticsearch(下面简称ES)或Apache Solr ,来构建强大的索引能力、搜索能力, 例如支持模糊查询、全文检索、组合查询、排序等。 ES二级索引方案

HBase的RegionServer宕机以后怎么恢复的?

重启regionServer,它会自动的去和zookeeper建立连接。

HBase的一个region由哪些东西组成?

store模块(一张表就是一个region,一个列族就是一个store模块,所以尽量不要太多的列族,不然每个store模块里面的memStore都要占用128MB内存) memStore:每一个store模块中都有一个memStore,它默认是128MB,当这里面的数据达到128MB或者隔了1个小时后,就会将里面的数据flush到storeFile中 storeFile:HBase中存储数据的文件,接收memStore刷写的数据,当storeFile达到3个及以上时,会进行合并(compact)操作,合并一个大的HFile

HBase高可用怎么实现的?

通过备用的HMaster,实现高可用 在hbase的conf目录下,建立一个backup-masters文件,然后将需要作为备用HMaster节点的ip地址写入文件,并且分发到其他节点上,然后重启HBase即可

为什么HBase适合写多读少业务?

HBase更适合写多读少的场景。在写入这一块,每个table在合理预分区的情况,可以将负载均匀分配到不同的region server,而且可以LSM结构可以有效避免随机io,即使开启wal也是顺序写入,效率、吞吐量还是不错的。但这种结构也有一个问题,就是写放大,也就是常说的compation,这个问题要分析负载分布来调参,有的公司甚至还做了一些扩展以减小写放大的影响。 而读取默认hbase是没有二级索引的,唯一的索引就是rowkey,所以很多公司针对rowkey设计做文章,还有公司会扩展其他高效率查询的引擎作为二级索引,例如es,再或者通过phoneix。如果没有二级索引的支持,hbase本身对复杂一点的检索效率是很有问题的。 所以,光说hbase,写多读少可能更准确一些

列式数据库的适用场景和优势?列式存储的特点?

优势: 1)列式存储由于每一列单独存放,所以数据即索引,课使得在访问数据上只访问关心的字段部分,减少访问的吞吐量和系统的IO 2)列式存储同样由于列式单独存放,所以可以支持高并发处理 3)列式存储一列的数据类型一致,数据特征相似,所以可以利用磁盘压缩算法进行高效压缩 应用场景: 列式存储适合数据分析类OLAP场景,比如进行客户流量预测,需要对数据进行反复的遍历,由于列式存储可以比行式存储有着更好的压缩比,所以存储量更小,读取速度更快。

HBase的rowkey设计原则

官方推荐设计方案: 1)避免rowkey的数据使用固定的前缀:比如以手机号作为前缀,或者时间戳作为前缀 2)rowkey在设计的时候,尽量短一些,不建议过长,同时列族,列名在设计时也应该短一些 默认:hbase支持rowkey最长64kb,但是一般建议在0~100字节范围内,通常在10~30左右 3)使用数值类型的字节作为rowkey要比使用string类型的字节更加节省空间 4)保证rowkey的唯一性 业务规定: 1)rowkey的设计需要满足一些固定的查询需求 2)保证相关性的数据,放置在一个region当中

HBase的rowkey为什么不能超过一定的长度?为什么要唯一?rowkey太长会影响Hfile的存储是吧?

因为HBase数据的读写首先会在memStore内存中进行,并且进行写入操作的时候,会将这一条数据的所有内容都放在这里,而默认memStore是128MB,如果rowKey过长,那么memStore存储的数据就将减少,然后就会过早的进行flush,导致读写效率下降

HBase的名称空间

HBase的名称空间,类似于hive中数据库或者mysql中数据库,只不过叫法不同而已 1)方便管理维护工作 2)更好进行业务划分 3)方便权限化管理 建议一个项目或者一个业务模块构建一个名称空间 默认情况下,hbase提供了两个名称空间:

  • default:默认名称空间,在创建hbase表的时候,如果没有指定名称空间,默认将表构建在default空间下
  • hbase:hbase的系统名称空间,主要用于存储hbase的管理表,比如meta表就是存储在hbase名称空间下,此空间一般不使用

如何使用名称空间

1)如何查看当前有哪些名称空间? list_namespace describe_namespace ‘名称空间名称’ 2)如何创建一个名称空间? create_namespace ‘名称空间名字’ 3)如何向某一个名称空间下创建表? create ‘名称空间:表名’ ,‘列族’…… 4)如何删除名称空间 drop_namespace ‘名称空间名字’ 注意 1)一旦将表创建在某一个名称空间下,在以后使用到这个表,必须带上名称空间,只有default空间是可以省略的 2)在删除一个名称空间的时候,一定要保证当前这个空间下没有任何的表

列族的设计

一句话:列族建议越少越好,能用一个来解决,坚决不使用多个

什么情况下需要构建多个列族呢?一般建议2~5个左右

情况一:当表中列非常多的时候,但在查询使用时,仅仅使用其中某几个,此时可以将常用字段放置在一个列族中,其他不经常使用放置在另一个列族中 情况二:一个表中的数据,可能需要对接多个不同业务,但是不同业务使用这个表的字段也是不同的,可以针对不同业务使用字段,将其放置到不同的列族中

HBase的表的压缩方案的选择

image.png

  • 当数据量比较大的时候,写入请求远远大于读取请求,建议优先保障最大压缩比,建议使用GZ(GZIP)
  • 当数据量比较大的时候,读取请求远远大于写入请求,或者读写都比较高的时候,建议使用LZO/SNAPPY

create ‘表名’,{NAME=>’列族’,COMPRESSION=>’压缩算法’} alter ‘表名’,{NAME=>’列族’,COMPRESSION=>’压缩算法’} 注意:设置压缩后,压缩只能在将数据落在hdfs上才能生效,在memStore内存中无法压缩

HBase的大合并、小合并是什么?

minor(小合并):先将那些较小的storeFile合并为一个较大的HFile过程 阈值:3个及以上 在此阶段,类似于内存合并中基础型方案,仅仅是将多个storeFile合并为一个较大HFile,对过期版本的数据,不做任何的处理,仅仅在合并的时候对数据进行排序即可,采用边读边追加到HDFS上的方式 合并效率比较高 major(大合并):将较大的HFile和之前的大的HFile进行再次合并操作,合并为一个更大的HFile 阈值:7天 在出发major合并后,在合并过程中,类似于饥渴型方案,将过期数据全部清理掉,整个合并过程,也采用边读边追加写入到HDFS的方式 此操作在合并的时候,会影响当前regionServer的读写操作,而且此操作对IO影响比较大 一般此操作采用手动触发(一般为刚刚启动hbase集群的时候)

HBase和关系型数据库(传统数据库)的区别(优点)?

HBase 关系型数据库
数据类型 简单的字符串类型 丰富的数据类型
数据操作 CRUD,表和表之间没有分离 支持join等其他操作
存储模式 基于列 基于行
数据维护 更新操作其实是插入了新数据 更新操作就是替换
可伸缩性 水平拓展 需要借助MyCat等

HBase数据结构

rowKey:类似关系型数据库的主键,通常HBase查询通过rowKey,rowKey范围查询,全表扫描,通常在hbase中通过rowKey字典序排列 列族:行中的数据通过列族来组织,列族也暗示了数据的物理排列。所以列族必须预先定义,并且不容易被修改。每行都拥有相同的列族,可能有些行的数据为空。列族是字符串和字符的组合,可以在文件系统路径中使用。 列限定符:数据在列族中的位置是通过列标识来指定的。列标识不需要预先指定,每行的列标识也不需要相同。就像行键一样,列标识没有数据类型,通常也是字节数组。 时间戳:单元数据是有版本的。版本的区分就是他们的版本号,版本号默认就是时间戳。当写入数据时,如果没有指定时间,那么默认的时间就是系统的当前时间。读取数据的时候,如果没有指定时间,那么返回的就是最新的数据。保留版本的数量根据每个列族的配置。默认的版本数量是3。

HBase为什么随机查询很快?

1、HBase数据写入一般先放在memStore中,而对HBase的读操作会首先访问memStore,memStore基于内存,访问会快一些 2、HBase默认会有rowKey,这是一个主键,相当于索引,通过rowKey查找定位是十分快速的 3、HBase在表很大的时候,HFile会进行分裂,然后划分成多个HFile,并且region也一分为二,不同region可能分布在不同的regionServer上,这样在高并发的读取环境下,依然能够快速响应 4、如果hbase2.x版本以上,那么可以开启内存合并操作,此时memStore中的数据达到阈值时,会写入pipline管道中,这个也是基于内存的只读管道,也可以访问这里。 5、hbase对数据的读取是基于block的,查询某个rowKey时,布隆过滤器会告诉我们这个rowKey可能在这个block,或者一定不在这个block。当一定不在时就可以跳过block的读取,节约了时间和IO,当可能存在时再读取到内存执行查找操作

HBase的LSM结构

LSM树(Log-Structured Merge Tree),LSM树原理把一棵大树拆分成N棵小树,它首先写入内存中,随着小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。

HBase的Get和Scan的区别和联系?

按指定rowKey获取唯一一条记录,get方法: Get 的方法处理分两种:设置了 ClosestRowBefore 和没有设置的 rowlock,主要是用来保证行的事务性,即每一个 get 是以一个 row 来标记的,一个 row 中能够有不少 family 和 column。 按指定条件获取一批记录,scan方法:

  • scan 能够经过 setCaching 与 setBatch 方法提升速度(以空间换时间)。
  • scan 能够经过 setStartRow 与 setEndRow 来限定范围([start, end]start 是闭区间, end 是开区间)。范围越小,性能越高。
  • scan 能够经过 setFilter 方法添加过滤器,这也是分页、多条件查询的基础。

HBase数据的存储结构(底层存储结构)

hbase的数据是按rowKey字典序有序排列的,需要对新添加的数据进行排序,新添加的数据在内存中会使用跳表(skip list)构建memStore,最终持久化为HFile存到磁盘。为什么使用skip list,一方面因为高效,插入操作的时间复杂度为logn,另一方面在写磁盘的时候,从头至尾遍历一遍链表顺序写入磁盘即可。

HBase数据compact流程?

compact合并压缩机制: 指的是memStore不断的进行刷新,在HDFS上的storeFile会越来越多,当storeFile达到阈值后,就会触发compact合并机制,将多个storeFile最终合并为一个大的HFile的过程 minor(小合并):先将那些较小的storeFile合并为一个较大的HFile过程 阈值:3个及以上 在此阶段,类似于内存合并中基础型方案,仅仅是将多个storeFile合并为一个较大HFile,对过期版本的数据,不做任何的处理,仅仅在合并的时候对数据进行排序即可,采用边读边追加到HDFS上的方式 合并效率比较高 major(大合并):将较大的HFile和之前的大的HFile进行再次合并操作,合并为一个更大的HFile 阈值:7天 在出发major合并后,在合并过程中,类似于饥渴型方案,将过期数据全部清理掉,整个合并过程,也采用边读边追加写入到HDFS的方式 此操作在合并的时候,会影响当前regionServer的读写操作,而且此操作对IO影响比较大 一般此操作采用手动触发(一般为刚刚启动hbase集群的时候)

HBase - 图3

HBase的预分区

默认情况下一个表只有一个region,一个region只能被一个regionServer所管理,当这个表出现大量的并发读写操作的时候,会导致所有的请求全部打到一个regionServer上,从而导致这个regionServer的读写效率下降,甚至可能出现宕机风险 image.png 解决方案: 1)手动预分区方案: 格式: create ‘表名’,’列族’,SPLITS=>[‘’,’’] 在[]中设置范围即可 例如:create ‘test’,’c1’,SPLITS=>[‘10’,’20’,’30’,’a’,’z’],有6个分区,从””到10,从10到20…,从z到”” 2)hash预分区方案: 格式: create ‘表名’,’列族’,{NUMREGIONS=>n,SPLITALGO=>’HexStringSplit’} 情况一:当表接下来插入的数据rowkey非常熟悉的情况下,明确知道每个范围有多少条数据,此时建议使用手动预分区 情况二:当表中数据是未知的,无法确定未来数据的rowkey的范围信息,此时建议使用hash方案,在后续插入数据的时候,将rowkey的前缀设计为hash的前缀即可 思考:请问使用region预分区,是否可以解决并发分担到不同regionServer上的问题呢? 并不能完全避免,因为当插入数据都是某一个范围下的数据,会导致所有请求依然打向到同一个region中,即使有多个region,也没用,问题依然存在,如何解决呢?需要对rowkey进行设计,保证生产出来的rowkey能够均匀的落在不同的region中

HBase的热点问题

数据热点:大部分数据集中性落在某一个region中,此时认为出现了数据热点 解决方案:让rowkey前缀在不断的变化中 方案一:反转策略 手机号反转或者时间戳反转 好处:解决热点问题 弊端:导致相关性数据被放置到了不同region中 方案二:hash方案(此种方案较多)配合hash的预分区 好处:保证相关性数据被放置在一起,同时大概率解决热点 弊端:如果某一个相关性数据比其他的都多得多,依然出现热点问题 方案三:加盐策略 直白加随机数 好处:解决热点问题 弊端:导致相关性数据被放置到了不同region中

HBase的memstore冲刷条件

flush刷新机制: 指的是客户端不断的向memStore中写入数据,当memStore达到阈值时,就会触发flush机制,将数据从内存刷新到HDFS上,行成一个storeFile的文件 阈值:128M / 1h 注意: 128M:出发region级别刷新操作 1h:出发regionServer级别刷新操作 内部执行刷新的时候,整个刷新流程:

  1. 当memStore中内存数据达到阈值时,首先会将当前这个内存空间关闭,重新开启一个新的内存空间,继续写入
  2. 将这个达到阈值的内存空间数据(一份数据称为segment片段数据)会放置到一个pipline的内存管道中,这个管道是一个只读管道,等待刷新到HDFS上
  3. hbase2.x版本中
    1. hbase会尽可能让这个管道内的数据晚的刷新到HDFS上,当内存不足时(达到0.85~0.9),此时就会触发flush刷新机制,将管道内的数据进行合并刷新操作(内存合并),在HDFS上形成一个storeFile文件
    2. hbase2.x以下版本中,一直有一个flush的线程在监听这个pipline管道,一旦发现这个管道内有了数据,直接将其刷新到HDFS上,形成一个storeFile文件,每个segment片段就是一个storeFile文件

但是:虽然说hbase2.x以上的版本支持了内存合并操作,实际上并没有开启,所以在默认情况下,与1.x版本的执行逻辑是一致的

HBase的MVCC

  • 获取行锁
    • 获取写序号
  • 写WAL文件
  • 更新memStore:将每个cell写入到memStore
    • 以写序号完成操作
  • 释放行锁

HBase的大合并与小合并,大合并是如何做的?为什么要大合并

minor(小合并):先将那些较小的storeFile合并为一个较大的HFile过程 阈值:3个及以上 在此阶段,类似于内存合并中基础型方案,仅仅是将多个storeFile合并为一个较大HFile,对过期版本的数据,不做任何的处理,仅仅在合并的时候对数据进行排序即可,采用边读边追加到HDFS上的方式 合并效率比较高 major(大合并):将较大的HFile和之前的大的HFile进行再次合并操作,合并为一个更大的HFile 阈值:7天 在出发major合并后,在合并过程中,类似于饥渴型方案,将过期数据全部清理掉,整个合并过程,也采用边读边追加写入到HDFS的方式 此操作在合并的时候,会影响当前regionServer的读写操作,而且此操作对IO影响比较大 一般此操作采用手动触发(一般为刚刚启动hbase集群的时候) 合并操作本质就是以IO操作换取后续的读性能的提高

既然HBase底层数据是存储在HDFS上,为什么不直接使用HDFS,而还要用HBase

HDFS吞吐量比较大,但是查询效率低,而HBase虽然吞吐量比较少,但是查询效率是很高的

HBase和Phoenix的区别

1)Phoenix中建立的表,在HBase中可查,但是在HBase中建立的表,在Phoenix中不可查 2)Phoenix支持join,HBase不可以

HBase支持SQL操作吗

HBase是不支持SQL查询的,不过可以集成Hive或者集成Phoenix来实现SQL化

HBase适合读多写少还是写多读少

HBase更适合写多读少

Region分配

分配region流程:

  1. 当master启动后,首先从zookeeper上获取当前有哪些节点启动了
  2. 连接这些从节点,让其报告当前管理了哪些region,master进行登记记录即可
  3. master再连接meta表,从meta表中获取一共有哪些region,与从节点汇报上来的region进行比对,找到没有被分配的region
  4. 将没有被分配的region分配给那些管理相对较少的regionServer上,同时还需要保证一个表上的多个region被均匀分配给不同的regionServer上,保证负载均衡

HBase的Region切分

split分裂机制: 指的是当我们不断的进行合并过程中,大的HFile会越来越大,当HFile达到一定的阈值后,就会出发split的分裂机制,将这个HFile进行一分为二点操作,形成两个新的HFile文件,同时对应的region也会一分为二,形成两个新的region,让每个region去管理其中一个新的HFile即可,一旦分裂结束后,原有的老的region就会下线,对应老的HFile也会被删除 注意:新分裂出来的两个region,在最开始的时候,是被同一个regionServer管理,后期是否会将其分给其他的regionServer,交由给HMaster进行负载管理 阈值:最终10GB

HBase的内存合并

内存合并是在hbase2.x版本中,memStore数据flush过程的一个操作,它有三种策略:

  1. basic(基础型):
    • basic compaction策略不清理多余的数据版本,无需对cell的内存进行考核,合并效率高
    • basic适用于所有大量写模式
  2. eager(饥渴型):
    • eager compaction会过滤重复的数据,清理多余的版本,这会带来额外的开销,合并效率低
    • eager模式主要针对数据大量过期淘汰的场景,例如:购物车、消息队列等
  3. adaptive(适应型):
    • adaptive compaction根据数据的重复情况来决定是否使用eager策略
    • 该策略会找出cell个数最多的一个,然后计算一个比例,如果比例超出阈值,则使用eager策略,否则使用basic策略

HBase的split机制(region分裂)

split分裂机制: 指的是当我们不断的进行合并过程中,大的HFile会越来越大,当HFile达到一定的阈值后,就会出发split的分裂机制,将这个HFile进行一分为二点操作,形成两个新的HFile文件,同时对应的region也会一分为二,形成两个新的region,让每个region去管理其中一个新的HFile即可,一旦分裂结束后,原有的老的region就会下线,对应老的HFile也会被删除 注意:新分裂出来的两个region,在最开始的时候,是被同一个regionServer管理,后期是否会将其分给其他的regionServer,交由给HMaster进行负载管理 阈值:最终10GB

HBase的split机制有什么作用呢

一旦分裂后,一个表可以有多个region,而多个region可以被多个regionServer所管理,相当于在触发读写操作,能够让更多的regionServer参与起来,共同完成数据的读写操作,提升读写并发量,提升读写效率 但是,如果region达到10GB才能进行分裂,那么从0~10GB这个过程,只能有一个region来工作,在这个过程中,如果出现了大量的并发请求,依然会导致对应regionServer出现宕机风险 希望这个表可以尽快的分裂为多个region,让更多的regionServer参与进来: 解决方案:

  1. - 在构建表的时候,可以让表一开始就拥有多个regionregion的预分区(手动分裂)
  2. - 让表尽可能的早点进行分裂:自动分裂

在hbase中,为了能让其尽早分裂,专门提供了一个计算工作,让程序自动计算何时进行触发分裂机制: 计算公式为: min(R^2 * “hbase.hregion.memstore.flush.size”,”hbase.hregion.max.filesize”) 说明: R:表的region个数 hbase.hregion.memstore.flush.size:默认值为128MB hbase.hregion.max.filesize:10GB 每次切分,都是将这个region中数据找到rowKey的中间值,进行一分为二操作 切分后的region还可以进行读写操作

HBase从节点的上线和下线

HBase - 图5

HBase主节点的上线和下线

由于master并不参与数据的IO读写操作,即使master宕机,也不会影响读写操作,短暂让master下线并不会影响hbase的集群 如果master下线,主要会影响对元数据的操作功能:创建表、修改表、删除表、负载处理、region分配……

HBase - 图6

HBase的版本确界和TTL

hbase的数据版本(version)出现主要是为了解决什么问题? 历史版本数据是否需要存储的问题 版本默认为1,表示只保留最新的版本数据即可,版本设置越大,表示需要记录的历史变化越多 上界(最大值):最多能够保留多少个有效的历史版本数据,默认为1 下界(最小值):至少需要保留多少个历史版本数据,即使数据已经过期了,默认为0

TTL(Time to Live):存活时间 在hbase中,可以对数据设置过期时间,当到达过期时间后,数据会被自动过期掉,相当于删除掉 默认过期时间为永久有效

HBase的协处理器

思考:求出表中年龄的最大值,表是hbase的表 处理方案: 1)通过scan扫描全表数据 2)遍历全表数据,一个个进行比对操作,找到最大值 目前这种处理方式效率比较低,同时对客户端的压力比较大 如何解决: 是否可以将这种求最大值的代码从客户端迁移到服务端,让每个regionServer求出的每个region内部的最大值,将这个最大值的结果返回客户端,客户端基于这个结果再次求出最终的最大值即可(分而治之) 在hbase中为了能实现类似这中操作,专门提供协处理器 observer:类似数据库中触发器,或者理解为监听器,可以通过observer对hbase中各种操作进行监听,一旦发现触发了某种事件,执行相应逻辑代码 作用:

  - 操作日志的记录
  - 权限的控制
  - 等等

endpoint:类似于数据库中存储过程,或者可以理解为将一段代码封装为一个功能方法,将这个方法,放置到服务端,让各个regionServer执行操作,将执行的结果返回给客户端,客户端进行进一步的处理操作 作用:

  - 聚集计算:求最大值,求最小值,求和等等