Hbase
数据模型
在HBase中,一条数据拥有唯一的主键,一个或多个列族(一般最多设计不会超过3),列族必须在定义的时候声明,一个列族有任意数量的列,所以说列具有松散性,在声明时可以不定义,每一个数据可以有多个版本,当版本达到定义的版本数时(默认为1),就会把最早的版本清理掉,一个列族的数据在物理上保存在同一个HFile/StoreFile中。在查找时通过主键找单对应的列族,再找到对应的列,最后确定具体的版本号。
架构模型
1.zookeeper:完成HMaster的选举,监控HRegionServer,维护元数据的集群配置。
2.Client:用来连接访问,可以通过HBase Shell,JAVA API,SQL等访问。
3.HMaster(cp架构):
管理分配:管理和分配HRegion到具体的HRegionServer,还有分割HRegion时,关于新的HRegion的分配,管理用于对Table结构的DDL(创建,修改,删除)操作。
负载均衡:一方面负责将数据均衡的分明配不同的HRegionServer,防止发生数据倾斜。另一方面将用户的请求散发到不同的HRegionServer,防止HRegionServer访问过热。
维护数据:当有失效的HRegion时,会将失效的HRegion分配到正常的HRegionService。同时当有HRegionService宕机时会把内部的HRegion迁移到另外的HRegionService。
权限控制:
4.HRegionService
HRegionServer 直接对接用户的读写请求,是真正干活的节点,属于 HBase 具体数据的管理者。主要工作职责如下
①、实时和HMaster保持心跳,汇报当前节点的信息。
②、接收到HRegion的创建表命令时,会分配一个HRegion对应一张表。
③、负责切分在运行过程中变的过大的HRegion。
④、当HRegionServer意外关闭时,当前节点的HRegion会被其他HRegionServer管理。
⑤、维护HMaster分配给他的HRegion,处理对这些HRegion的请求。
当客户端发送 DML 和 DQL 操作时,HRegionServer 负责和客户端建立连接;
WAL:Write Ahead Log 日志先行。记录了数据写入、更新日志,它被用来做故障恢复;
MemStore:写缓存,数据首先会被写入到 MemStore 中。每个 HRegion 的每个 Column Family 都会有一个 MemStore。
负责与底层的 HDFS 交互,存储数据(HLog、HFile)到 HDFS。
BlockCache:读缓存,在内存中存储了最常访问的数据,采用 LRU 机制进行淘汰。
5.HRegion
负载均衡:当HRegion的数据达到10G时(默认),就会切分出来另外一个HRegion。
Split:当表被刚创建的时候,HBase会分配给Table一个HRegion,所有的请求都会访问到同一个HRegionServer 的同一个 HRegion 中,这个时候就达不到负载均衡的效果了,集群中的其他 HRegionServer 可能处于比较空闲的状态。解决这个问题可以用 pre-splitting 在创建 Table 时提前生成多个 HRegion。
在 Table 初始化的时候如果不配置的话,HBase 是不知道如何去 Split HRegion 的,因为 HBase 不知道应该把哪个 RowKey 作为 Split 的开始点。如果我们可以大概预测到 RowKey 的分布,我们可以使用 pre-spliting 来帮助我们提前 Split HRegion。
如果我们的预测不是特别准确,还是会导致某个 HRegion 过热被集中访问,不过还好我们还有 auto-split,默认按 10G 自动切分。但是如果文件到达 9G 后迟迟未到 10G 此时对于 HBase 来说是比较难受的。最好的办法就是首先预测 Split 的切分点,做 pre-splitting,后面再交给auto-split 来处理。
HBase 在每次数据合并之后都会针对相应 HRegion 生成一个 requestSplit 请求,requestSplit 首先会执行 checkSplit,检测 FileSize 是否达到阈值,如果超过阈值,就进行切分。
6.Store:一个 HRegion 由多个 Store 组成,每个 Store 都对应一个 Column Family,Store 包含 1 个 MemStore 和 0 或多个 StoreFile 组成。
MemStore:作为 HBase 的内存数据存储,数据的写操作会先写到 MemStore 中,当 MemStore 中的数据增长到指定阈值(默认 128M)后,HRegionServer 会启动 FlushCache 进程将 MemStore 中的数据写入 StoreFile 持久化存储,每次写入后都形成一个单独的 StoreFile。当客户端检索数据时,先在 MemStore 中查找,如果 MemStore 中不存在,则会在 StoreFile 中继续查找。
StoreFile:MemStore 中的数据写到文件后就是 StoreFile,StoreFile 底层是以 HFile 格式保存的。HBase 以 StoreFile 的大小来判断是否需要切分 HRegion。当一个 HRegion 中所有StoreFile 的大小和数量都增长到超过指定阈值时,HMaster 会把当前 HRegion 分割为两个,切分后其中一个 HRegion 会被转移到其他的 HRegionServer 上,实现负载均衡。
7.HFile: StoreFile(HFile) 是 HBase 最终存储数据的介质
Block:每个 HFile 由 N 个 Block 组成。
KeyValue:每个 Block 又是由多个 KeyValue 数据组成,KeyValue 对象是数据存储的核心,KeyValue 包装了一个字节数组,同时将偏移量offsets 和 lengths 放入数组中,这个数组指定从哪里开始解析数据内容。
8.HLog:一个 HRegionServer 只有一个 HLog 文件。负责记录数据的操作日志,当 HBase 出现故障时可以进行日志重放、故障恢复。例如磁盘掉电导致 MemStore 中的数据没有持久化存储到 StoreFile,这时就可以通过 HLog 日志重放来恢复数据。
9.HDFS:HDFS 为 HBase 提供底层数据存储服务,同时为 HBase 提供高可用支持。HBase 将 HLog 存储在 HDFS 上,当服务器发生异常宕机时,可以重放 HLog 来恢复数据。
数据刷写
触发时机
内存阈值
1.MerStore达到128M时(默认)。
2.如果数据增加过快,MerStore默认达到4个时进行合并,除此之外还会再刷写的时候阻塞所有写入该Store的请求。
内存总和
HBase 为 HRegionServer 所有的 MemStore 分配了一定的写缓存,当HRegionServer的所有内存总和大于阈值时进行刷写,并且阻塞所有的HRegionServer的写操作。
计算公式:例如:HBase 堆内存总共是 32G,MemStore占用内存为:32 * 0.4 * 0.95 = 12.16G 将触发刷写。0.4和0.95是默认系数。
日志阈值
HBase 使用了 WAL 机制(日志先行),当数据到达 HRegion 时是先写入日志的,然后再被写入到 MemStore。当日志达到阈值时,也会进行刷写。
定期刷写
当达到指定时间时,HBase会自动刷写,默认是一小时,一般调大,比如10小时。如果时间过短,一方面会刷写频繁,另一方面会产生很多小文件,影响随机读的性能。
更新频率
内存的更新数量已经足够多,默认三千万,也会刷写。
手动刷写
Shell 中通过 flush 命令。
- hbase> flush 'TABLENAME' //表名
- hbase> flush 'REGIONNAME' //region名字
- hbase> flush 'ENCODED_REGIONNAME' //
- hbase> flush 'REGION_SERVER_NAME'
注意
1.触发刷写时,都会先检查刷出去的StoreFile文件数是否超过参数配置的个数,默认是16。如果文件数达到16,会被推迟到 hbase.hstore.blockingWaitTime 参数设置的时间后再刷写。
刷写策略
1.HBase1.1版本之前,MemStore 刷写是 HRegion 级别的。如果要刷写某个 MemStore ,所有在同一个HRegion里面的Store里面的MemStore都刷写。
2.HBase1.1版本之后
①、HBase1.1之前的策略还存在。
②、查看MemStore里面的内存是否大于指定的阈值,大于刷写。
计算公式:flushSizeLowerBound = max((long)128 / 3, 16) = 42 。
3代表有几个列族。
③、将HRegion中的MemStore按照isSloppyMemStore分到两个HashSet里面(sloppyStores 和 regularStores ),然后
Ⅰ、判断sloppyStores里面的MemStore内存占用是否达到阈值,flushSizeLowerBound = max((long)128 / 3, 16) = 42,达到阈值刷写。没有达到阈值不做处理。
Ⅱ、再判断regularStores里面的MemStore内存占用是否达到阈值,flushSizeLowerBound = max((long)128 / 3, 16) = 42,达到阈值刷写。没有达到阈值不做处理。
Ⅲ、如果以上两个条件都没有满足刷写,就回退到HBase1.1版本之前的刷写策略。
刷写流程
prepareFlush阶段(准备刷写)
刷写之前MemSotre拍快照(Snapshot),拍快照为了防止刷写的时候出问题。同时为了防止刷写数据的同时又拍快照而造成对后续处理困难,所以就在刷写期间上锁,阻塞用户端的写操作,所以再创建Snapshot期间持有锁,而且Snapshot的创建非常快,所以此锁期间对客户的影响一般非常小。
flushCache阶段(刷写阶段)
如果创建快照没问题,那么返回的result将为null。我们就可以进行下一步internalFlush-CacheAndCommit(内部刷新缓存并提交) 。它包括两个阶段,flushCache和commit阶段。
flushCache阶段:把准备阶段创建好的快照写道临时文件里面,临时文件存放再HRegion文件夹下面的.tmp目录里面。
commit阶段:把flushCache阶段产生的临时文件移到(rename)对应的列族下面,并做一些清理工作,比如删除第一步生成的Snapshot。如果改名、清理、删除失败,那么这个流程失败。
数据合并
合并分类
Minor Compaction(次要/小)
把一些小的、相邻的StoreFile合并成一个更大的StoreFile,这个过程不做任何删除数据,多版本数据的清理工作,但是会对minVersion并且设置TTL的过期版本数据进行清理。目的就是让StoreFile变少变大。
注:快速让小文件合并成大文件。
Major Compaction(主要/大)
把所有的StoreFile合并成一个StoreFile清理三类无意义的数据:被删除的数据、TTL过期数据、版本号超过设定版本号的数据。
一般Major Compaction时间持续比较长,整个过程会消耗大量的系统资源,对上层业务有比较大的影响,因此线上业务都会关闭自动触发Major Compaction功能,改为手动业务在低峰期触发。
注: 清理大文件不必要的数据,释放空间。
合并时机
触发 Compaction 的方式有三种:MemStore 刷盘、后台线程周期性检查、手动触发。
MemStore 刷盘
每次MemStore数据刷写完后会产生File,检查File的数量是不是大于了配置的数量,大于就进行合并(Compaction)。Compaction 都是以 Store 为单位进行的,整个 HRegion 的所有 Store 都会执行 Compact。
周期性检查
定期检查是否需要Compact。先检查文件数是否大于配置,大于就触发合并。如果不满足就检查Major Compaction条件(默认7天触发一次,可配置手动触发)
周期性检查线程 CompactionChecker 大概 2hrs 46mins 40sec 执行一次。计算公式为:hbase.server.thread.wakefrequency(默认为 10000 毫 秒) * hbase.server.compactchecker.-interval.multiplier(默认1000)。
手动执行
一般来讲,手动触发 Compaction 通常是为了执行 Major Compaction,一般有这些情况需要手动触发合并:
1.自动Major Compaction影响读写性能(可以选择直接关闭),因此会在低峰期手动触发。
2.用户在执行完alter(更改)操作后希望立即生效,手动执行Major Compaction。
3.HBase管理员发现硬盘容量不够的情况下手动触发Major Compaction删除大量过期数据。
合并策略
线程池
HBase CompacSplitThread 类内部对于 Split、Compaction 等操作专门维护了各自所使用的线程池。和 Compaction 相关的是longCompactions 和 shortCompactions。前者用来处理大规模 Compaction,后者处理小规模 Compaction。默认值为 2 *maxFlilesToCompact(默认为 10) * hbase.hregion.memstore.flush.size,如果 flush size 大小是 128M,该参数默认值就是 2 * 10 * 128M = 2.5G。
注:就是通过公式计算。
合并策略
HBase 主要有两种 Minor Compaction 策略:RatioBasedCompactionPolicy(0.96.x 之前) 和 ExploringCompactionPolicy(当前默认)。
RatioBasedCompactionPolicy(基于比列的合并策略):从老到新依次扫瞄HFile文件,满足以下条件之一停止扫描:当前文件大小(老文件) < 比当前文件新的所有文件大小总和 * Ratio(高峰期1.2,非高峰期5),当前所剩候选文件数 <= 阈值(默认为3)。例如:当前文件 2G < 所有文件大 小总和 1G * 1.2,高峰期不合并,非高峰期合并;
ExploringCompactionPolicy 策略(默认策略):Exploring策略会记录所有合适的文件集合,然后寻找最优解,待合并文件数最多或者待合并文件数相同的情况下文件较小的进行合并;
注:扫瞄完所有的文件,找到最优的合并文件进行合并。
FIFO Compaction 策略:收集过期文件并删除,对应业务的列族必须设置有 TTL;
Tier-Based Compaction 策略(分层策略):针对数据热点情况设计的策略,根据候选文件的新老程度将其划分为不同的等级,每个等级都有对应的 Ratio,表示该等级文件比选择为参与 Compation 的概率(基于 Ratio 策略根据热点情况设置不同的 Ratio);
Stripe Compation 策略(条纹策略):将整个 Store 中的文件按照 Key 划分为多个 Range,此处称为 Stripe,一个 Stripe 内部就类似于一个小HRegion,可以执行 Minon Compation 和 Major Compation。
文件合并
分别读出待合并 HFile 文件的 KV,并顺序写到位于 ./tmp 目录下的临时文件中,再将临时文件移动到对应 HRegion 的数据目录。将Compaction 的输入文件路径和输出文件路径封装为 KV 写入 WAL 日志,并打上 Compaction 标记,最后强制执行 Sync。将对应 HRegion 数据目录下的 Compaction 输入文件全部删除。
数据切分
通过切分,一个 HRegion 变为两个近似相同大小的子 HRegion,再通过 balance 机制均衡到不同 HRegionServer上,使系统资源使用更加均衡。
切分原因
1.数据分布不均匀
一个HRegionServer上数据文件越来越大,读请求也越来越多,一旦所有的请求都落在同一个HRegionServer上,尤其是很多热点数据,必然会导致很多很严重的性能问题。
2.Compaction性能损耗严重
Compaction本质上是一个排序合并的操作,合并操作需要占用大量内存,因此文件越大,占用内存越多。Compaction有可能需要迁移到本地进行处理(balance之后的Compaction就会存在这样的场景),如需要迁移的数据是大文件的话,带宽资源就会损耗严重。
3.资源耗费严重
HBase的数据写入量也是惊人的,每天都有上亿条的数据写入不做切分的话一个热点HRegion的新增数据量就有可能几十G,用不了多长时间读请求就会把单台HRegionServer的资源耗光。
触发时机
HBase 在每次数据合并之后都会针对相应 HRegion 生成一个 requestSplit 请求,requestSplit 首先会执行 checkSplit,检测 FileSize 是否达到阈值,如果超过阈值,就进行切分。
切分策略
1.0.94版本之前只有一种切分策略ConstantSizeRegionSplitPolicy ,当Store中的某个列族达到10G(默认)时候,HRegionServer就会自动分裂。
2.0.94版本之后有三种切分策略ConstantSizeRegionSplitPolicy,IncreasingToUpperBound-RegionSplitPolicy 还有 KeyPrefixRegionSplitPolicy。
①、ConstantSizeRegionSplitPolicy同0.94版本之前的策略一样。
②、IncreasingToUpperBoundRegionSplitPolicy:0.94版本之后默认的切分策略
- # R 为同一个 Table 中在同一个 HRegionServer 中的 HRegion 的个数
- hbase.hregion.memstore.flush.size 默认值 128MB。
hbase.hregion.max.filesize 默认值为 10GB 。- Min(R^2 * "hbase.hregion.memstore.flush.size", "hbase.hregion.max.filesize")
如果初始时 R=1 ,那么 Min(128MB, 10GB)=128MB ,也就是说在第一个 Flush 的时候就会触发分裂操作。当 R=2 的时候 Min(2*2*128MB, 10GB)=512MB ,当某个 StoreFile 大小达到 512MB 的时候,就会触发分裂。如此类推,当 R=9 的时候, StoreFile 达到 10GB 的时候就会分裂,也就是说当 R>=9 的时候, StoreFile 达到 10GB 的时候就会分裂。
③、KeyPrefixRegionSplitPolicy:可以保证相同的前缀的 RowKey 保存在同一个 HRegion 中。指定RowKey前面的几位划分HRegion,通过读取KeyPrefixRegionSplitPolicy.prefix_length 属性,该属性为数字类型,表示前缀长度,在进行 Split 时,按此长度对 SplitPoint 进行截取。此种策略比较适合固定前缀的 RowKey。当 Table 中没有设置该属性,指定此策略效果等同与使用 IncreasingToUpperBoundRegionSplitPolicy。
我们可以通过配置 hbase.regionserver.region.split.policy 来指定 Split 策略,也可以写我们自己的 Split 策略。
切分流程
寻找切分点
系统首先会遍历所有的Store,找到其中最大的一个,再在Store中找出最大的HFile,定位这个文件中心位置对应的RowKey,作为Region的切分点。
开启切分事务
切分线程会初始化一个 SplitTransaction 对象,从字面上就可以看出来 Split 流程是一个类似“事务”的过程,整个过程分为三个阶段:prepare - execute - rollback。
prepare阶段
在内存中初始化两个子 HRegion,具体是生成两个 HRegionInfo 对象,包含 tableName、regionName、startkey、endkey 等。同时会生成一个 Transaction Journal,这个对象用来记录切分的进展。
execute阶段
这个阶段会有事务的参与。
rollback 阶段
如果 execute 阶段出现异常,则执行 rollback 操作。为了实现回滚,整个切分过程被分为很多子阶段,回滚程序会根据当前进展到哪个子阶段清理对应的垃圾数据。
切分优化
对于预估数据量较大的表,需要在创建表的时候根据 RowKey 执行 HRegion 的预分区。通过 HRegion 预分区,数据会被均衡到多台机器上,这样可以一定程度解决热点应用数据量剧增导致的性能问题。 再要进行系统的切分的话是对每个分区切分,没有系统的切分默认是一个切分。
表设计
行键设计
重要性
原因
HBase的行键默认按字典排序进行,这种设计优化了扫瞄(Scan),允许将相关的行或彼此靠近的行一起读取,但是设计不好的行键是热点的常见来源。
热点发生在大量Client直接访问一个或者极少数个节点,大量访问会使热点HRegion所在的HRegionServer超过自身的承受力,性能下降甚至不可用,也会对同一台服务器托管的其他区域产生不利影响(主机资源全被这个热点HRegion占用,已无法服务其他HRegion的请求),所以行键的设计使集群得到充分和均匀的利用就变得非常重要。
策略
防止热点的有效措施
1.反转策略:反转固定长度或者数字格式的RowKey。这样可以使得 RowKey 中经常改变的部分放在前面。这样可以有效的随机 RowKey,但是牺牲了 RowKey 的有序性。例如:以手机号为 RowKey,可以将手机号反转后的字符串作为 RowKey,这样就避免了以手机号那样比较固定的开头而导致的热点问题。
2.加盐策略:在RowKey前面增加一个随机数,加盐之后的 Rowkey 就会根据不同的前缀分散到各个 HRegion 上以避免热点。
3.哈希策略:把主键取哈希值。
反转、加盐、Hash都属于散列思想,目的就是把RowKey打散,但是又有迹可循。
预分区
主键设计三原则
唯一原则
单主键
组合主键(注意顺序)
长度原则
不要超过16个字节
对齐RowKey长度
散列原则
反转
加盐
哈希
列族设计
最优设计
追求原则:在合理范围内,尽可能的减少列族
最有设计:将所有相关性很强的Key-value都放在同一个列族。这样既能做到查询效率高,也能保证尽可能少的访问不同的磁盘文件。
控制长度:列族名的长度要尽可能的少,一个是为了节省空间,一个是为了加快效率,最好是一个字节。
列族属性
HFile 会被切分为多个大小相等的 Block,每一个 Block 大小可以在创建表列族的时候通过 BlockSize 参数指定,默认是 64K。Block 大的话一次加载进内存的数据就多,扫描查询 Scan 效果好,但是 Block 小的话,随机查询 Get 效果好。
常用优化
表优化
预分区
Pre-Creating Regions。默认情况下,在创建 HBase 表的时候会自动创建一个 HRegion 分区,当导入数据的时候,所有的 HBase 客户端都会向这一个 HRegion 写数据,直到这个 HRegion 足够大了才进行切分,所以建表时一般会提前预分区,这样当数据写入 HBase 时,会按照HRegion 分区的情况,在集群内做数据的负载均衡。
RowKey
HBase 中 RowKey 用来检索表中的记录,支持以下三种方式:
1.通过单个 RowKey 访问:即按照某个 RowKey 键值进行 Get 操作;
Column Family
不要在一张表里定义太多的 Column Family。目前 HBase 并不能很好的处理超过 2~3 个 Column Family 的表。因为某个 Column Family 在Flush 的时候,它邻近的 Column Family 也会因关联效应被触发 Flush,最终导致系统产生更多的 I/O。
Version
创建表的时候,可以通过 setMaxVersions(int maxVersions) 设置表中数据的最大版本。如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)。
创建表的时候,可以通过 setTimeToLive(int timeToLive) 设置表中数据的存储生命期,过期数据将自动被删除,例如如果只需要存储最近两天的数据,那么可以设置 setTimeToLive(2 * 24 * 60 * 60)。
Compact & Split
实际应用中,可以考虑必要时手动进行 Major Compact,将同一个 RowKey 的修改进行合并形成一个大的 StoreFile。同时,可以将StoreFile 设置大些,减少 Split 的发生。
写入优化
多Table并发写
创建多个 Table 客户端用于写操作,提高写数据的吞吐量,一个例子:
- public void dbTables() throws IOException {
- Configuration conf = HBaseConfiguration.create();
- Connection conn = ConnectionFactory.createConnection(conf);
- String table_log_name = "test";
- Table[] tableLogs = new Table[2];
- Put p = new Put(Bytes.toBytes("row1"));
- p.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("12"));
- Put p2 = new Put(Bytes.toBytes("row2"));
- p2.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("12"));
- for (int i = 0; i < 2; i++) {
- tableLogs[i] = conn.getTable(TableName.valueOf(table_log_name));
- }
- tableLogs[0].put(p);
- tableLogs[1].put(p2);
- tableLogs[1].close();
- tableLogs[0].close();
- }
WAL Flag
在 HBae 中,客户端向集群中的 RegionServer 提交数据时(Put/Delete操作),首先会先写 WAL(Write Ahead Log)日志(即 HLog,一个 HRegionServer 上的所有 HRegion 共享一个 HLog),当 WAL 日志写成功后,再接着写 MemStore,然后客户端被通知提交数据成功;如果 写 WAL 日志失败,客户端则被通知提交失败。这样做的好处是可以做到 HRegionServer 宕机后的数据恢复。
因此,对于相对不太重要的数据,可以在 Put/Delete 操作时,通过调用 Put.setWriteToWAL(false) 或 Delete.setWriteToWAL(false) 函数,放弃写 WAL 日志,从而提高数据写入的性能。
批量写
通过调用 Table.put(Put) 方法可以将一个指定的 RowKey 记录写入 HBase,同样 HBase 提供了另一个方法:通过调用 Table.put(List) 方法可以将指定的 RowKey 列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络 I/O 开销,这对于对数据实时性要求高,网络传输 RTT(Round Trip Time) 高的情景下可能带来明显的性能提升。
HTable参数设置
1.Auto Flush:通过调用 HTable.setAutoFlush(false) 方法可以将 HTable 写客户端的自动 Flush 关闭,这样可以批量写入数据到 HBase,而不是有一条 put 就执行一次更新,只有当 put 填满客户端写缓存时,才实际向 HBase 服务端发起写请求。默认情况下 Auto Flush 是开启的。
2.Write Buffer:通过调用 HTable.setWriteBufferSize(writeBufferSize) 方法可以设置 HTable 客户端的写 buffer 大小,如果新设置的 buffer 小于当前写 buffer 中的数据时,buffer 将会被 Flush 到服务端。其中,writeBufferSize 的单位是 byte 字节数,可以根据实际写入数据量的多少来设置该值。
3.多线程并发写:在客户端开启多个 HTable 写线程,每个写线程负责一个 HTable 对象的 Flush 操作,这样结合定时 Flush 和写 buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被 Flush(如 1 秒内),同时又保证在数据量大的时候,写buffer 一满就及时进行 Flush。下面给个具体的例子:
读取优化
显示指定的列
当使用 Scan 或者 GET 获取大量的行时,最好指定所需要的列,因为服务端通过网络传输到客户端,数据量太大可能是瓶颈。如果能有效过滤部分数据,能很大程度的减少网络 I/O 的花费。
关闭 ResultScanner
如果在使用 table.getScanner 之后,忘记关闭该类,它会一直和服务端保持连接,资源无法释放,从而导致服务端的某些资源不可用。所以在用完之后,需要执行关闭操作,这点与 JDBS 操作 MySQL 类似。
查询结果
对于频繁查询 HBase 的应用场景,可以考虑在应用程序和 HBase 之间做一层缓存系统,新的查询先去缓存查,缓存没有再去查 HBase。
缓存优化
设置Scan缓存
HBase 中 Scan 查询可以设置缓存,这样可以有效的减少服务端与客户端的交互,更有效的提升扫描查询的性能。
hbase.client.scanner.caching 配置项可以设置 HBase Scanner 一次从服务端抓取的数据条数,默认情况下一次一条。通过将其设置成一个合理的值,可以减少 Scan 过程中 next() 的时间开销,代价是 Scanner 需要通过客户端的内存来维持这些被 Cache 的行记录;
1.在 HBase 的 conf 配置文件中进行配置 -----> 整个集群生效
2.在 Table.setScannerCaching(int scannerCaching) 中进行配置 -----> 本次表连接生效
开启块缓存
如果批量进行全表扫描,默认是有缓存的,如果此时没有缓存,会降低扫描的效率。scan.setCacheBlocks(true|false);对于经常读到的数据,建议使用默认值,开启块缓存。
缓存查询结果
对于频繁查询 HBase 的应用场景,可以考虑在应用程序和 HBase 之间做一层缓存系统,新的查询先去缓存查,缓存没有再去查 HBase。例如 Redis。
Hbase缺点
对于 HBase 而言,它将“节点探活”这一重要的任务交给 ZooKeeper 来做,是可以商榷的。因为如果运维不够细致的话,会使得ZooKeeper 成为影响 HBase 稳定性的一个坑。