HBase写流程原理

geomesa读写hbase hbase 读写性能_数据

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

HBase读流程原理

geomesa读写hbase hbase 读写性能_hbase_02

1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。
2)访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey,
查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以
及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
3)与目标 Region Server 进行通讯;
4)分别在 Block Cache(读缓存),MemStore 和 Store File(HFile)中查询目标数据,并将
查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不
同的类型(Put/Delete)。
5) 将从文件中查询到的数据块(Block,HFile 数据存储单元,默认大小为 64KB)缓存到
Block Cache。
6)将合并后的最终结果返回给客户端。

HBase读写很快的原因

1.采用列簇式存储

2.HBase能提供实时计算服务主要原因是由其架构和底层的数据结构决定的, 即由LSM-Tree(Log-Structured Merge-Tree) + HTable(region分区) + Cache决定——客户端可以直接定位到要查数据所在的HRegion server服务器,然后直接在服务器的一个region上查找要匹配的数据,并且这些数据部分是经过cache缓存的。

HBase会将数据保存到内存中,在内存中的数据是有序的,如果内存空间满了,会刷写到HFile中,而在HFile中保存的内容也是有序的。当数据写入HFile后,内存中的数据会被丢弃。

HFile文件为磁盘顺序读取做了优化,按页存储。下图展示了在内存中多个块存储并归并到磁盘的过程,合并写入会产生新的结果块,最终多个块被合并为更大块。

geomesa读写hbase hbase 读写性能_数据_03

多次刷写后会产生很多小文件,后台线程会合并小文件组成大文件,这样磁盘查找会限制在少数几个数据存储文件中。HBase的写入速度快是因为它其实并不是真的立即写入文件中,而是先写入内存,随后异步刷入HFile。所以在客户端看来,写入速度很快。另外,写入时候将随机写入转换成顺序写,数据写入速度也很稳定。

而读取速度快是因为它使用了 LSM树型结构,而不是B或B+树。磁盘的顺序读取速度很快,但是相比而言,寻找磁道的速度就要慢很多。HBase的存储结构导致它需要磁盘寻道时间在可预测范围内,并且读取与所要查询的rowkey连续的任意数量的记录都不会引发额外的寻道开销。比如有5个存储文件,那么最多需要5次磁盘寻道就可以。而关系型数据库,即使有索引,也无法确定磁盘寻道次数。而且,HBase读取首先会在 缓存(BlockCache)中查找,它采用了 LRU(最近最少使用算法),如果缓存中没找到,会从内存中的MemStore中查找,只有这两个地方都找不到时,才会加载HFile中的内容,而上文也提到了读取HFile速度也会很快,因为节省了寻道开销。

什么是LSM树呢?
什么是LRU?

eg.

A:如果快速查询(从磁盘读数据),hbase是根据rowkey查询的,只要能快速的定位rowkey, 就能实现快速的查询,主要是以下因素:
1、hbase是可划分成多个region,你可以简单的理解为关系型数据库的多个分区。
2、键是排好序了的
3、按列存储的

首先,能快速找到行所在的region(分区),假设表有10亿条记录,占空间1TB, 分列成了500个region, 1个region占2个G. 最多读取2G的记录,就能找到对应记录;

其次,是按列存储的,其实是列族,假设分为3个列族,每个列族就是666M, 如果要查询的东西在其中1个列族上,1个列族包含1个或者多个HStoreFile,假设一个HStoreFile是128M, 该列族包含5个HStoreFile在磁盘上. 剩下的在内存中。

再次,是排好序了的,你要的记录有可能在最前面,也有可能在最后面,假设在中间,我们只需遍历2.5个HStoreFile共300M

最后,每个HStoreFile(HFile的封装),是以键值对(key-value)方式存储,只要遍历一个个数据块中的key的位置,并判断符合条件可以了。 一般key是有限的长度,假设跟value是1:19(忽略HFile上其它块),最终只需要15M就可获取的对应的记录,按照磁盘的访问100M/S,只需0.15秒。 加上块缓存机制(LRU原则),会取得更高的效率。

B:实时查询
实时查询,可以认为是从内存中查询,一般响应时间在1秒内。HBase的机制是数据先写入到内存中,当数据量达到一定的量(如128M),再写入磁盘中, 在内存中,是不进行数据的更新或合并操作的,只增加数据,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O的高性能。

实时查询,即反应根据当前时间的数据,可以认为这些数据始终是在内存的,保证了数据的实时响应。

1、Hbase为什么写比读快

