【Flink】第四篇:【迷思】对update语义拆解D-、I 后造成update原子性丢失
【Flink】第五篇:checkpoint【1】
【Flink】第五篇:checkpoint【2】
【Flink】第八篇:Flink 内存管理
【Flink】第九篇:Flink SQL 性能优化实战
【Flink】第十篇:join 之 regular join
【Flink】第十三篇:JVM思维导图
【Flink】第十四篇:LSM-Tree一般性总结
【Flink】第十五篇:Redis Connector 数据保序思考
【Flink】第十六篇:源码角度分析 sink 端的数据一致性
【Flink】第十七篇:记一次牛轰轰的OOM故障排查
【Flink】第十八篇:Direct Memory 一箩筐
【Flink】第十九篇:从一个批量写HBase性能问题到一个Flink issue的距离
【Flink】第二十篇:HBase GC 调优实战
上一篇提到在用Flink SQL批量写HBase,遇到了三个坑,
- HBase 写热点
- HBase gc 调优
- HBase Canary报警,Slow Read
这一篇就来谈谈HBase的写热点。
HBase的设计思想主要是LSM。参见【Flink】第十四篇:LSM-Tree一般性总结。而LSM存储引擎的主要设计思想就是不断的将内存的有序存储结构flush到磁盘,这时候会在磁盘形成一个个的小的文件,如果每次都去做新文件和旧文件的合并,这显然是没必要,并且低效的。
这又是一个酸碱平衡的问题:我们要找到文件数和文件大小之间的一个平衡,既能控制住小文件的数量,以免带来的读写方放大可控;也不至于为了控制文件数量而每时每刻都要求只有一个合并的大文件。
所以HBase就有了小合并minor compation、大合并major compaction、分区split这三种管理HBase的Region的每个列族下的storefile文件:文件数 文件大小的动态策略。
这里就不详细说这三种策略了。我想分享一些我对于HBase的理解。其实,从本质上讲,HBase作为大数据存储,它原生实现了两个维度的表伸缩性:
- 水平切分,实现垂直伸缩性
以rowkey的字典顺序作为顺序聚集索引,并以此作为唯一索方式。以此来对数据表进行一个range方式的水平数据划分,即Region,最终将它们进行一个分布式存储和管理。
- 垂直切分,实现水平伸缩性
以列族为单位,在列族内部的字段是可动态扩展的,并且在物理存储结构上。列族内部的row是紧贴的,而列族之间是以Store为单位分开的。这就使得HBase具有了在列族内部,字段在水平方向具有可伸缩性。
但是,在HBase最简单的建表方式下,初始状态是只有一个Region,所以,对于我们这次Flink SQL批模式写HBase,程序从启动后就以最大吞吐量去写,是会存在写热点风险的,Flink全力写这初始的一个Region。
所以,我们要对HBase数据量比较大的表做一个预分区,但是预分区是建立在对于Rowkey的结构及数据分布特点、散列度、散列情况等了解的基础之上。于是去业务库对大表进行一个简单的数据分析(Data Profiling)。
在了解了要作为rowkey的业务字段的大体情况下,决定用reverse对业务主键进行一个反转,反转之后,对将开头的字符串进行一个预分区。
主流的写热点方案主要有三种:
- 加盐:添加随机值
- hash:采用md5散列算法取前4位做前缀
- 反转:例如将手机号反转
但是这其中又存在一个酸碱平衡:往往越散列的方式对于读越是利空的。所以,要结具体场景,在满足读需求并结合场景下数据特点进行选择。这里给出我的一个经验法则:
平衡读和写:厌热点:打散放前面,亲热点:顺序放后面
再结合HBase2.0之后的一些优化,例如,In-Memory Compaction,In-Memory Compaction是HBase2.0中的重要特性之一,通过在内存中引入LSM结构,减少多余数据,实现降低flush频率和减小写放大的效果。
CompactingMemStore中,数据以 segment 作为单位进行组织,一个memStore中包含多个segment。数据写入时首先进入一个被称为 active 的segment,这个segment是可修改的。当active满之后,会被移动到 pipeline 中,这个过程称为 in-memory flush 。pipeline中包含多个segment,其中的数据不可修改。CompactingMemStore会在后台将pipeline中的多个segment合并为一个更大、更紧凑的segment,这就是compaction的过程。
如果RegionServer需要把memstore的数据flush到磁盘,会首先选择其他类型的memstore,然后再选择CompactingMemStore。这是因为CompactingMemStore对内存的管理更有效率,所以延长CompactingMemStore的生命周期可以减少总的I/O。当CompactingMemStore被flush到磁盘时,pipeline中的所有segment会被移到一个snapshot中进行合并然后写入HFile。
最终,我的DDL如下:
代码语言:javascript复制create 'table',
{METADATA => {'SPLIT_POLICY' => 'org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy'}},
{NAME => 'info', TTL => 345600, IN_MEMORY_COMPACTION => 'BASIC'},
SPLITS => ['02','04','06','08']