Hbase读书笔记(四)

1.本文说明

笔者最近一年从事维护Hbase相关工作,总结相关文章用于阐述对于Hbase的部分理解,本文知识点来源于
《HBase原理与实践》第四章

2.前言

对于使用Hbase的业务方来说,从Hbase客户端到Hbase服务端,再到HDFS客户端,最后到HDFS服务端,这是一整条路径,其中任何一个环节出现问题,都会影响业务的可用性并造成延迟。因此,Hbase的业务方需要对Hbase客户端有较好地理解,以便优化服务体验。而事实上,由于Hbase本身功能的复杂性以及Region定位功能设计在客户端上,导致Hbase客户端并不足够轻量级。

2.1 Hbase客户端实现

Hbase提供了面向Java、C/C++、Python等多种语言的客户端,由于Hbase本身是Java开发的,所以非Java语言的客户端需要先访问ThriftServer,然后通过ThriftServer的Java Hbase客户端来请求Hbase集群。
对其他语言的客户端,推荐使用ThriftServer的方式来访问Hbase服务。

  • 基础访问说明
    步骤1:获取集群的Configuration对象
    对访问Hbase集群的客户端来说,一般需要3个配置文件:hbase-site.xml、core-site.xml、hdfs-site.xml。只需要把这3个配置文件放到JVM能加载的classpath下即可。
    步骤2:通过Configuration初始化集群Connection
    Connection是Hbase客户端进行一切操作的基础,它维持了客户端到整个Hbase集群的连接,例如一个Hbase集群中有2个HMaste、5个RegionServer,那么一般来说,这个Connection会维持一个到Active Master的TCP连接和5个到RegionServer的TCP连接。
    通常,一个进程只需要为一个独立的集群建立一个Connection即可,并不需要建立连接池。
    建立多个连接,是为了提高客户端的吞吐量,连接池是为了减少建立和销毁连接的开销,而Hbase的Connection本质上是由连接多个节点的TCP链接组成,客户端的请求分发到各个不同的物理节点,因此吞吐量并不存在问题,另外,客户端主要负责收发请求,而大部分请求的响应耗时都花在服务端,所以使用连接池也不应能带来更高的收益。
    Connection还缓存了访问的Meta信息,这样后续的大部分请求都可以通过缓存的Meta信息定位到对应的RegionServer。
    步骤3:通过Connection初始化Table
    Table是一个非常轻量级的对象,它实现了用户访问表的所有API操作,例如Put、Get、Delete、Scan等,本质上,它所使用的连接资源、配置信息、线程池、Meta缓存等,都来自步骤2创建的Connection对象,因此由一个Connectiobn创建的多个Table,都会共享连接、配置信息、线程池、Meta缓存这些资源。
    步骤4:通过Table执行Put和Scan操作。

2.2 定位Meta表

Hbase一张表的数据是由多个Region构成,而这些Region是分布在整个集群的RegionServer上的,那么客户端在做任何数据操作的时候,都要先确定数据在哪个RegionServer上,然后再根据Region的RegionServer信息,去对应的RegionServer上读取数据。
hbase:meta表的结构非常简单,整个表只有一个名为info的CF,而且Hbase保证hbase:meta表始终只有一个Region,这是为了确保meta表多次操作的原子性,因为Hbase本质上只支持Region级别的事务。