(1)根本原因是hbase的存储引擎用的是LSM树,是一种面向磁盘的数据结构:

Hbase底层的存储引擎为LSM-Tree(Log-Structured Merge-Tree)。LSM核心思想的核心就是放弃部分读能力,换取写入的最大化能力。LSM Tree它的核心思路其实非常简单,就是假定内存足够大,因此不需要每次有数据更新就必须将数据写入到磁盘中,而可以先将最新的数据驻留在内存中,等到积累到最后多之后,再使用归并排序的方式将内存内的数据合并追加到磁盘队尾(因为所有待排序的树都是有序的,可以通过合并排序的方式快速合并到一起)。另外,写入时候将随机写入转换成顺序写,数据写入速度也很稳定。

不过读取的时候稍微麻烦,需要合并磁盘中历史数据和内存中最近修改操作,所以写入性能大大提升,读取时可能需要先看是否命中内存,否则需要访问较多的磁盘文件。极端的说,基于LSM树实现的HBase的写性能比MySQL高了一个数量级,读性能低了一个数量级。

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

geomesa读写hbase hbase 读写性能_geomesa读写hbase_04

补充:

深入理解LSM树:https://www.pianshen.com/article/3694420068/

LSM-Tree全称是Log Structured Merge Tree,是一种分层,有序,面向磁盘的数据结构,其核心思想是充分了利用了,磁盘批量的顺序写要远比随机写性能高出很多,如下图示:

geomesa读写hbase hbase 读写性能_geomesa读写hbase_05

围绕这一原理进行设计和优化,以此让写性能达到最优,正如我们普通的Log的写入方式,这种结构的写入,全部都是以Append的模式追加,不存在删除和修改。当然有得就有舍,这种结构虽然大大提升了数据的写入能力,却是以牺牲部分读取性能为代价,故此这种结构通常适合于写多读少的场景

2、Hbase为什么读取速度也快

HBase能提供实时计算服务主要原因:

(1)是由其架构和底层的数据结构决定的,即由:LSM-Tree(Log-Structured Merge-Tree) + HTable(region分区) + Cache

客户端可以直接定位到要查数据所在的HRegion server服务器,然后直接在服务器的一个region上查找要匹配的数据,并且这些数据部分是经过cache缓存的。

前面说过HBase会将数据保存到内存中,在内存中的数据是有序的,如果内存空间满了,会刷写到HFile中,而在HFile中保存的内容也是有序的。当数据写入HFile后,内存中的数据会被丢弃。HFile文件为磁盘顺序读取做了优化,按页存储。下图展示了在内存中多个块存储并归并到磁盘的过程,合并写入会产生新的结果块,最终多个块被合并为更大块。

(2)rowkey是排序的

(3)数据按列存储

行式存储、列式存储、列簇式存储的概念

行式存储:行式存储系统会将一行数据存储在一起,一行数据写完之后再接着写下一行,最典型的如MySQL这类关系型数据库,行式存储在获取一行数据时是很高效的,但是如果某个查询只需要读取表中指定列对应的数据,那么行式存储会先取出一行行数据,再在每一行数据中截取待查找目标列。这种处理方式在查找过程中引入了大量无用列信息,从而导致大量内存占用。因此,这类系统仅适合于处理OLTP类型的负载,对于OLAP这类分析型负载并不擅长。

列式存储:列式存储理论上会将一列数据存储在一起,不同列的数据分别集中存储,最典型的如Kudu、Parquet on HDFS等系统(文件格式),如图所示。

geomesa读写hbase hbase 读写性能_hbase_06

列式存储对于只查找某些列数据的请求非常高效,只需要连续读出所有待查目标列,然后遍历处理即可;但是反过来,列式存储对于获取一行的请求就不那么高效了,需要多次IO读多个列数据,最终合并得到一行数据。另外,因为同一列的数据通常都具有相同的数据类型,因此列式存储具有天然的高压缩特性。

列簇式存储:从概念上来说,列簇式存储介于行式存储和列式存储之间,可以通过不同的设计思路在行式存储和列式存储两者之间相互切换。比如,一张表只设置一个列簇,这个列簇包含所有用户的列。HBase中一个列簇的数据是存储在一起的,因此这种设计模式就等同于行式存储。再比如,一张表设置大量列簇,每个列簇下仅有一列,很显然这种设计模式就等同于列式存储。上面两例当然是两种极端的情况,在当前体系中不建议设置太多列簇,但是这种架构为HBase将来演变成HTAP(Hybrid Transactional and Analytical Processing)系统提供了最核心的基础。

