HBase

HBase

HBase简介

  • Hbase是一种分布式、可扩展、支持海量存储的NoSQL数据库。

    基于Hadoop可分布式,基于HDFS可扩展,可存储数十亿行百万列海量数据。

说明:
  1. HBase的数据虽然存储在HDFS上,且HDFS只支持追加写而不支持随机写,但HBase通过技术手段实现随机、实时读写。

    HBase以追加的方式对旧数据进行覆盖,从而实现对文件的修改,保留了时间戳记录不同的时间版本。

  2. RDBMS: 传统关系型数据库。

  3. 非关系型数据库:底层的物理存储结构以KV键值对的形式存储数据。

  4. 表:有行有列,且必须得有元数据用来描述行和列。

  5. 数据仓库:数仓并非仅作为数据的存储,而存储数据最终的目的是为了计算和分析。

HBase 模型

  • 逻辑上HBase的数据模型关系桶关系型数据库类似,数据存储在表中,有行有列。但从HBase的底层物理存储结构来讲,HBase更像一个多维的Map (multi-dimensional map)。

HBase 逻辑结构

  • HBase的表中,第一列为RowKey主键,横向划分了多个Region分区,纵向划分了多个列族,横纵切分的区域称为一个Store。
说明:
  1. RowKey是有序的,以字典序进行排列。

  2. HBase不是以列进行划分的,而是以列族作为列的划分,每个列族有多个列字段,建表时可以不指明列字段,但必须指明列族,列字段可以在列族中随时添加。

  3. Store是HBase中最小的一个存储单位,最终都是以StoreFile的形式存储在HDFS上,StoreFile的文件格式为Hfile。

  4. HBase的表可以是稀疏表,允许某个Cell值为空,即不存在值,但不是Null。

  5. Cell 是一个五位的K-V 。

    Key(RowKey、Column Family、Column Qualifier、TimeStamp、Type)-Value。

HBase 数据模型

  1. Name Space :命名空间,类似于MySql中的数据库,Hbase自带两个命名空间,hbase和deafault(默认),Hbase中存放的是Hbase的内置表,内置表不能通过list显示,只能list_namespace_tables '库名'显示,default是用户默认使用的命名空间。
  2. Table:Hbase定义表时只需要声明列族即可,不需要指明字段,字段可以动态、按需指定。与关系型数据库相比,HBase能够轻松应对字段更变场景。
  3. Row:HBase表中的每行数据都由一个RowKey和多个Column组成,数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索。
  4. Column:HBase中的每个列都由Column Family(列族)和Column Qualifier(列限定符)进行限定。
  5. TimeStamp:版本号,用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase的时间。
  6. Cell :唯一确定的单元,Cell中的数据全部是以字节码形式存储。
说明:HBase官方不建议设置过多的列族,每个列族都会对应一个文件,列族过多会产生多个小文件,并且读取某行数据时会扫描多个文件中的数据,涉及网络IO。

HBase 随机写实现

  • Timestamp是HBase实现实时随机更改数据的关键,HDFS本身不支持随机修改,HBase只能通过追加的方式对数据进行操作,并且给每个操作后的数据都附带了一个timestamp,通过这个时间戳来显示不同版本的数据,默认只显示最新版本号的数据,对数据的删除同样是对文件的追加,给删除字段增加了一个最新版本号的Delete标签,带了这个标签的数据会被隐藏无法查出来,但是可以查之前版本号的,如果DeleteFamily整个字段的值都不能查询。所有这些操作都并没有对原始数据进行修改,全都是通过追加的方式写入文件中,当旧数据非常多的时候,HBase就会将HDFS上的文件下载下来修改过后再上传上去。

HBase 架构

  1. Master:所有RegionServer的管理者,其实现类为HMaster,负责RegionServer中的Region分配,监控RegionServer的状态,负载均衡和故障转移;处理DDL请求对表进行操作,修改表的元数据(create、delete、alter)。

    说明:在那个节点启动HBase就会在那个节点启动Master,不需要设定。

  2. RegionServer:Region的管理者,其实现类为HRegionServer,负责Region的切分与合并;处理DML请求,对数据进行操作(get、put、delete)。

    说明:RegionServer启动后会向Zookeeper注册,Master通过zookeeper监控RegionServer状态。

  3. Zookeeper:HBase通过Zookeeper来做Master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。

  4. HDFS:为HBase提供最终的底层数据存储服务,同时为HBase提供高可用的支持。

  5. Region:多行RowKey对应的所有Store构成Region,即一个Region包含多个Store。

