date: 2020-10-26 15:43:00
updated: 2020-10-26 18:45:00
HBase进阶
1. 架构
master负责管理多个region server,一个region server里有多个region。
一个表会划分多个region,起初只有一个,数据增多,region增大到一定程度会拆分成2个region
一个表最终被保存在多个region server里
hmaster挂了不影响读写,但是 create table 这种涉及元数据的操作,如果hmaster挂了就会报错
一个region里有多个store,一个store代表一个列族。(region按照行键来划分,所以相当于每一行有几个列族就有几个store)
store包括两部分:内存中的memstore和磁盘的storefile。写数据会先写到memstore,当数据达到阈值(默认64M)后 region server 会启动 flushcache 进行将数据写到 storefile里,每次形成一个storefile
当storefile文件的数量增长到一定阈值后,系统会进行合并(minor、major ),在合并过程中会进行版本合并和删除工作(major),形成更大的storefile
当一个region所有storefile的大小和数量超过一定阈值后,会把当前的region分割为两个,并由hmaster分配到相应的regionserver,实现负载均衡
客户端检索数据,先在memstore找,找不到再去blockcache查找,找不到再找storefile,即:client->memstore->blockcache->storefile。如果读到了会把数据放到blockcache里缓存,方便下次读取
Region是HBase中存储和负载均衡的最小单元,不同的Region可以分布在不同的 Region Server上
Region由一个或者多个Store组成,每个store保存一个columns family
每个Strore又由一个memStore和0至多个StoreFile组成
memstore为写入缓存,blockcache为读取缓存
2. 写数据
- Client 向 zk 发送请求,请求 meta 表所有的 regionServer
- zk 返回 regionServer 地址
- Client 获取到 meta 表,请求对应的 region 所在的 regionServer
- 返回 meta 表数据
- Client 向 regionServer 发送写数据请求
- 写入 wal(write ahead log)
- 将数据写入 memstore
- regionServer 反馈给 Client 写入成功
在 0.9 版本(低版本)时,还存在一个 -ROOT- 表,作用是为了避免meta表过大而拆分为多个子表,可以通过 -ROOT- 表来对meta表进行管理
第6步和第7步 具体流程如下:
-
hbase-server-2.3 版本 搜索 HRegion 类,再搜索 STEP 1,
doMiniBatchMutate(BatchOperation<?> batchOp)
方法即为写数据的部分// STEP 1. Try to acquire as many locks as we can and build mini-batch of operations with locked rows => 写入之前先获取锁 // STEP 2. Update mini batch of all operations in progress with LATEST_TIMESTAMP timestamp // We should record the timestamp only after we have acquired the rowLock, // otherwise, newer puts/deletes are not guaranteed to have a newer timestamp => client如果不传时间戳,会自动获取服务器端的时间戳 // STEP 3. Build WAL edit // STEP 4. Append the WALEdits to WAL and sync. // STEP 5. Write back to memStore
-
hbase-server-1.3 版本
doMiniBatchMutate(BatchOperation<?> batchOp)
方法不一样。也是先Build WAL edit
,写入日志,但是并没有先同步,而且先写入memstore,在 finally 那里去判断 wal log 是否同步成功,如果不成功,回滚 memstore 记录
写数据时会先向hlog写(方便memstore里的数据丢失后根据hlog恢复,向hlog中写数据的时候也是优先写入内存,后台会有一个线程定期异步刷写数据到hdfs,如果hlog的数据也写入失败,那么数据就会发生丢失)
频繁的溢写会导致产生很多的小文件,因此会进行文件的合并,文件在合并的时候有两种方式,minor和major,minor表示小范围文件的合并,major表示将所有的storefile文件都合并成一个
3. Flush 过程
当写数据到一定程度之后,会把内存中的数据flush到磁盘,配置项在 hbase-default.xml
1. habse.regionserver.global.memstore.size
默认大小为内存大小的0.4。当regionserver的所有的memstore的大小超过这个值的时候,会阻塞客户端的读写
2. habse.regionserver.global.memstore.size.lower.limit
默认大小为第一个配置项的大小的0.95。即从这个值开始flush到内存,如果写数据过快,超过flush的速度,导致memstore逐渐变大,达到堆大小的0.4,那么就会暂停读写操作,专注于flush,直到memstore的大小下降
3. hbase.regionserver.optionalcacheflushinterval
默认为1个小时(当前内存最后一次编辑时间+1个小时),自动flush到磁盘
4. hbase.hregion.memstore.flush.size
单个region里memstore大小。默认为128M,超过这个大小就会刷写
5. hbase.regionserver.max.logs
如果wal的文件数量达到这个值(默认32),就会刷写
6. hbase.regionserver.hlog.blocksize
默认HDFS 2.x版本默认的blocksize大小
4. 读数据
- Client 向 zk 发送请求,请求 meta 表所有的 regionServer
- zk 返回 regionServer 地址
- Client 获取到 meta 表,请求对应的 region 所在的 regionServer
- 返回 meta 表数据
- Client 向 regionServer 发送读数据请求
- 同时会读 memstore 和 storefile,如果 storefile 里有数据,会加载到 blockcache 中,然后把数据做一个合并,取时间戳最大的那条数据返回给client,并且将数据写入到 blockcache
- 遵循的大体流程是 client->memstore->blockcache->storefile。如果读到了会把数据放到blockcache里缓存,方便下次读取
同时去读内存和磁盘,是为了避免磁盘的时间戳大于内存的时间戳,即put数据的时候设置了老时间戳
5. compact 合并
memstore不断flush到磁盘,生成hfile。
minor compactions 会把多个文件hfile合并为一个大的hfile
major compactions 会把所有hfile合并为一个hfile,默认是7天,但是生产上应该关闭,会非常消耗资源,应在空闲时间手动触发
compact 会 rewrite hfile to a single storefile 重写的过程会下载hdfs文件,然后重新写入,所以很消耗资源
hbase.hstore.compactionThreshold
是一个store(列族)允许的hfile个数,超过这个个数就会合并
6. region split 切分
region 交给不同服务器,缓解热点问题 => hfile 不断拆分,文件越来越大,到最后有可能还是会导致热点问题的存在,因为有的文件特别大,查的数据都在这个文件里 => 建表的时候实现 预分区
HBase 默认分区规则
memstore.flush.size=128MB
max.store.size=10G
分区规则:Min(R^2 * “hbase.hregion.memstore.flush.size”, “hbase.hregion.max.filesize”)
第一次拆分大小为:min(10G,11128M)=128M // 一开始的时候就一个region,当数据量达到128M时,会一分为二,变成2个region,然后会往第二个region里写数据,但是第一个不会写,处于半满状态 => 之前分裂的region都不会再被写入数据,处于半满状态
第二次拆分大小为:min(10G,33128M)=1152M
第三次拆分大小为:min(10G,55128M)=3200M
第四次拆分大小为:min(10G,77128M)=6272M
第五次拆分大小为:min(10G,99128M)=10G
第五次拆分大小为:min(10G,1111128M)=10G // 最大是10G
官方建议使用一个列族,避免的问题是:有的列族很多数据,有的列族可能只有几条数,按照region切分,然后flush到磁盘,可能会产生很多的小文件