架构原理

geomesa读写hbase hbase 读写性能_客户端_07

1)StoreFile
保存实际数据的物理文件,StoreFile 以 HFile 的形式存储在 HDFS 上。每个 Store 会有一个或多个 StoreFile(HFile),数据在每个 StoreFile 中都是有序的。
2)MemStore
写缓存,由于 HFile 中的数据要求是有序的,所以数据是先存储在 MemStore 中,排好序后,等到达刷写时机才会刷写到 HFile,每次刷写都会形成一个新的 HFile。
3)WAL
由于数据要经 MemStore 排序后才能刷写到 HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做 Write-Ahead logfile 的文件中,然后再写入 MemStore 中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。

MemStore Flush

geomesa读写hbase hbase 读写性能_Server_08

  1. MemStore Flush

所有的flush都是以Region为单位刷新

1)MemStore级别
当 Region中 某个 MemStore 的大小达到了hbase.hregion.memstore.flush.size(默认值128M),会触发Region的刷写(若Region中有多个Store,只要有其中一个达到hbase.hregion.memstore.flush.size(默认值128M)值,就会触发flush,每个Store都会生成一个StroeFile文件,可能会生成多个小文件,所以一般情况下,一个Region只设置一个列簇(即一个Store))

2)Region级别
当处于写高峰的时候,会延迟触发第一个时机(MemStore级别)

当 Region 中的MemStore的总大小达到了hbase.hregion.memstore.flush.size(默认值128M)* hbase.hregion.memstore.block.multiplier(默认值4)时,会阻塞所有写入该 Region 的写请求,优先flush!这时候如果往 MemStore 写数据,会出现 RegionTooBusyException 异常。

举个例子:当 Region 中的某个 MemStore 占用内存达到128M ,会触发flush ,此时是允许写入操作的;若写入操作大于 flush 的速度,当 Region 中的所有 MemStore 占用内存达到 128 * 4 = 512M 时,阻止所有写入该 Region 的写请求,直到Region 中的所有 MemStore ,flush完毕,才取消阻塞,允许写入。
3)RegionServer级别
当RegionServer中,所有Region中的MemStore的总大小达到
java_heapsize * hbase.regionserver.global.memstore.size(默认值0.4)* hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)会flush,flush的时候根据每个Region中,总MemStore占用的大小进行降序排序,依次flush;flush的时候优先flush占用空间大的region,每flush一个region,会查看总的占用大小是否小于 java_heapsize * hbase.regionserver.global.memstore.size(默认值0.4)* hbase.regionserver.global.memstore.size.lower.limit(默认值0.95);如果还是大于,则继续flush region,若小于,则停止flush(注:此情况是允许memStore写入的)

当处于写高峰的时候,会延迟触发第三个时机(HRegionServer级别)

当RegionServer中,所有Region中的MemStore的总大小达到了 java_heapsize * hbase.regionserver.global.memstore.size(默认值0.4)时,会阻塞当前 RegionServer 的所有写请求(无法往Region中的MemStore写入数据),直到RegionServer中,所有Region中的MemStore的总大小 低于 java_heapsize * hbase.regionserver.global.memstore.size(默认值0.4)* hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)时,才取消阻塞(允许写入数据)

举个例子,如果 HBase 堆内存总共是 10G,按照默认的比例,那么触发 RegionServer级别的flush ,是 RegionServer 中所有的 MemStore 占用内存为:10 * 0.4 * 0.95 = 3.8G时触发flush,此时是允许写入操作的,若写入操作大于flush的速度,当占用内存达到 10 * 0.4 = 4G 时,阻止所有写入操作,直到占用内存低于 3.8G ,才取消阻塞,允许写入。
4)HLog级别
当WAL文件的数量超过hbase.regionserver.maxlogs,region会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.maxlogs以下(该属性名已经废弃,现无需手动设置,最大值为32)。

WAL 数量触发的刷写策略是,找到最旧的 un-archived WAL 文件,并找到这个 WAL 文件对应的 Regions, 然后对这些 Regions 进行刷写。

5)定期刷写
到达自动刷写的时间,也会触发MemStore flush。自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时),指的是当前 MemStore 1小时会进行一次刷写。如果设定为0,则关闭定时自动刷写。

6)手动刷写
可以通过 shell 命令"flush ‘table’"或者"flush ‘regionname’"分别对一个Region或者多个Region进行flush

StoreFile Compaction

geomesa读写hbase hbase 读写性能_hbase_09