说明:
  1. 每一个Region会被一个RegionServer管理,每一个Region具体分配到哪一个RegionServer不确定。
  2. 某个RegionServer故障后,Master会将其管理的Region分配给其他 RegionServer管理。
  3. Namespace中的meta表中记录表的元数据信息,即表有哪些region组成,每个region由哪个Regionserver服务的,并且记录了Regionserver存储在那个节,mete表由Zookeeper维护,ZK记录了Meta表的位置。
  4. Hbase需要用hadoop来存数据,需要用zookeeper来进行regionserver的协调,所以要先开这两个再开hbase。

RegionServer 架构

  • 每个RegionServe 都包含Block Catch 、WAL、以及多个Region。
  • 每个Region中包含多个Store。
  • 每个Store对应一个列族,包含MemoryStore和StoreFile。
  1. StoreFile:保存实际数据的物理文件(有序的K-V文件),StoreFile以Hfile的形式存储在HDFS上,每个Store会有一个或多个StoreFile(多次溢写),数据在每个StoreFile中都是有序的。

  2. MemStore:写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。

  3. WAL:由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题数据会先写在'Write-Ahead logfile'文件中,然后再写MemStore中,在系统出现故障的时候,数据可以通过这个日志文件重建。

    底层采用hlog存储,用来记录写入memstore中的操作,每个Region共享一个WAL,WAL也会不停的滚动刷写。存满了以后就刷写,产生新的WAL文件用来存储操作数据;虽然WAL数据存储在HDFS中需要落盘,但HDFS采用追加写速度很快。

  4. BlockCache:读缓存,每次查询出的数据会缓存在BlockCache中方便下次查询。

说明:
  1. 每个RegionServer可以服务于多个Region。
  2. 每个RegionServer中有一个WAL以及一个BlockCache和多个Store和,每个Store对应一个列族,包含Memstore和StoreFile。
  3. MemStore每flush写一次就会产生一个StoreFile,只产生新的stroeFile文件与之前的文件无关。

HBase 写流程

  1. Client先访问zookeeper,获取hbase的meta表位于哪个RegionServer。
  2. 访问对应的RegionServer,获取HBase的meta表,根据读请求的Namespace:table/rowkey,查询出目标数据位于哪个RegionServer中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的metaCache,方便下次访问。
  3. 与目标RegionServer进行通讯。
  4. 将数据顺序写入(追加)到WAL。
  5. 将数据写入对应的MemStore,数据会在MemStore进行排序。
  6. 向客户端发送ack。
  7. 等达到MemStore的刷写时机后,将数据刷写到HFile。

HBase 读流程

  1. Client先访问zookeeper,获取hbase的meta表位于哪个RegionServer。

  2. 访问对应的RegionServer,获取HBase的meta表,根据读请求的Namespace:table/rowkey,查询出目标数据位于哪个RegionServer中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的metaCache,方便下次访问。

  3. 与目标RegionServer进行通讯。

  4. 分别在MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本timestamp或者不同的类型(Put/Delete)。

    说明:读取数据的时候是先读MemStore和BlockCache中的数据,无论BlockCache中的数据有没有都会读StoreFile的数据,如果BlockCache中没有,读StoreFile中的全部数据,如果BlockCache中没有,则读StoreFile中的部分数据(除BlockCache中以外的数据)。

  5. 将查询到的新的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache。

  6. 将合并后的最终结果返回给客户端。

说明:
  1. 每个StoreFile是以Bloce(64K)块存储的,读取时会将StroeFile中的块加载到Block Cache中。
  2. 对表中的数据进行读写操作不需要Master也能执行,Maste的主要作用是负责管理数据的负载均衡和表DDL操作,Master 挂了以后向一个表中添加数据会导致该表的region变大,当表的region大到一定程度时RegionServer就会把这个分region成两个region,但该rgion并不会分给别的RegionServer管理,因为负责region负载均衡的Master挂掉了。
  3. 读数据的来源可能时StoreFile、memstore、block cache 。
  4. MemStore不能代表全部数据,一定要读StoreFile的,先通过Hfile文件中的索引定位Block信息,然后没有直接读Block,而是先去看一下Cache中有没有该Block有直接读,没有才真正打开Hfile读block信息。Block也是有范围的。
  5. Hfile自带布隆过滤器,是一个很长的数组,每个元素默认是0,布隆过滤器包含了多个hash算法。
  6. Memstore加快写速度,block Cache加快读流程。
  7. HBase读流程如何快速定位RowKey在哪一个Hfile中时间范围-根据每一条数据的时间戳、Rowkey范围、布隆过滤器

