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读流程原理
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文件为磁盘顺序读取做了优化,按页存储。下图展示了在内存中多个块存储并归并到磁盘的过程,合并写入会产生新的结果块,最终多个块被合并为更大块。
多次刷写后会产生很多小文件,后台线程会合并小文件组成大文件,这样磁盘查找会限制在少数几个数据存储文件中。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操作,合并成一棵大树,以优化读性能。
补充:
深入理解LSM树:https://www.pianshen.com/article/3694420068/
LSM-Tree全称是Log Structured Merge Tree,是一种分层,有序,面向磁盘的数据结构,其核心思想是充分了利用了,磁盘批量的顺序写要远比随机写性能高出很多,如下图示:
围绕这一原理进行设计和优化,以此让写性能达到最优,正如我们普通的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等系统(文件格式),如图所示。
列式存储对于只查找某些列数据的请求非常高效,只需要连续读出所有待查目标列,然后遍历处理即可;但是反过来,列式存储对于获取一行的请求就不那么高效了,需要多次IO读多个列数据,最终合并得到一行数据。另外,因为同一列的数据通常都具有相同的数据类型,因此列式存储具有天然的高压缩特性。
列簇式存储:从概念上来说,列簇式存储介于行式存储和列式存储之间,可以通过不同的设计思路在行式存储和列式存储两者之间相互切换。比如,一张表只设置一个列簇,这个列簇包含所有用户的列。HBase中一个列簇的数据是存储在一起的,因此这种设计模式就等同于行式存储。再比如,一张表设置大量列簇,每个列簇下仅有一列,很显然这种设计模式就等同于列式存储。上面两例当然是两种极端的情况,在当前体系中不建议设置太多列簇,但是这种架构为HBase将来演变成HTAP(Hybrid Transactional and Analytical Processing)系统提供了最核心的基础。
架构原理
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
- 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
由于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
默认情况下,每个 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里空间充足的时候,写入的操作时间减少,
写性能的优化:
客户端优化
- 批量写
批量写是为了减少rpc的次数。
1.HTable.put(List<Put>)
- Auto Flush
当autoflush=false时可以提升写入性能,但是关闭之后数据超2M(hbase.client.write.buffer)或用户执行了hbase.flushcommits()时才向regionserver提交请求
服务端优化
- WAL Flag
根据实际的业务场景和需求可以更改WAL机制来提高写入的性能,不写入WAL,异步写入WAL,同步写入WAL(默认的)
- 增大memstore的内存
可以调高Memstore的数值,降低 BlockCache 的数值,减少MemStore Flush的次数,可以提高写入的性能
- 减少HFile产生
如果HFile合并的速度还没有写入的速度快,就会造成阻塞。所以在业务低峰期做majorcompaction,可以提高系统的性能;或者添加新的节点
读性能的优化
客户端优化
- get请求可以使用批量请求,但是要根据实际的业务的场景,根据单条数据的大小来选择多少条数据为一批来提高读的性能。
1.Result[] re= table.get(List<Get> gets)
- scan缓存设置的大小
scan一次性需求从服务端返回大量的数据,客户端发起一次请求,客户端会分多批次返回客户端,这是避免一次性传输较多的数据给服务端及客户端有较大的压力。数据会加载到本地的缓存中,默认100条数据大小。当数据量较大时,传输数达到百次甚至数万的rpc请求。可以适当调大缓存的大小,减少rpc请求的数量。
1.scan.setCaching(int caching) //大scan可以设置为1000
- 请求指定列族或者列名
HBase是列族数据库,同一个列族的数据存储在一块,不同列族是分开的,当数据量较大时可以指定需要获取的列族或者列名,来减小IO,提高读的性能。
- 离线计算访问Hbase建议禁止缓存
当离线访问HBase时,往往就是一次性的读取,此时读取的数据没有必要存放在blockcache中,在读取时禁止缓存,可以提高读的性能。
1.scan.setBlockCache(false)
服务端优化
- 请求均衡
在业务高峰器查看读取的压力实在一台或者是几台服务器中,将热点数据进行拆分
- 调整BlockCache的数值
BlockCache是读缓存,对于读的性能很重要。如果读比较多,可以调高 BlockCache 的数值,降低 Memstore的数值。
- HFile文件数目
因为读取需要频繁打开HFile,如果文件越多,IO次数就越多,读取的延迟就越高此需要主要定时做 major compaction,可以在业务低峰期做major compaction
- Compaction是否消耗较多的系统资源
compaction主要是将HFile的小文件合并成大文件,提高后续业务的读性能,但是也会带来较大的资源消耗。 所以不要在高峰期间做 major compaction。
Bloomfilter设置是否合理
Bloomfilter主要用来在查询时,过滤HFile的,避免不需要的IO操作。Bloomfilter能提高读取的性能,可以根据当前的业务场景来选择BLOOMFILTER 的设置,默认设置为:BLOOMFILTER => ‘ROW’。