由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)
和不同类型(Put/Delete)有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少 HFile 的个数,以及清理掉过期和删除的数据,会进行 StoreFile Compaction。
Compaction 分为两种,分别是 Minor Compaction 和 Major Compaction。Minor Compaction
会将临近的若干个较小的 HFile 合并成一个较大的 HFile,但不会清理过期和删除的数据。
Major Compaction 会将一个 Store 下的所有的 HFile 合并成一个大 HFile,并且会清理掉过期和删除的数据。

Region Split

geomesa读写hbase hbase 读写性能_geomesa读写hbase_10

默认情况下,每个 Table 起初只有一个 Region,随着数据的不断写入,Region 会自动进
行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但处于负载均衡的考虑,
HMaster 有可能会将某个 Region 转移给其他的 Region Server。
Region Split 时机:
1.当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize,
该 Region 就会进行拆分(0.94 版本之前)。
2.当 1 个 region 中 的某 个 Store 下所有 StoreFile 的总 大 小超过 Min(R^2 *
“hbase.hregion.memstore.flush.size”,hbase.hregion.max.filesize"),该 Region 就会进行拆分,其
中 R 为当前 Region Server 中属于该 Table 的个数(0.94 版本之后)。

Hbase性能比较

hbase配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FH3hs31o-1615795053144)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1615173642232.png)]

<?xml version="1.0" encoding="UTF-8"?>

<!--Autogenerated by Cloudera Manager-->
<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>hdfs://dsy0:8020/hbase</value>
  </property>
  HTable客户端的写缓冲的默认大小。这个值越大,需要消耗的内存越大。因为缓冲在客户端和服务端都有实例,所以需要消耗客户端和服务端两个地方的内存。得到的好处是,可以减少RPC的次数。可以这样估算服务器端被占用的内存: hbase.client.write.buffer * hbase.regionserver.handler.count
  <property>
    <name>hbase.client.write.buffer</name>
    <value>2097152</value>
  </property>
  通常的客户端暂停时间。最多的用法是客户端在重试前的等待时间。比如失败的get操作和region查询操作等都很可能用到。
  <property>
    <name>hbase.client.pause</name>
    <value>100</value>
  </property>
  失败以后的重试次数
  <property>
    <name>hbase.client.retries.number</name>
    <value>10</value>
  </property>
  当调用Scanner的next方法,而值又不在缓存里的时候,从服务端一次获取的行数。越大的值意味着Scanner会快一些,但是会占用更多的内存。当缓冲被占满的时候,next方法调用会越来越慢。慢到一定程度,可能会导致超时。例如超过了hbase.regionserver.lease.period。
  <property>
    <name>hbase.client.scanner.caching</name>
    <value>100</value>
  </property>
每条记录的最大大小
  <property>
    <name>hbase.client.keyvalue.maxsize</name>
    <value>10485760</value>
  </property>
  客户端是否允许中断
  <property>
    <name>hbase.ipc.client.allowsInterrupt</name>
    <value>true</value>
  </property>
  客户端超时获取
  <property>
    <name>hbase.client.primaryCallTimeout.get</name>
    <value>10</value>
  </property>
hbase客户端主呼叫超时多目标
  <property>
    <name>hbase.client.primaryCallTimeout.multiget</name>
    <value>10</value>
  </property>
  客户端扫描仪超时周期
  <property>
    <name>hbase.client.scanner.timeout.period</name>
    <value>60000</value>
  </property>
  <property>
    <name>hbase.coprocessor.region.classes</name>
    <value>org.apache.hadoop.hbase.security.access.SecureBulkLoadEndpoint</value>
  </property>
  <property>
    <name>hbase.regionserver.thrift.http</name>
    <value>false</value>
  </property>
  <property>
    <name>hbase.thrift.support.proxyuser</name>
    <value>false</value>
  </property>
  rpc超时时间
  <property>
    <name>hbase.rpc.timeout</name>
    <value>60000</value>
  </property>
  启用hbase快照
  <property>
    <name>hbase.snapshot.enabled</name>
    <value>true</value>
  </property>
  hbase快照区域超时
  <property>
    <name>hbase.snapshot.region.timeout</name>
    <value>300000</value>
  </property>
  hbase快照主超时毫秒
  <property>
    <name>hbase.snapshot.master.timeout.millis</name>
    <value>300000</value>
  </property>
  安全认证
  <property>
    <name>hbase.security.authentication</name>
    <value>simple</value>
  </property>
  rpc 保护
  <property>
    <name>hbase.rpc.protection</name>
    <value>authentication</value>
  </property>
  HBase守护进程和客户端都是Zookeeper客户端。该参数是他们和Zookeeper之间会话超时时间,单位毫秒
  <property>
    <name>zookeeper.session.timeout</name>
    <value>60000</value>
  </property>
  <property>
    <name>zookeeper.znode.parent</name>
    <value>/hbase</value>
  </property>
  <property>
    <name>zookeeper.znode.rootserver</name>
    <value>root-region-server</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>dsy2,dsy1,dsy0</value>
  </property>
  <property>
    <name>hbase.zookeeper.property.clientPort</name>
    <value>2181</value>
  </property>
  <property>
    <name>hbase.rest.ssl.enabled</name>
    <value>false</value>
  </property>