Memory Flush

  • Memory 刷写时机:

    1. 当某个memstore的大小达到了默认值128M,其所在region的所有memstore都会刷写。

      当memstore的大小达到了512M(默认大小*4)时,会阻止继续往该memstore写数据。

    2. 当regionserver中memstore的总大小达到 'heapsize*0.4*0.95' 时Region会按照其所有memstore的大小顺序(由大到小)依次进行刷写,直到regionserver中所有memstore的总大小减小到上述值以下。

      当regionserver中memstore的总大小达到 'heapsize*0.4'时会阻止继续往所有的memstore写数据。

    3. 到达自动刷写的时间也会触发memstoreflush,自动刷新的时间间隔默认1小时。

    4. 多个region共享一个WAL,当WAL文件的数量超过'hbase.regionserver.max.logs'时Region会按照时间顺序依次进行刷写,默认最大32。已经不可配了。

说明:
  • HBase希望每个regionserver管理较少的region,每个region相对较大,避免由于文件总量达到了刷写条件而每个文件都很小,产生过多的小文件。并且太多的region会增加master负载均衡的压力,每个RegionServer维护100-200个Region为合理。

StoreFile Compaction

  • 由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本和不同类型有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction,Compaction分为两种。

    1. Minor Compaction(小合并):会将临近的若干个较小的HFile合并成一个较大的HFile,并清理掉部分过期和删除的数据。
    2. Major Compaction(大合并):会将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉所有过期和删除的数据。
      • 抖动比例:0.5,默认七天大合并,关掉进行手动合并。
说明:
  1. 每次刷写后都会小合并。但要判断,文件大小、文件个数、文件是否正在合并。
  2. 小合并只能删除部分过期数据,只能删除自己确保的数据,没办法保证其文件中是否还有该数据的时候不删除不能确定的数据。
  3. 大合并会删除所有的过期数据,不存在管不着的范围,但不推荐使用,会设计大量的数据io到内存进行合并,会损耗计算机的性能。

Region Split

  • 默认情况下每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的RegionServer。

  • Region Split时机:

    1. 当1个region中的某个Store下所有StoreFile的总大小超过hregion的最大文件大小,该Region就会进行拆分。

    2. 当1个region中的某个Store下所有StoreFile的总大小超过"Min(initialSizeR^3 ,hbase.hregion.max.filesize)",该Region就会进行拆分。

      其中initialSize的默认值为2*hbase.hregion.memstore.flush.size,R为当前RegionServer中属于该Table的Region个数。

    具体切分策略: N^3 * 256 = 256、2048、6192、16384 MB > 10 GB,第四次以及之后都是10G。

HBase 数据删除

  • HBase多用于数据的增改,删除操作较少。
  • 删除数据时机:刷写(内存中的部分已知过期数据)、大合并(所有数据见面)。
  • 表的删除:删除表之前必须将表禁用表disable,再drop 不然删不掉。
  • 数据删除:
    • 创建表时声明的版本数就是删除表以后会保留的版本数,如果不指明,则默认时1,当数据被清除以后会保留最新的哪一个。
    • 可以存在相同时间戳的数据,指定一个时间戳,只是在查询的时候显示不出来了而已,只能显示一个,并且在客户端中删除一个会导致这两个都显示不出来,因为时间戳相同。

