目 录CONTENT

文章目录

Hadoop实践|趣头条百 PB 规模 Hadoop 实践

醉酒的行者
2025-10-11 / 0 评论 / 0 点赞 / 23 阅读 / 0 字

随着趣头条业务的高速发展,趣头条及其附属产品如米读等,和母公司 innotech 集团的产品矩阵,目前总的存储数据量规模已经达到百 PB 左右,包括 HDFS 的热数据,和阿里云 OSS 的冷数据。日均计算任务个数达到二十万个,Hadoop 集群规模近 2000 台左右。Hadoop 集群支持趣头条和母公司 innotech 集团各种数据平台和业务,这一年多一路走来也经历了好几个阶段,到目前形成了较强的源码自研能力,和各种疑难杂症的定位分析解决能力。下面主要介绍趣头条这些年来来对 Hadoop 集群的一些优化,希望对其他公司有所帮助。

NameNode负载和扩展性问题

拆RPC端口以及拆NameSpace组成Federation

针对 NameNode 单点瓶颈,在把 NameNode 拆分成 Client RPC 端口和 Service RPC 端口后,推进了 HDFS Federation 的架构,原因是 NameNode 单点存在元数据量激增的问题,也存在 NameNode RPC 负载激增的问题。

针对 Federation 之间的数据迁移引入 FastCopy:如下图所示:

针对大数据量的 Federation 各个 NameSpace 之间的拷贝,比 Distcp 提升 3 倍左右的效率。

Balancer负载转移和搬迁优化

拆分成 Federation 架构之后,HDFS Balancer 操作对 Active NameNode 造成了很大对负载,为此我们把 Balancer 操作的负载转移到了 standby 上面,从而降低了 Active NameNode 的 RPC 负载。具体把 Balancer 负载转移到 Standby NameNode 思想和社区最新的 HDFS 读写分离思想是一致的,读写分离 HDFS 社区具体的 Issue 为:HDFS-12943 ,而对应的 Balancer 转移到了 ObserverNode 的 patch 为: HDFS-14162。而我们的版本还不支持读写分离的功能,为了快速降低负载,我们把 Balancer 对 Active NameNode 的 RPC 主动抛异常到了 Standby NameNode,并且让 Standby NameNode 对 Balancer 放行。搬迁的时候忽略小的块,按照从大到小到顺序降序,增加搬迁的速度。具体如下图:

拆分日志相关的NameSpace降低负载

有了 HDFS Federation 架构以后,日志还是会和业务的 NameSpace 互相产生影响,为此我们把 defaultFs 修改成系统单独的 NameSpace。我们也向 Hadoop YARN 社区贡献了针对提交目录,日志聚合目录可以负载均衡到各个 NameSpace 的设想,具体 Issue 见:YARN-9634。

NameNode用户的拥塞控制

社区提出了 FairCallQueue ,如上图所示,原有的 FIFO 的 RPC 结构,改成了 Fair 的结构,来对高频率的单账户进行缓解和限制,详细 issue 见:HADOOP-10282。应用以后,有效的隔离了 Presto 等即使查询用户并发量聚集时候,对 HDFS 其他线上业务的影响。

目前我们使用了 FairCallQueue + RPC Backoff, 能满足我们拥塞控制的需求。有效限制了异常高负载的用户对整体 RPC 可用性的影响。

针对用户较多的 NameSpace 我们正准备进行用户优先级分更多层级,进行多层的 Qos 保障。

异步化各种操作提高NameNode的吞吐量

editlog和auditlog的异步化

原先版本的 NameNode 的 editlog 的行为和 auditlog 的行为都是同步阻塞的,这对 NameNode 的吞吐量影响很大,为此我们把这 editlog 和 auditlog 两个行为改成了异步化。

块汇报的优化

数据量越来越大以后,对 NameNode 的堆栈信息统计后发现,块汇报的压力对用户的影响较大,为此我们考虑对块汇报进行了优化。首先全量块汇报的时候加盐,分散整体汇报对 NameNode 的压力。然后增量汇报的时候进行如下优化:

首先把 NameNode 端的块汇报异步进行聚合,有效的缓解了 RPC 的压力,对应的 Issue 为:HDFS-9198。

然后相应的 DataNode 端的块汇报也进行了批量聚合,对应 Issue 为:HDFS-9710。

NameNode锁时间追踪

HDFS-10872 添加了 NameNode 锁住时间对应的 Metrics。NameNode 的锁队列长度堆积过高的时候,我们增加了全局锁对应的锁住时间,对某些锁占用时间过长的情况,进行分析,对很多锁优化对细节很有帮助。

Decommission的改进

大集群的 Decommission 操作非常常见,如机器迁移下线,机器故障需要下线等。而旧的 Decommssion 代码存在如下的问题:

•遍历每个节点,对每个 disk 进行遍历,负载集中,没有分布到各个节点和各个磁盘,会导致命中的那个磁盘非常热。•通过上述 NameNode 的锁时间追踪发现,加入一个 DataNode 进行下线,会占用较长时间的写锁。•replica 队列堆积的问题。•等待复制队列多次判断是否 replica,会重复占用写锁的时间。

社区最近也有了更完善的实现:HDFS-14854。

Qos保障,业务控制,限流以及作业追踪

软限制和作业追踪

针对访问过高的用户,进行审计增强,目前的审计日志无法获取用户的作业信息。某些异常作业对某些治理不够完善的大表的疯狂访问等等行为,会对集群造成很大的稳定性和性能影响。为此我们引入了审计增强。这一块需要改的比较多包括计算引擎,以及 YARN,HDFS 都需要改一些依赖的 Patch,主要有:

•HDFS 相关:HDFS-9184。•YARN 相关:YARN-4349。•HIVE 相关:HIVE-12254。•SPARK 相关:SPARK-15857。

Flink 还没有 CallerContext 内置,我们提了个 Issue,待完善:FLINK-16809。

Flink 入库对 HDFS 的压力还是非常大的,加上业务滥用,有很严重的小文件问题见 FLINK-11937。

这样审计日志中就有了关键的作业信息,然后通过打到 Kafka,Flink 做实时分析,就能很容易的定位到 HDFS 高负载的作业。

硬限制:NameNode源码改动

另外一种硬的方式,不做事后分析,做事前强限制:上述提到过拥塞控制是用户对应的拥塞控制,这里对目录进行硬限制,因为除了不合理的用户的高频访问,还存在大表或者治理非常不完善的目录或者库表,可以做 QPS 限制,可以针对如 create,delete 等操作,在 NameNode 代码里对相关的目录对应的 RPC 做窗口的统计,如果 QPS 大于阈值则对客户端返回一个重试信息,进行

用户体验和运维便利性

自研 HDFS Proxy

由于历史原因导致,很多算法等业务需要独立的客户端进行管理,而业务的激增导致了客户端配置的频繁更新造成了很大的人力运维成本。且客户端的种类过于繁多,例如调度客户机,容器化的调度客户机,普通 gateway 等等。为了实现配置转移到了服务端进行控制,我们开发了 HDFS Proxy,客户端无需配置,Hdfs Client 将请求转发到对应的 HDFS Proxy Server。Proxy 可以横向扩展,上面挂了一层负载均衡器。非常轻量级,已经使用了将近半年,由于 viewFs 客户端维护方式很不利于运维管理,且我们当前版本比较老,且 Router 不够成熟,主要用于和 Router 进行过渡。具体结构如下图所示:

HDFS Router 改进和二次开发

随着 Router 的成熟,和我们对 Router 进行了一些定制化的改进,我们慢慢从我们轻量级的 HDFS Proxy 切换到 Router,毕竟开源的力量是伟大的,我们也要站在巨人的肩膀上。

Router审计日志的完善和作业追踪

Router 本身对 AuditLog 支持对不好,为此我们增加了定制对 AuitLog,并且准备继续在 Router 这一层对任务进行追踪。在 Router 层实现软限制和作业追踪。

Router Trash重构和RPC优化