</configuration>

单条写入(10000条数据)

族列

运行所耗时间

网络接口接受的字节数

网络接口传送的字节数

群集磁盘IO

HDFSIO

1

47942ms

343 kb/s

341.4 kb/s

1.6MB/s

105.8kb/s

2

52884ms

308.4kb/s

391.2kb/s

1.3MB/s

100.4kb/s

3

54860ms

381.1kb/s

414.1kb/s

1.3MB/s

154.2kb/s

总结:族列的减少对运行时间没有太大的影响,可能原因是族列过少,耗时过长是频繁的与数据库交互消耗的时间较长。

不写入WAL:53554ms

异步写入wal: 38844ms

批量写入

100一批写入10000条数据

族列

运行所耗时间

网络接口接受的字节数

网络接口传送的字节数

群集磁盘IO

HDFSIO

1

8667ms

245.4Kb/s

142.1Kb/s

1.1MB/s

128.4Kb/s

2

8994ms

342.4Kb/s

291.2KB/s

927Kb/s

133Kb/s

3

9169ms

904.6KB/s

852.1Kb/s

2.2MB/s

771.8kb/s

随着族列的增加,消耗的时间变长,由于每一批的数据量较少,与数据库交互浪费时间。

不写入WAL:8735ms

异步写入wal: 9242ms

1000一批写入

族列

运行所耗时间

网络接口接受的字节数

网络接口传送的字节数

群集磁盘IO

HDFSIO

1

8419ms

225KB/s

214KB/s

696Kb/s

37.7Kb/s

2

8673ms

267.6Kb/s

252.8Kb/s

950.1Kb/s

48.7Kb/s

3

8752ms

340.8Kb/s

331.6Kb/s

1.5MB/s

127.5Kb/s

随着族列的增加写入数据库的时间相差不大

不写入WAL: 8302ms

异步写入 wal : 8666ms

5000一批写入

族列

运行所耗时间

网络接口接受的字节数

网络接口传送的字节数

群集磁盘IO

HDFSIO

1

8331ms

258.1Kb/s

255.6kb/s

836.6KB/s

84.4Kb/s

2

8565ms

305.2kb/s

266.5kb/s

1.1Mb/s

98.5Kb/s

3

8707ms

381.9Kb/s

310.1Kb/s

1.4Mb/s

184Kb/s

随着族列的增加,消耗的时间基本上没什么变化

不写入WAL:8520ms

异步写入 wal : 8560ms

1百万条数据

30000一批耗时: 48707ms 47030ms 不写入WAL:43988ms 异步写入 wal : 45980ms

20000一批耗时: 47224ms 46409ms 不写入WAL:40118ms 异步写入 wal : 41622ms

15000一批耗时:33681ms 38881ms 不写入WAL:34328ms 37344ms 异步写入 wal : 40535ms

10000一批耗时:42954ms 39356ms 不写入WAL:35403ms 异步写入 wal : 41736ms

5000一批耗时: 40415ms 46806ms 不写入WAL:40208ms 异步写入 wal : 44489ms

8000一批耗时: 40865ms 42372ms 不写入WAL:37698ms 异步写入 wal : 41052ms

总结:在数据量为1000万条数据,族列为三,一次行批量的put集合的长度在15000到30000之间比较合适(电脑的运行内存:8G;主机的运行内存:16G),1000一批与数据库频繁的交互,增加了数据的压力,写入消耗的时间主要由put集合的长度决定,列族的多少决定了put集合的长度,在实际应用应该结合电脑的性能,主机的内存,数据量的大小,在保证内存的合理利用,与数据库交互的次数合理的情况下,才能高效的处理数据

根据最佳性能测试(2*hbase.client.write.buffer/put.heapSize()(528))