Phoenix

  • Phoenix是Hbase的一个皮肤客户端,将SQL转换成Hbase的一些操作执行。

  • Thin客户端通过queryserver将SQL传给RegionServer,port:8765。

  • Thick客户端直接将SQL传给了RegionServer。

  • 在Phoenix中创库创表时,需要开启Hbase映射,使其可以在hbase中创库创表。

  • Phoenix中必须指明primary key 充当hbase中的Rowkey。

  • Phoenix 创建表的时候会在HBase默认创建列族从0开始。

  • Phoenix 连接Zookeeper时默认连接localhost2181,如果本地没有配置Zookeeper会连接不上。

  • Phoenix通过中间层编码的方式减小HBase中数据存储key所占的空间大小,可以通过column_encoded_bytes=0来设置使其不进行编码,但是建议使用编码,可以减少 数据所占内存空间。

  • Phoenix可能会存空列,但是HBase是以kv的形式存储的,不能单独的存储一个k,所以在存储Phoenix中的空列时会随机的赋值一个value。

  • HBase底层存储数据都是字节数组,查询的时候要统一解码,由于采用toStringBinary用来解码,只能解码字符串, 所以数值解的看不了,需要对数值进行转换。 scan ' table_name ' ,{COLUMNS => ['0:SALARY :toInt' ]}。

  • 如果在HBase客户端直接put一个数字则会被认为是一个字符串,必须要告诉HBase这是一个数字,需要Bytes.toBytes(123456) ,并且Hbase默认的数值类型为long ,直接写数字Hbase会认为是String,直接toInt会得到一个0,所以要toLong,但是前面不能有不满足Long的,即负数不行。

  • Phoenix中不区分大小写,且所有小写字母会在HBase中转换成大写,如果不想让其大写,需要用双引号引起来,并且Phoenix中的名字大小写一定要和HBase中的名字大小写一致,在Phoenix中创建的表可以直接在Hbase中进行操作,但是在Hbase中创建的表必须在Phoenix中创建相应的映射。Phoenix中创建的表或schema会自动的在HBase中生产相应的表和namespace,但是在HBase中创建的不会在Phoenix中自动生成。

  • 交叉查询问题:

    1. Phoenix插入数值, Phoenix查没有问题。
    2. Hbase插入数值,Hbase查没有问题。
    3. Phoenix插入数值,Hbase查有问题。
    4. Hbase插入数值,Phoenix查有问题。
  • Phoenix插入数值在Hbase看的时候数值会变成负数,反过来也相同。但自己插自己查都没问题。

    1. 如果数据中没有所谓的负数的数值,建议直接使用 UNSIGNED types。
    2. 如果数据中有所谓的负数的数值,正常使用Phoenix存和查没有问题。

    原因在于两者的字节序列化方式不同,对于 varchar、char、unsigned * tpye, Phoenix采用HBase的字节序列化方式, 而在其他类型如tinyint、smallint、integer、bigint等采用自己的字节序列化方式。

  • Hbase在对rowkey进行字典排序,Rowkey是数值型时,负数的高位是1,整数的高位是0,会导致负数一定会排在整数的后面,Phoenix认为负数1排在整数0前面不好,所以Phoenix对整数和负数的高位进行了颠倒,使其在hbase存储时,整数会在负数的后面。如果不希望Phoenix对数值进行颠倒,则采用unsigned类型,或者不要交叉查询。

  • 与HBase映射

    phoenix建表,hbase也建表

    hbase存在表,phoenix中添加试图

    hbase中存在表,phoenix中创建表

    hbase中有表创建hive的表只能是外部表

二级索引Phoenix

  • 索引的本质:空间换时间,创建索引提高了查询效率,但占用了空间且更新慢。

  • 一级索引:RowKey

  • 二级索引:就是给出了RowKey之外的列创建索引。根据索引创建新表,需要过滤条件过滤时创建二级索引,过滤是需要定位的,而查找只需要将包含的结果输出即可,如果不需要过滤只是查询一下,不用建立该字段的索引,而将字段包含进索引表即可。

  • 实现原理:协处理器,当对原始表进行数据操作时,协处理器会在索引表进行相应的操作。

    说明:Phoenix建二级索引的时候需要做很多的操作,所以需要在HBase中设置相关的设置,否则HBase不允许Phoenix进行相关操作。

  • 二级索引分类:

    1. 全局二级索引Global:将索引数据另外写一张表,更适合读,因为索引表和原表可能维护在不同的regionServer中,写的时候需要写两张表,还不一定在同一个节点,会跨regionserver进行操作,效率低,读数据的时候按照索引查只查索引表就可以不用查询原表。
      • 原理:Phoenix创建的全局二级索引在Hbase中创建一张新表,表的rowKey 就是索引字段+原表的rowkey。
      • 通过rowkey字段进行过滤可以快速定位,而通过非rowkey的字段进行过滤会进行全表扫描,而且找到了也不会停止,可能存在该字段的多个值,一定会扫面全表。
    2. 本地二级索引Local:将索引字段+原表的rowkey 作为新的rowKey插入到原表中。该表还包含了之前的rowkey。
  • Example:

id name sex deptId
11 张三 male 10

创建索引:

Globle:Sex

创建单个字段的全局二级索引:

create index  myindex on table(sex) 

创建含其他字段的全局二级索引:

create index  myindex on table(sex) include(deptId) 

说明:index中的数据在RowKey中,include中的字段在列中。

Local :Sex

create local index myindex on table(sex)

如果是本地索引,索引在原表数据的原Region中,跟数据在一起,按照索引切分出来RowKey直接查询数据即可,最多在Region中进数据扫描。

查询:

select id,sex,deptId from t where sex='male' and deptId='10';

需要建两个字段的索引, sex,deptId。

select id,sex,deptId from t where sex='male'; 

需要建一个索引sex,另外一个查询的字段值用Include(deptId)即可。

RowKey 设计

  • 理论:散列性、唯一性、长度(满足需求的情况下尽可能短)。

  • 预分区:

    1. 相同信息尽量放在一个分区中,方便查询时只需要查询少量的分区。
    2. 希望数据均衡的放到不同的region,一定程度上防止数据倾斜。
    3. 如果数据本身就在一个范围内再对该数据进行分区是不合适的,可能会造成数据分布不均匀。
    4. 不建议分配特别大的内存空间,太大了可能会增加gc负担,特别是fullgc时,会导致所有线程阻塞。
  • 分区设置:

    1. 手动设置与分区

      create 'staff1','info',SPLITS => ['1000','2000','3000','4000']
      
    2. 通过十六进制序列预分区

      create 'staff2','info',{NUMREGIONS => 15, SPLITALGO =>'HexStringSplit'}
      
    3. 通过文件进行预分区

      create 'staff3','info',SPLITS_FILE => 'split.txt'
      
    4. API代码实现创建二位byte数组

  • 需求:电信,根据手机号查询某个人某年[某月某日]的通话详情!

    1. 预分区:确定个数(数据量) -> 分区键[000|,001|,002|...999|] ->1001 个分区

    2. 分区号:000_,001_,002_,...,999_ 说明 |的ASICII码比_大。

      分区号如何给到数据->考虑 设计考虑散列性、查询考虑集中性 在这两个之间找到平衡点。

      将用户每个月的记录放在一个分区,分区的计算:身份信息+日期 进行hash 然后对分区数取余。

      (手机号)%(分区数-1) -> 365

      (手机号+年月)%(分区数-1) -> 12

    3. Rowkey : 按月设计分区, 则 rowKey = 分区号+身份信息+日期 。

      如:000_手机号_年月日时分秒

    4. 查找对象 :13412341234 2020-04

    5. 查找范围:

      startRow:00X_13412341234_2021-04
      stopRow :00X_13412341234_2021-05

      范围扫描 startrow stoprow 左闭右开。

  • 我们公司RowKey 的设计。

    维度数据:用户、其他维度 按照数据量分,数据量小的连预分区都没做。

    预分区:数据量虽然不多,但是考虑到企业之后的发展,按照HBase服务器台数分区了10台。

    分区号:00_,01_,02_,..09_,
    hash(用户id)%(分区数-1)
    RowKey:0X_123456

HBase优化

  1. 预分区

    每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护。依照这个原则可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。

  2. RowKey设计

    数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。

    RowKey 设计:

    • 生成随机数、hash、散列值
    • 字符串反转
    • 字符串拼接
  3. 内存优化

    HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,通常为16-32G,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。

  4. 基础优化

    • 减小Zookeeper会话超时时间,加快RegionServer挂掉后Master响应。
    • 读写请求较多时,增加设置RPC监听数量。
    • 手动控制Major Compaction.
    • 优化HStore文件大小,减小Region中的文件大小最大值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。
    • 优化HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存。
    • 指定scan.next 扫描HBase所获取的行数,值越大,消耗内存越大。
    • BlockCache占用RegionServer堆内存的比例,默认0.4,读请求比较多的情况下,可适当调大。
    • MemStore占用RegionServer堆内存的比例,默认0.4,读请求比较多的情况下,可适当调大。
作者:yuexiuping
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
posted @ 2021-06-14 18:18  yuexiuping  阅读(43)  评论(0编辑  收藏  举报