一、背景及问题
0.Hdfs元数据管理
1.背景介绍
当前在数据资产管理平台上,需要展示每张hive表及分区的热力情况(文件数、存储量、更新时间等信息)。目前热力数据包含两部分内容:热力元数据和审计日志,其中审计日志可以直接消费kafka得到,而热力元数据暂时没有可以直接获取的地方,需要我们这边主动采集。
目前已经完成一版采集方案,为离线定时同步采集(T+1),因实时性不满足需求,所以需要再寻找更加实时的采集方案。
2.面临的问题与挑战
问题与挑战:
量大
(1) 集群的目录和文件数(节点)多,Top20的集群目录和文件数均上亿(大部分在1-3亿个目录和文件),对存储有很大的挑战
(2)EditLog量大,Top20集群的EditLog的qps均在100以上,部分集群的qps在1000以上,对处理有很大的挑战
顺序性依赖
EditLog的日志处理逻辑需要依赖顺序,即一条消息处理完成,才能处理下一条消息,因此无法并行处理EditLog,只能串行处理,这就导致无法通过增加机器或cpu来提高处理能力。
近实时
业务上需要近实时的收集数据,延迟不能太大,目前可接受分钟级别的延迟。
二、解决思路及技术选型
1.探索的方案及遇到的问题
方案 |
方案描述 |
方案优势 |
方案问题 |
备注 |
离线同步(T+1) | 通过每天拉取一次FsImage,解析FsImage,得到FsImage生成时的Hdfs集群情况信息 | 方案实现简单,目前平台已实现并将结果写入到hive表中,我们只需直接读取hive表即可 | 1.方案延迟大,无法满足实时性需求 2.不同集群的FsImage生成时间差距较大 |
|
基于内存的实时同步方案(nna) |
对于每个集群,部署一个特殊的NameNode节点,此节点不参与集群事务性的操作,只是进行元数据收集工作,元数据存储到内存中 | 可以实现实时采集 | 1.消耗的内存资源非常大,每个Hdfs集群均需要部署一台分析机器,并且元数据均存储在内存中,对于Top20的集群,每台机器的内存均需要上百G。 2.后期很难维护,公司现有八十多个Hdfs集群,就意味着我们也要申请八十多个机器与其对应 |
因资源问题,此方案pass |
基于ElasticSearch的实时同步方案(nna的改版) | 与nna类似,对于每个集群,同样部署一个特殊的NameNode节点,但是元数据不存储到内存中,而是存储到ES中 | 可以节省内存资源 | ES性能跟不上,消息处理不过来 |
因性能问题,此方案pass |
基于RocksDB+ElasticSearch的实时同步方案 | 不再每个集群部署一台机器,而是统一拉取FsImage和EditLog,并且将元数据存储到RocksDB中,来降低内存开销以及提高性能,最终将聚合的结果数据批量写入到ES | 1.可以满足近实时采集需求 2.不需要消耗大量的内存 3.可以满足性能要求 |
方案难度较大,实施过程中遇到很多坑 | |
2.技术选型
(1) 数据存储 -> RocksDB
Hdfs集群元数据量大(上亿数据),性能要求高(每秒顺序读写上千次),内存资源有限。基于以上要求,现有的大部分数据库都很难满足,尤其是有网络开销的数据库,很难达到性能要求,而内存资源又有限,因此最好的选择是基于磁盘的单机数据库。
RocksDB 是由 Facebook 基于 LevelDB 开发的一款提供键值存储与读写功能的 LSM-tree 架构引擎。用户写入的键值对会先写入磁盘上的 WAL (Write Ahead Log),然后再写入内存中的跳表(SkipList,这部分结构又被称作 MemTable)。LSM-tree 引擎由于将用户的随机修改(插入)转化为了对 WAL 文件的顺序写,因此具有比 B 树类存储引擎更高的写吞吐。
(2) 数据序列化 -> Protobuf
因Hdfs集群元数据量大,因此即使存储到磁盘上,存储开销依然很大,并且RocksDB为了提高写入和查询性能,会将部分数据放到内存中,因此单条数据越小,对内存和磁盘的开销越小,因此需要找一个在性能和序列化后数据量均较好的序列化方案。
Protobuf具备了优秀的序列化协议的所需的众多典型特征:
1、标准的IDL和IDL编译器,这使得其对工程师非常友好。
2、序列化数据非常简洁,紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。
3、解析速度非常快,比对应的XML快约20-100倍。
4、提供了非常友好的动态库,使用非常简介,反序列化只需要一行代码。
(3) 状态同步 -> Zookeeper
方案在实现过程中,会涉及到producer、consumer、loader三种角色,不同角色之间会有协同工作(例如loader下载完FsImage后,producer才能开始拉取EditLog等),如何协同不同角色?此处选择用Zookeeper的监听机制实现,当一个角色完成事件后,另一个角色可以立即监听到并做后续事情。
三、方案介绍
1.整体设计
FsImage Loader:从Hdfs上Download FsImage到本地,解析后进行后续处理,写入RocksDB
EditLog Loader:每隔1分钟拉取一次EditLog,写入kafka
EditLog Consumer:消费kafka中的EditLog消息,写入RocksDB,及聚合后写入ES
状态同步:同步FsImage Loader、EditLog Loader、EditLog Consumer三者之间的协同
2.详细设计
总体流程如下
hdfs内部的元数据同步流程如下所示,以mkdir为例
从图中可以看到元数据会写一份到JournalNode,同时standy的节点可以访问这部分数据,拉取元数据主要就是根据这部分来进行的。
(1)FSImage加载及解析流程
先下载faimage文件,然后根据这个文件启动一个FsNameSystem,然后加载数据。原来的数据是加载到内存中的,是一个树,如下所示:
每个节点都会有个节点id,现在主要就是加载了file和Directory,其他的没怎么加载,暂时没有发现问题,后面发现问题了再改。为了少占用内存,我们是加载到rocksDb中的,其中最重要的就是treeIndex和detail两个库,里面节点的定位是以节点id来定位的。加载过程中会涉及到启停kafka的consumer和producer。这个过程会写es和rocksDb
(2)EditLog加载及解析流程
editlog加载是采用定期拉取远程Journal的editlog文件方式,一旦producer启动起来后,就会根据zk中的txid去Journal node拉取editlog文件,并将下一个txid写入到zk中,拉取的数据经过处理后会发送到kafka。
consumer端获取到数据后处理,这里获取的是op,op不会带节点的id,里面都是些绝对路径,所以就借用到了之前加载时库中的节点id,通过路径解析出id,然后进行目录的聚合等操作,然后再写会rocksDb,并写出到es。
(3)状态含义及管理
现在状态是各个角色分开管理的,loader、consumer、producer。主要是借用的zk的节点监控来回调触发各种操作的。虽然比之前用mysql来管理要方便了,但仍然感觉有些繁杂,后面可以优化成RPC,和选主一并实现。
(4)RocksDB及ES存储结构
RocksDB
RocksDb数据存储到本地磁盘中,通过不同的目录,存储不同的数据(此处称之为库),目前对于一个集群,会创建4个库:detail、treeIndex、txidIdempotent、middleData,每个库(目录)下存储RocksDB的数据文件,存储位置及结构如下:
detail库:key是节点id(Long类型,Hdfs集群生成),value是由type、fileSize等组成的信息,通过Protobuf序列化后存储。detail库中每条数据存储一个节点的明细信息,包括文件和目录。
treeIndex库:key是parentId|type|curPath,value是节点id,根据treeIndex库,可以快速的由Hdfs目录或文件全路径(path)找到对应的id。下面会详细介绍此索引。
txidIdempotent库:用于幂等判断,因为方案中使用了kafka,实现消息的ExactlyOnce比较难,因此此处加了幂等判断,即使用kafka AtLeastOnce。幂等库key是txid(hdfs集群生成),value为1,代表已处理过。
middleData库:为FsImage加载过程中存储临时数据使用,FsImage加载完成后删除。
ElasticSearch
ElasticSearch存储最终的结果数据,用于DAM后端查询使用。当前是每个集群创建一个索引,索引ID由hdfs的path拼接而成。FsImage和EditLog的数据,在处理完成后,经过聚合,得到对应Path的数据,先在内存中缓存一段时间(10s),然后批量写入到ElasticSearch中。
(5)树形结构索引
由上文可知,处理的数据均存储到detail库中,但detail库的key是id,而很多EditLog消息中并不会有id(例如OP_TIMES、RENAME等),这就需要根据Path能够快速获取到id,而treeIndex库就是为了此目的。
索引结构:
上图为索引结构示例图,索引数据最终以KV结构存储到rocksdb中,对于每条editlog中的path,通过索引可以查询到对应id,从而可以从detail库中取出具体数据进行操作。
(6)增量计算及数据聚合
1、新增文件op(op_close):detail插入一条,同时treeIndex也加入一条,同时对其所有的递归父目录,修改文件数和文件num
2、删除文件:和新增类似
3、rename文件或者目录:先删除再新增,是目录时需要递归调用
(7)数据序列化及反序列化文章来源:https://www.toymoban.com/news/detail-701204.html
序列化和反序列化使用Protobuf,先写一个proto文件,然后安装一个proto程序生成一个java类,然后就可以调用里面得方法对java对象进行序列化和反序列化了
文章来源地址https://www.toymoban.com/news/detail-701204.html
到了这里,关于hdfs元数据实时采集的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!