对于使用Hbase,通常情况下是Hbase客户端到Hbase服务端再到HDFS客户端这么一个路径,所以使用Hbase时需要对Hbase客户端有较好的理解。实际上,由于Hbase的复杂性以及Region的定位设计在客户端上(hbase:meta元数据表缓存在客户端上用于定位region),导致Hbase客户端并不足够轻量级。
Hbase客户端实现:
Hbase提供了多种语言的客户端,由于Hbase是通过Java语言实现的,所以非Java语言需要先访问ThriftServer,然后通过JavaHbase客户端请求Hbase集群。另外Shel(hbase-shell)l客户端实质是用JRuby(java编写的Ruby解析器)调用官方客户端。
访问客户端主要有以下介个步骤:
1、获取集群的Configuration对象(HDFS,Kafka这些也都有COnfiguration对象)。一般需要3个配置文件:hbase-site.xml,core-site.xml,hdfs-site.xml.在maven项目中,直接放在resource文件夹下面即可。
2、通过Configuration初始化Connection。Connection是Hbase客户端进行一切操作的基础,维持了客户端到整个集群的连接。如果一个集群中存在2个Master和5个RegionServer,那么一般来说,这个Connection会维持一个到ActiveMaster的TCP连接和5个到RegionServer的Tcp连接。从这个情况可以看出,一个请求会被分布在5个Regionserver的其中一个之上,所以Hbase连接没有必要使用连接池对连接进行管理。另外Connection还缓存了Meta信息(hbase:meta),可以更方便的找到数据。
3、通过Connection初始化Table,一个轻量级对象,实现了所有需要的API操作(增删改查)。本质上同一个Connection创建的Table都是会共享Connection的资源,包括连接,配置信息,线程池,Meta缓存。
4、通过Table进行操作。
定位Meta表:
Hbase客户端又一个很重要的过程,定位Meta表。由于Hbase一张表kennel由多个Region构成,而这些Region又可能分布在不同的RegionServer上,在通过客户端查询时,都需要先找到数据在那个Region,再到对应的服务器上找数据。所以Hbase内部维护了一张特殊的表:hbase:meta。这个表专门存放集群的所有Region信息。hbase:meta中的hbase指的是命名空间,可以根据不同的业务设置不同的名称,meta指的是hbase表名。
hbase:meta只有一个名为Info的列簇,而且只有一个Region,这样可以保证meta表多次操作的原子性,因为Hbase本质只支持Region级别的事物。表内主要存储以下信息:
rowkey | 一个rowkey对应一个region,主要由 TableName(业务表名) StartRow(region区间起始rowkey) Timestamp(region创建时间戳) EncodedName(以上字段的MD5Hex值) |
info:regioninfo | 主要存储4个信息:EncodedName,RegionName,Region的StartRow和StopRow |
info:seqnumDuringOpen | 存储Region打开时的sequenceId |
info:server | region存储在哪个RegionServer上 |
info:serverstartcode | RegionServer启动的TiimeStamp |
hbase:meta作为最为核心的表,当大量请求同时放过来时,因为只有一个Region,很快就会成为热点Region并且无法承担这么大的压力。所以Hbase会将hbase:meta缓存在客户端上。
hbase:meta缓存在客户端MetaCache的缓存中,调用Hbase时,会在MetaCache中查找rowkey对应的Region,有以下几种情况:
1、region信息为空,说明缓存中没有这个rowkey所在region的任何信息。在首次查找时,需要先读取zookeeper中/hbase/meta-regioen-server的Znode用来确定hbase:meta所在的RegionServer,之后进行查询,新的信息存放在缓存中。
2、region信息不为空,但是调用RPC请求后发现并不在这个RegionServer上。这说明缓存中的信息已经过期,重新查询更新缓存信息。通常这种情况发生在不regionserver宕机或者region迁移,这种情况也很少出现。
3、region信息不为空且信息在对应的RegionServer上。这种为大多数的情况。直接通过缓存信息就能定位到对应的服务器查询数据。
Hbase Sacan操作:
Hbase的Scan时比较复杂的RPC操作。通常table.getScanner(sacn)获取到Scanner,通过scanner.next()就能拿到一个result。
1、用户每次next,都会尝试在cache对列中拿result
2、如果缓存队列为空,则会发起一次RPC请求当前scanner后续的result数据。
3、客户端获取到结果后,通过scanResultCache重组,结果放在cache对列中。
为什么需要对结果进行重组?这是因为为了避免当前RPC请求耗尽资源,对结果实现了多维度的限制,这样用户得到的数据可能就不是一行完整的数据了。
Scanner包含几个重要概念:
caching | 每次缓存数据最多放caching个result到缓存中 |
batch | 用户result中最多含有一行数据中的batch个cell。如果一行数据包括5个cell,batch为2的情况下,用户会拿到3个result,cell个数为2,2,1 |
allowPartial | 允许一行部分cell的result,设置这个会跳过重组,直接将Result返回 |
maxResultSize | 单次RPC操作最多拿maxResultSIze字节的结果集 |