https://mapr.com/blog/in-depth-look-hbase-architecture/
HBase架构
先抛出一个问题:HBase读写是否都经过HMaster节点?
Client
读写请求:Meta cache(第一次会cache meta信息,第二次检索直接走cache,除非数据移动、丢失、遗漏,二次检索:检索数据+ 更新cache)
HMASTER
1.Region assignment 分配
- assgin(startup)启动的时候
- re-assgin(recovery修复、balancer负载均衡)重新分配
2.DDL (create, delete tables) operations: 表处理
职责:协调处理REGIONSERVER,通过ZK去监听REGIONSERVER状态
REGIONSERVER
- reads and writes
- locality(把RS节点部署在对应DN上)
- Regions:如何划分:按RK水平切分 start-key end-key(Contiguous Keys)(用于做后续数据检索) 1 rs => 1000Regions create table 时可以直接指定预分区(split)
- WAL :1)预写日志文件(存还没有被持久化的新数据;用于故障恢复) 2)edits编辑时是追加到wal末尾
- BlockCache(read cache) 存储经常被读取使用的数据, full时不常使用的回收
- Memstore(write cache):存储未落盘的新数据,会做排序;
There is one MemStore per column family per region ** 1 memstore -> N HFiles** ** 生产中不建议过多的cf,不超三个**
每一个cf都会对应一个Memstore,一个memstore对应一堆Hfiles
为何不是多个cf?
因为一个cf对应一个Memstore,一旦一个Memstore满了cf就会有Flush刷盘的操作,这时候全部的cf对应的都会有刷盘操作,这就影响了整个表的cf,越多影响越大
- Flush:memstore accumulates enough data 落盘的操作
- HFiles:1)真正数据落盘体现(sorted kv rows) 2)Btree 3)trailer(bloom filter、time range filter) 按照顺序写入,是非常的块(思考Hadoop中块的三副本是如何一个存储的顺序)
ZK
协处理器(3/5…奇数台, 做选举)
监控集群状态
- 与RS通信
- 与master通信
Meta table location
首次读数据
- 首先从ZK获取RS(regionserver)
- 根据你要检索的rk(rowkey)找到对应region,从zk找region的location
- 直接去真正的rs上获取数据
- 读取顺序(block cache -> memstore -> hfile)(随着时间的推移,可以不去查元数据表,直接通过cache缓存,除非数据移动、丢失、遗漏,二次检索:检索数据)
首次写数据
- put -> wal文件
- wal -> memstore -> client(返回成功信息)
Meta Table:
- a list of all regions
- like a b tree
- structure (key:region start-key, region id ;value:rs)
read amplification:
一个memstore 存在多个hfiles文件,可能会导致读取数据时多个文件 同时被检查,这就导致读放大
write amplification(写放大):
由于major compact导致的
Compact:(解决读放大,类似于合并、压缩)
- minor compact 会自动选取几个小的hfile合并成相对大的hfile
- major compact 1)将全部的hfiles文件合并成一个hfile; 2)清除 droped data(带有清除标记) ;以及过期cell单元格 3)将move的region移动到原有RS
mysql数据是秒删除,而对于Hbase,不是秒删除的,只有在做major compact的时候才会把数据真正删除掉;但是对于major compact是把所有的hfiles文件合并成一个hfile,这时候就会产生大量磁盘IO和网络通信,这时候就会引发写放大
Split:
- 初始为 1 region,达到一定值,进行split:变2 region,通知master
- for balancer region move,由于负载均衡,会把region迁移
结构流程:
-
读数据:
Client(get数据请求) -> ZK(Meta table,RegionServer是在ZK上面) -> RegionServer ->block cache ->memstore(未知的flush,不知道数据有没有落盘,因为memstore存的就是没有持久化、没有被落盘的数据) ->hfiles -
写数据:
Client(put) ->ZK(Meta table) -> RegionServer ->WAL ->Memstore -> (这一步表示已经写成功了)
Hfiles -> read amplification -> Compact(Minor/Major) -> write amplification ->Split(Move region For Balancing)
HBase 调优
- WAL关闭 (不在乎普通的预写日志丢不丢,就可以关闭,比如不是按照日志收费和计费的),这样写的时候可以少一层,速度肯定会比之前快
- flush
- block cache:
- memstore: size 可以控制落盘时间
- compact: 阀值 (多大的作为小文件去合并,因为合并操作会影响读取)
- split: 阀值
- balancing
- RK:(RK检索、scan扫描) 1)长度不建议过长 2)RK设计:a.加timestamp b.reverse反转 c.hash/md5 d.salting加盐 e.pre-split预分区 … 3)二级索引 Phoenix Solr ES
- TTL默认时效是forever
- version
- compression(snappy)
- API put/list/bulkload scan/get(batch/only one)
- conf 1) zk 2)retry 3)timeout 4)flush 5)compact 资源消耗过大/多:IO资源 带宽 => Minor Compact:建议 5-6 Major Compact手动处理 6)split
- JVM 1)gc 2)hdfs 3)zk
- HFile 大小 1)问题:HFile大小和HDFS block size什么关系?? 2)HFile文件是否过大 hbase.hstore.compaction.max.size = RegionSize/hbase.hstore.compactionThreshold
HBase 迁移[集群间数据同步、外部数据源到HBase数据同步]:
集群间同步方式:
- DistCp(需要停掉集群)
- Replication(动态备份)
- CopyTable
- Export/Import
- Rest API
- Snapshot快照
- HDFS文件迁移
外部数据源: - canal
- maxwell
HBase 案例
RIT:Region In Transition
- 发生原因 1)机器故障: Master/RS故障 2)Compact一直不结束
- open -> pending_close -> closing -> closed
关注日志:(Info)
- 临时(可以自我修复) 1)新Master重启(第一次 HA) -> 重启一台 -> 观察日志 CDH管理界面仍旧存在问题,重启CDH 2)Compact 一直不结束(数据表造成)
- 永久性(人工干预) 1)Compact 永久性阻塞 :a.memstore内存不够 -> 触发flush b.storefile数量过多 -> compact -> 阻塞flush
- 表因素 a.临时方案:加大集群的compact线程数 b.从当前处理batch移除表 -> hbck去检查一致性
- HDFS异常 region split 文件丢失 a.hbck文件检查 b.删除hdfs文件(暴力方式)
HBase 问题排查(注意INFO级别):
- web ui
- master log
- rs log
- hbck: 查看region
- fsck: 查看block (里面有一些高危命令,慎用)
- jvm命令
建议:
运维人员:
- CDH/HDP/TDH平台部署维护
- Shell
- 确定监控指标
- 告警及时信息
- 监控资源信息
- 预估(集群预估 + 大促活动数据量的高峰)
- 问题定位(查看日志)
- 组件性能优化
开发人员:
- 官网 + 外部网站(辅助)
- 源码 1)选定合适的版本 2)从一个简单点/业务问题点入手 3)关注测试用例 4)理解后化为自己的东西
面试人员:
官网附录的一些问题以及解决方法
Phoenix(二级索引):
- salt_buckets vs Pre-split :100个人 pre-split 按姓氏分组 - > 10 salt_buckets = 5: 按平均人头分配成5组
- SQL
- local indexes
- global indexes
- full indexes(ali/es)
data type:
mysql char(4) => phoenix char(4XXX) => phoenix varchar(8)
time => 转换成phoenix的time
Function:
phoenix: to_char(create_time,'yyyy-MM-dd')
mysql: create_time >= '2019-06-18 00:00:00' and create_time <= '2019-06-18 23:59:59'
== to_date(create_time,'yyyy-MM-dd')
spark sql: date_format(create_time,'yyyy-MM-dd')