针对数据成本优化,我们做了 Hive 生命周期的项目,每天都有大量的 Trash 操作。Router 对 Trash 的支持很不好,社区有类似客户端的修改方案,但是很不友好,为此我们对 Trash 操作进行了重构,增加的新的 RPC 调用。重构后不仅解决了 Router 中对应多个 NameSpace 的删除操作。还把之前的 Trash 对 NameNode 的 RPC 负载降低了 50%,客户端从 (mkdir rename) -> trash。这一块也贡献给了社区,待完善:HDFS-15083。

Router 支持全局Quota管控

如果单个目录挂载了多个 NameSpace,Router 目前也支持了全局的 Quota 管控,但还有部分细节需要完善。

Router rename across Federation

针对 Federation 的各个 NameSpace 之间的 FastCopy 上述有做了介绍,Router 有个功能可以实现类似 FastMove 的功能,针对跨 NameSpace 的已挂载的目录,可以进行 rename 到其他 NameSpace。

HDFS目录实时解析

HDFS 的目录信息解析,需要从 FSImage 进行解析,集群大了以后,我们 FSImage 达到几十上百 G,解析过程相当缓慢,只能以 T+1 的方式进行解析。为此,我们开发了准实时的解析项目,来应对,例如:短时间内存储增量巨大的目录,小文件数量剧增的目录。

聚合策略:利用 EditLog 的操作码,通过实时流,利用 Tidb 或者 Durid 等进行准实时聚合。

操作数种类比较多,主要追踪的操作有:OP_DELETE, OP_MKDIR, OP_ADD, OP_UPDATE_BLOCKS, OP_CLOSE, OP_RENAME_OLD。

服务的稳定性和性能

由于 Federation 架构 8 组业务的 NameSpace 共享同一个 DataNode 底层服务,加上本身我们的机型磁盘块很多,且业务的复杂性多样性对 DataNode 的访问,NameNode 的压力转移到了 DataNode 上面。

DataNode DU导致IO重和重启Uncached问题

存储从DU改为内存计算

DataNode 默认使用 DU 对存储总量进行汇总给 NameNode,DU 操作对 DataNode 的 IO 压力比较大,且 DataNode 的 IO 没有和全局锁进行分离,IO 也会占用锁的时间。DU 对 IO 压力大的解决方案有多种,分散 DU 的时间加个随机数然后分布到各个节点,减缓整体的 IO,但是无法避免还是需要 DU 操作。

针对磁盘的 DU,我们把存储总量的计算放到了内存里,因为内存里本身有磁盘块的信息,通过内存数据结果进行定期计算。具体最新的 Issue:HDFS-9710。

解决重启Uncached问题

DataNode 滚动的时候,经常会有 ungraceful shutdown 的情况,会导致存储量的缓存没有缓存到本地,那么启动的时候就会重复去计算,针对 DU 的场景会导致重启时间变得很长,为此我们加了定时线程对缓存进行更新,重启的时候就不用去重新计算存储总量了。具体 Issue 见:HDFS-15171。

慢节点和读写长尾优化

当集群节点日益增长当时候,很容易产生 DataNode 节点老化导致磁盘或者网络 IO 慢等其他问题,这就会造成用户的读写长尾等问题。

DataNode 端的 metrics 收集:HDFS-10917 慢节点监控,然后心跳汇报给 NameNode:HDFS-11194。

除了 DataNode 的慢节点监控,以及 NameNode 汇总慢节点信息,也能从客户端去监控读写速度以及读写长尾的 DataNode 节点,这一块社区也有对应的实现,有待完善:HDFS-12861。

开启客户端并发读:针对慢读取另起一个线程并发读,线程池的大小 dfs.client.hedged.read.threadpool.size 慢读取的阈值 dfs.client.hedged.read.threshold.millis (默认是 500)

写慢节点:写慢节点的时候,配合慢节点的情况,做快速的 PipeLine Recovery。

DataNode锁优化之旅

业务量激增导致 8 组 NameNode 的负载都打到了 DataNode,突然出现 DataNode 心跳时间陡增到数分钟,导致心跳没有即时收到,DataNode 经常在高峰期批量 Dead,对业务造成了很严重的影响。为此我们分析的 DataNode 的堆栈情况,发现是由于 DataNode 心跳的全局锁被其他并发过高的读写等操作占用,导致关键心跳线程被 Blocked 住。