image.png
  • hbase:meta的设计
    hbase:meta的一个rowkey就对应一个Region,rowkey主要由TableName(业务表名)、StartRow(业务表Region区间的起始rowkey)、TimeStamp(Region创建的时间戳)、EncodeName(上面3个字段的MD5Hex值)4个字段拼接而成。


    image.png

    各个字段解释的脑图如下:


    Hbase-Meta.png
  • 如何根据rowkey查找业务的Region,例如:现在需要查找micloud,note表中rowkey=‘userid334452'所在的region,可以设计如下语句:


    image.png
  • 解惑1,为什么需要用一个9999999999999的timestamp,以及为什么要用反向查询Reversed Scan呢?
    答:首先,9999999999999是13位时间戳中最大值,其次因为Hbase在设计hbase:meta表rowkey时,把业务表的StartRow(而不是StopRow)放在hbase:meta表的rowkey上。这样,如果某个Region对应的区间是[bbb,ccc),为了定位rowkey=bc的Region,通过正向Scan只会找到[bbb,ccc)这个区间的下一个区间,但是,即使我们找到了[bbb,ccc)的下一个区间,也没法快速找到[bbb,ccc)这个Region的信息,所以采用ReversedScan是比较合理的方案。
  • 解惑2,Hbase作为一个分布式数据库系统,一个大的集群可能承担数千万的查询写入请求,而hbase:meta表只有一个Region,如果所有的流量都先请求hbase:meta表找到Region,再请求Region所在的RegionServer,那么hbase:meta表将承载巨大的压力,这个Region会马上变为热点,且根本无法承担数千万的流量,如何解决这个问题?


    image.png

    答:把hbase:meta表的Region信息缓存在Hbase客户端,Hbase客户端有个叫做MetaCache的缓存,在调用Hbase API时,客户端会先去MetaCache中找到业务RowKey所在的Region,这个Region可能有一下三种情况:
    1.Region信息为空,说明MetaCache中没有这个RowKey所在Region的任何Cache。此时直接用上述查询语句去hbase:meta表中Reversed Scan即可,注意首次查找时,需要先读取ZK的/hbase/meta-region-server这个ZNode,以便确定hbase:meta表所在的RS。在hbase:meta表中找到业务rowkey所在的Region之后,将(regionStartRow,region)这样的二元组信息存放在一个MetaCache中。
    2.Region信息不为空,但是调用RPC请求对应RS后,发现Region并不在这个RS上;这说明MetaCache过期了,同样直接Reversed Scan Hbase:meta表,找到正确的Region并缓存。通常,某些Region在两个RS之间移动后会发生这种情况,但事实上,无论RS宕机导致的Region移动,还是由于Balance导致Region移动,发生的概率都极小。而且,也只会对Region移动后的极少数请求产生影响,这些请求只需要通过Hbase客户端自动重试locate meta即可成功。
    3.Region信息不为空,且调用RPC请求到对应RS后,发现是正确的RS,绝大部分的请求属于这种情况,也是代价极小的方案。
    由于MetaCache的设计,客户端分摊了几乎所有定位Region的流量压力;避免出现所有流量都打在hbase:meta的情况,这也是Hbase具备良好扩展性的基础。
    4.需要注意,所谓Region级别事务,就是当多个操作落在同一个Region内时,Hbase能保证这一批操作执行的原子性。如果多个操作分散在不同的Region,则无法保证这批操作的原子性。

2.3 Scan的复杂之处

2.3.1 基础流程

Hbase客户端的Scan操作应该是比较复杂的RPC操作,为了满足客户端多样化的数据库查询需求,Scan必须能设置众多维度的属性。常用的有startRow、endRow、Filter、caching、batch、reversed、maxResultSize、version、timeRange等。
table.getScanner(scan)可以拿到一个Scanner,然后只要不断地执行scanner.next()就能拿到一个Result,如图4-3所示:


image.png

用户每次执行scanner.next(),都会尝试去名为cache的队列中拿result(步骤4),如果cache队列为空,则会发起一次RPC向服务端请求当前scanner的后续result数据(步骤1)。客户端收到result列表后(步骤2),通过scanResultCache把这些results内的多个Cell进行重组,最终组成用户需要的result放入到Cache中(步骤3)。其中步骤1+步骤2+步骤3统称为loadCache操作。
为什么要在步骤3对Rpc Responce中的result进行重组呢?这是因为RS为了避免被当前RPC请求耗尽资源,实现了多个维度的资源限制(例如timeout、单次RPC响应最大字节数),一旦某个维度资源到达阈值,就马上把当前拿到的Cell返回给客户端。这样客户端拿到的result可能就不是一行完整的数据,因此在步骤3需要对result进行重组。

2.3.2 几个重要概念

image.png

2.3.3 Hbase客户端避坑指南

2.3.3.1 RPC重试配置要点

在Hbase客户端到服务端的通信过程中,可能会碰到各种各样的异常,例如有如下几种导致重试的常见异常:
1.待访问Region所在的RS宕机,此时Region已经被移到一个新的RS上,但是由于客户端存在meta缓存,首次RPC请求仍然访问到了旧的RS,后续将重试发起RPC
2.服务端负载较大,导致单次RPC响应超时,客户端后续将继续重试,直到RPC成功或者超过客户容忍最大延迟。