100万条数据:

同步写入WAL : 39581ms 39609ms 46820ms

不写入WAL: 32143ms 41530ms 40911ms 34539ms

异步写入WAL:43689ms 37997ms 39895ms

采用BufferedMutator:37175ms 38118ms

总结: WAL机制一方面是为了确保数据即使写入缓存丢失也可以恢复,另一方面是为了集群之间异步复制。默认WAL机制开启且使用同步机制写入WAL。不写入WAL的话,在发生异常时会出现数据的丢失,同步写入WAL的话会造成数据队列的阻塞,增加写入数据的时间。异步写入WAL,是延迟(1s)写入到WAL中,同步写入WAL是确保数据库中的数据和写入到WAL中的数据保持一致,异步写入WAL当还在没写入到WAL中发生异常,会导致数据的丢失或者写入到HDFS中的数据不一致。

同步写入到WAL:在测试的过程中有时会出现写入的时间过长,我认为是WAL中的文件达到了最大值32个(之前的版本中可以对WAL文件的个输进行设置hbase.regionserver.max.log (现在已被舍弃)),regionServer进行刷写到HDFS中,会出现写入的时间边长,当WAL里边的文件少时,写入的时间会缩短

异步(不写入)写入到WAL:在测试过程中出现写入的时间过长的原因,当他在写入到memstore时,进行了刷写操作,所以会出现时间变长的写入操作,当regionServer里空间充足的时候,写入的操作时间减少,

写性能的优化:

客户端优化

  1. 批量写
    批量写是为了减少rpc的次数。
1.HTable.put(List<Put>)
  1. Auto Flush

当autoflush=false时可以提升写入性能,但是关闭之后数据超2M(hbase.client.write.buffer)或用户执行了hbase.flushcommits()时才向regionserver提交请求

服务端优化

  1. WAL Flag

根据实际的业务场景和需求可以更改WAL机制来提高写入的性能,不写入WAL,异步写入WAL,同步写入WAL(默认的)

  1. 增大memstore的内存

可以调高Memstore的数值,降低 BlockCache 的数值,减少MemStore Flush的次数,可以提高写入的性能

  1. 减少HFile产生

如果HFile合并的速度还没有写入的速度快,就会造成阻塞。所以在业务低峰期做majorcompaction,可以提高系统的性能;或者添加新的节点

读性能的优化

客户端优化

  1. get请求可以使用批量请求,但是要根据实际的业务的场景,根据单条数据的大小来选择多少条数据为一批来提高读的性能。
1.Result[] re= table.get(List<Get> gets)
  1. scan缓存设置的大小

scan一次性需求从服务端返回大量的数据,客户端发起一次请求,客户端会分多批次返回客户端,这是避免一次性传输较多的数据给服务端及客户端有较大的压力。数据会加载到本地的缓存中,默认100条数据大小。当数据量较大时,传输数达到百次甚至数万的rpc请求。可以适当调大缓存的大小,减少rpc请求的数量。

1.scan.setCaching(int caching) //大scan可以设置为1000
  1. 请求指定列族或者列名

HBase是列族数据库,同一个列族的数据存储在一块,不同列族是分开的,当数据量较大时可以指定需要获取的列族或者列名,来减小IO,提高读的性能。

  1. 离线计算访问Hbase建议禁止缓存

当离线访问HBase时,往往就是一次性的读取,此时读取的数据没有必要存放在blockcache中,在读取时禁止缓存,可以提高读的性能。

1.scan.setBlockCache(false)

服务端优化

  1. 请求均衡

在业务高峰器查看读取的压力实在一台或者是几台服务器中,将热点数据进行拆分

  1. 调整BlockCache的数值

BlockCache是读缓存,对于读的性能很重要。如果读比较多,可以调高 BlockCache 的数值,降低 Memstore的数值。

  1. HFile文件数目

因为读取需要频繁打开HFile,如果文件越多,IO次数就越多,读取的延迟就越高此需要主要定时做 major compaction,可以在业务低峰期做major compaction

  1. Compaction是否消耗较多的系统资源

compaction主要是将HFile的小文件合并成大文件,提高后续业务的读性能,但是也会带来较大的资源消耗。 所以不要在高峰期间做 major compaction。

Bloomfilter设置是否合理

Bloomfilter主要用来在查询时,过滤HFile的,避免不需要的IO操作。Bloomfilter能提高读取的性能,可以根据当前的业务场景来选择BLOOMFILTER 的设置,默认设置为:BLOOMFILTER => ‘ROW’。