Synchronized非公平锁改为ReentrantLock公平锁

首先,我们把 DataNode 的 Synchronized 非公平锁改成了,ReentrantLock 默认为公平锁。

ReentrantLock公平锁拆成公平的读写锁

针对全局的可重入 ReentrantLock,拆成了读写锁,效果很好,堵住几千个线程缓解了很多。

拆分成以BlockPool为单位的细粒度读写锁

继续拆锁,拆分成以 BlockPool 为单位的读写锁,意味着,如果你有 8 组 NameSpace 的话,一个 DataNode 全局锁,可以拆分成 8 把锁。我在社区提了个 Issue:HDFS-15180。从灰度的节点看,锁的进一步拆分,带来了预期的效果,没有拆分之前,Directory Scan 扫描操作会占用较长的锁时间,经常长达 10 几秒,甚至几十秒:

滚动以后,因为每个 BlockPool 单独扫描,锁住时间降低到 2s, 1s,甚至更小。

HDFS 的存储量计算,从 DU 改为内存计算以后,内存中的 deepcopy 部分本身会占用较长的锁时间超过 300ms,拆分为 BlockPool 锁以后,没有超过 300ms 的情况出现。拆分前有超过 300ms 的情况较多:

最终目标,最小单位的锁

最终目标,一个 Volume 中对应的 BlockPool 单位拆成一把锁。如下图所示,如果 HDFS Federation 有 4 组 NameSpace,每个 DataNode 有 3 个磁盘块。那么就对应了 4 个 BlockPool(BP),和 3 个 Volume,原生的 DataNode 全局锁是一把锁,理想的情况下,在这个例子情况下是可以拆分成为 4*3 = 12 把读写锁,对应锁住的范围就是两个椭圆的交接重合部分。目前我们已经拆成了 BlockPool 为单位,对应到这个例子就是 4 把读写锁,对性能的提升效果不错。

DataNode 接受指令异步化

我们查看日志的时候发现,DataNode 接受指令的时候,会把心跳线程给阻塞住,为此把阻塞的线程改成异步的线程池去处理这个指令操作,这样不会把心跳线程给堵住。如下图所示,修改之前,心跳阻塞时间可以达到几十秒。

DataNode IO和锁分离

DataNode 的 IO 操作,有时候会占用很长的锁时间,为此我们正准备把 IO 和锁进行分离。

进行中的和未来规划

•我们在测试环境已经测试了 Hadoop3 的新功能,准备在新集群迁移的时候使用,完成 Hadoop2 到 Hadoop3 的升级 。

•Router 还有很多功能正在不断完善。

•为了解决 NameNode 读占比大的问题,我们准备对 Hadoop3 尝试读写分离功能,把读转移到 Standby NameNode。

•持续跟进新功能:

•NameNode 分段锁:HDFS-14703 , 解决 NameNode 锁吞吐量的问题。

•Ozone,解决小文件,和 NameNode 扩展性的问题,数据存储底层也是用的 DataNode。看了部分 Ozone 在开发中的代码,有个存储原地改变的功能还在开发中,DataNode 的数据直接从 HDFS 转成 Ozone,很不错的功能,可能还有其他惊喜。

•EC 的冷存储和公有云 OSS 的冷存储,成本和性能进行对比。

•HDFS 操作全面异步化:HDFS-9924。

•NameNode 启动时间优化。

本文由趣头条相关同事投稿。

作者简介:朱琦,趣头条大数据部门离线组负责 Hadoop 组件。2018 年硕士毕业于南京邮电大学。Apache Hadoop HDFS && YARN Contributor。实习曾在 Citrix 从事虚拟化相关工作,在 Morgan Stanley 基础架构部门从事分布式存储相关工作。毕业以来曾在中国电信大数据基础架构部门从事 Hadoop 相关工作。目前负责趣头条 Hadoop HDFS , Hadoop YARN 等相关工作。

另外,趣头条大数据部门离线组负责人李富强,给了本文很多建议。

0

评论区