2.3.3.2 Hbase常见的几个超时参数

1.hbase.rpc.timeout:表示单次RPC请求的超时时间,一旦单次RPC超过该时间,上层将收到TimeOutException,默认时间为60000ms.
2.hbase.client.retries.number:表示调用API时最多容许发生多少次RPC重试,默认为35次。
3.hbase.client.pause:表示连续两次RPC重试之间的休眠时间,默认为100ms。重试策略如下:
第1次 RPC重试 100ms
第2次 RPC重试 200ms
第3次 RPC重试 300ms
第4次 RPC重试 500ms
第5次 RPC重试 1000ms
第6次 RPC重试 2000ms
按照默认配置将会卡在休眠和重试两个步骤。
4.hbase.client.operation.timeout:表示单次API的超时时间,默认值为1200000ms,注意,get/put/delete等表操作称为一次API操作,一次API可能会有多次RPC重试,这个operation.timeout限制的是API操作的总超时。
案例:
假设某业务要求单次Hbase的读请求延迟不超过1s,那么该如何设置上述4个超时参数呢?
首先,hbase.client.operation.timeout应该设为1s,其次,在SSD集群上,如果集群参数设置合适且集群服务正常,则基本可以保证p99延迟在100ms以内,因此hbase.rpc.timeout设成100ms,这里,hbase.client.pause用默认的100ms,hbase.client.retries.number可以稍微设大一点,比如6次。

2.3.3.3 CAS接口是Region级别串行执行的,吞吐受限

image.png

这些接口在高并发场景下,能很好地保证读取与写入操作的原子性。例如:有多个分布式的客户端同时更新一个计数器Count,可以通过increment接口来保证任意时刻都只有一个客户端能成功原子地执行count++操作。
需要特别注意的是,这些CAS接口在RS上是Region级别串行执行的,也就是说,同一个Region内部的多个CAS操作是严格串行执行的,不同Region间的多个CAS操作可以并行执行。
以checkAndPut为例,简要说明一下CAS的操作步骤:
1.服务端拿到Region的行锁(row-lock),避免出现两个线程同时修改一行数据,从而破坏了行级别原子性的情况。
2.等待该Region内的所有写入事务都已经成功提交并在mvcc上可见。
3.通过Get操作拿到需要check的行数据,进行条件检查,若条件不符合,则终止CAS。
4.将checkAndPut的put数据持久化。
5.释放1)步拿到的行锁。
关键在于第2)步,必须要等所有正在写入的事务成功提交并在mvcc上可见。
因此那些依赖CAS的接口服务,需要意识到这个操作的吞吐是受限的,因为CAS操作本质上是Region级别串行执行的。当然,在Hbase2.x版已经调整设计,对同一个Region内的不同行可以并行执行CAS,这大大提高了Region内的CAS吞吐。

2.3.3.4 Scan Filter设置

Hbase作为一个数据库系统,提供了多样化的查询过滤手段,最常用的就是Filter,例如一个表有很多个列簇,用户想找到那些列簇不为C的数据。可以设计一个如下的Scan:


image.png

如果想查询列簇不为C且Qualifier在[a,z]区间的数据,可以设计一个如下的Scan:


image.png

上面代码使用了一个带AND的FilterList来连接FamilyFilter和ColumnRangeFilter,有了Filter,大量无效数据可以在服务端内部过滤,相比直接返回全局数据到客户端,然后在客户端过滤,要高效很多。
案例剖析

prefixFilter
1.PrefixFilter是将RowKey前缀为指定字节串的数据都过滤出来并返回给用户。
例如:如下Scan会返回所有RowKey前缀为'def'的数据:


image.png

注意,这个Scan虽然能得到预期的效果,但并不高效。因为对于rowkey在区间(-♾,def)的数据,Scan会一条条扫描,发现前缀不为def,就读下一行,直到找到第一个Rowkey前缀为def的行为止。
解决方法1:
在Scan中简单加一个startRow即可,RegionServer发现Scan设了startRow,首先寻址定位到这个startRow,然后从这个位置开始扫描数据,这样就跳过了大量的(-♾,def)的数据。代码如下所示:
image.png

解决方法2:
将PrefixFilter直接展开,扫描[def,deg)区间的数据,这样效率是最高的
image.png

pageFilter


image.png

Hbase里的Filter状态全部都是Region内有效的,也就是说,Scan一旦从一个Region切换到另一个Region,之前那个Filter的内部状态就无效了,新Region内用的其实是一个全新的Filter。具体到这个问题,就是PageFilter内部计数器从一个Region切换到另一个Region,计数器已经被清0.
image.png

当然,如果想实现分页功能,可以不通过Filter而直接通过limit实现,代码如下:
image.png

所以,对用户来说,正常情况下PageFilter并没有太多存在的价值。
SingleColumnPageFilter
这个Filter的定义比较复杂,让人有点难以理解,但是事实上,这个Filter却非常有用,下面举例说明:
image.png

这个例子表面上是将列簇为family、列为qualifier、且值为value的Cell返回给用户,但是事实上,对那些不包含family:qualifier列的行,也会默认返回给用户。
如果用户不希望读取那些不包含family:qualifier的数据,需要设计如下Scan:
image.png

不要使用SingleColumnValueFilter和其他Filter组合成FilterList,直接指定列,通过ValueFilter替换掉SingleColumnValueFilter,代码如下:
image.png

2.3.3.4 少量写和批量写

Hbase是一种对写入操作非常友好的系统,但是当业务有大批量的数据要写入Hbase时,仍会碰到写入瓶颈,为了适应不同数据量的写入场景,Hbase提供了3种常见的数据写入API,如下:


Hbase少量写和批量写.png

2.3.3.5 业务发现请求延迟很高,但是Hbase服务端延迟正常

某些业务发现Hbase客户端上报的P99和P999延迟非常高,但是观察HBase服务端的P99和P999延迟正常,这种情况下一般需要观察Hbase客户端的监控和日志。按照我们的经验,一般来说,有这样一些常见问题:

  • Hbase客户端所在进程JAVA GC,由于Hbase客户端作为业务代码的一个Java依赖,因此一旦业务进程发生较为严重的FULL GC,必然会导致Hbase客户端监控到的请求延迟很高,这时需要排查GC原因。
  • 业务进程所在机器的CPU或者网络负载较高,对于上层业务来说一般不涉及磁盘资源开销,所以主要看load和网络是否过载。
  • Hbase客户端层面的BUG,这种情况出现概率不大,但也不排除有这种可能。

2.3.3.6 Batch数据量太大,可能导致MutilActionResultTooLarge异常

Hbase的batch接口使得用户可以把一批操作通过一次RPC发送到服务端,以便提升系统的吞吐量,这些操作可以是PUT、DELETE、GET、INCREMENT、APPEND等等。像Get或者INCREMENT的BATCH操作中,需要先把对应的Block从HDFS中读取到HBASE内存中,然后通过RPC返回相关数据给客户端。
如果Batch中的操作过多,则可能导致一次RPC读取的Block数据量很多,容易造成Hbase的RegionServer出现OOM,或者出现长时间的FULL GC。因此,HBase的RegionServer会限制每次请求的Block总字节数,一旦超过则会报MultiActionResultTooLarge异常,此时客户端最好控制每次Batch操作的个数,以免服务端为单次RPC消耗太多内存。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1、通过Configuration初始化集群Connection 1.1、Connction维持了客户端到整个HB...
    loukey_j阅读 2,539评论 0 1
  • 参考自《HBASE总结与实践》xmind转markdown存在图片丢失,源文件下载地址:github hbase ...
  • 一、简介 Hbase:全名Hadoop DataBase,是一种开源的,可伸缩的,严格一致性(并非最终一致性)的分...
    菜鸟小玄阅读 2,425评论 0 12
  • Hbase是一种NoSQL数据库,这意味着它不像传统的RDBMS数据库那样支持SQL作为查询语言。Hbase是一种...
    Secret_Sun阅读 1,036评论 0 0
  • 一:hbase现有硬件资源的理论性能 1.集群容量规划公式: 优化调整,发挥硬件的最大优势; 按照默认配置, Re...
    sunTengSt阅读 1,612评论 0 1