背景
-
最近学习腾讯/美团关于如何拆namenode大锁的方案,总体方案上大概相似,特此进行记录。
-
笔者目前在hdfs使用和原理上相对熟悉,但是源码层面的实现原理阅读不多,也经常关注社区关于分布式存储的动态,希望在不久能看懂各位大佬的优秀思路和实现。
-
这是一篇关于namenode锁优化的常更文章,非全部原创。内容来源于笔者日常阅读和学习。
全局锁机制
hdfs HA部署架构
-
从下图hdfs HA部署架构图中可看出是比较复杂的。部署上要求至少要引入三个 zookeeper 节点,三个 JournalNode 节点,两个 ZKFC 节点。文件系统的 inode 信息和 block 信息以及 block 的 location 信息全部在 NameNode 的内存中维护,这使得 NameNode 对内存的要求非常高,需要定制大内存的机器才能承载更大的元数据量。
hdfs部署架构
hdfs 全局锁的分类
- 首先关于锁的类型,在NameNode使用了JDK提供的可重入读写锁,一句话概括就是:读锁并行写锁排它。
- 全局锁管理的主要数据结构大概分为下列三种
- 目录树:文件系统的全局目录视图。获取目录树上任一节点的信息必须先拿到全局读锁;目录树上任一节点新增、删除、修改都必须先拿到全局写锁
- 数据块集合:文件系统的全量数据信息。获取其中任一数据块信息必须先拿到全局读锁;新增、删除,修改都必须先拿到全局写锁。
-
集群信息:HDFS集群节点信息的集合。获取节点信息等必须先拿到全局读锁;注册,下线或者变更节点信息请求处理时必须先拿到全局写锁
NN全局锁的作用范围
hdfs 锁的类型
- 从NameNode的核心处理逻辑路径上有两把锁:FSNamesystemLock和FSEditLogLock,其中FSNamesystemLock类型采用可重入读写锁,也是待优化的全局锁,而FSEditLogLock则采用Synchronized排它锁。
- 当一个client请求如果涉及到对数据写操作,则需要持有的锁流程是:(1)获取全局锁(FSNamesystemLock)入口:外部RPC请求从IPC层进入NameNode和内部线程请求;(2)获取局部锁(FSEditLogLock)入口:主要来源外部RPC请求对元数据的写操作;
- 以RPC请求mkdir为例,其步骤如下:
- (1)RPC请求从IPC层进入NameNode;
- (2)获取全局写锁(FSNamesystemLock#writeLock#lock),如果持有读锁或者写锁的请求正在被处理,排队等待;
- (3)更新内存目录树结构;
- (4)释放全局写锁(FSNamesystemLock#writeLock#unlock);
- (5)获取EditLog排它锁;
- (6)写EditLog;
- (7)释放EditLog排它锁;
- (8)通过IPC层将结果返回客户端;
关于hdfs两把锁的拆分
- 在这里笔者思考下为何不把EditLog读写锁也并在FSNamesystemLock锁内执行,这种不更符合全局一把锁的含义?当前这样会带来很差的性能瓶颈,也不符合锁越少越好的软件设计初衷。但是为啥性能会查很多?我觉得主要与EditLog的流程相关。
- 我们都知道NameNode修改一条元数据后需要写一条edits log并保证都有一个全局顺序递增(加锁)的transactionId,再写本地磁盘 + 网络传输给journalnodes。磁盘写 /网络写都是巨大的性能瓶颈,因此分开成两个锁本身就有很强的必要性,关于写editlog的过程hadoop也做了很大的优化。
关于拆锁的过程
关于拆锁的必要性讨论
- 首先先明确下拆锁的必要性,得出的结论必须拆。
- 在这里也先明确下拆锁是一件很复杂很难的事情。
- 为什么要拆?首先全局上看主要是分离的两把锁,在通常情况下,FSNamesystemLock锁范围要远大于FSEditLogLock锁范围。考虑负载较高的大规模集群,按照9:1读写比预估,只有10%请求需要同时获取FSNamesystemLock和FSEditLogLock,但是100%请求需要获取全局锁FSNamesystemLock。而EditLog写入性能可以依赖新型硬件IO性能的提高,但全局锁FSNamesystemLock由于一定会请求到,那么在集群规模增加和负载增高后必然成为瓶颈!
关于腾讯分享的拆锁实践。
- 首先是确定拆锁的粒度,腾讯和美团一样,都是对到INODE级别进行加锁,但是这里也需要考虑到如果每个hdfs handle同时处理很深路径的请求,假设每个锁100b大小的话,也会占据很大的内容空间
- 针对上述问题腾讯这边借鉴了 Alluxio 2.0来引入的 LockPool 的概念。也就是有一个锁资源池,每个Inode 不再关联(新增)一个 Lock 了,而是需要 Lock 加锁的时候,就去资源池里申请锁,同时引用计数会增加,用完了 unlock 掉的时候,引用计数会减少。通过这样来减少锁的个数
- 关于拆锁后最重要的问题都是如何预防死锁和数据错乱
- 预防死锁的话,基本上现有方案都是采用自上而下的加锁方式,其中需要单独把rename等操作拎出来重点考虑。
- 而数据错乱这边则查询类的一般全链路加读锁,这类请求一般也占到极大比例。属性修改类的只对最深目录加写锁,而增删类算是最麻烦的则需要考虑读写锁的结合了。
-
总体上拆锁后关于死锁和数据正确性需要考虑的问题,如下图所示。
NN拆锁问题关注点
不拆锁如何优化?(可执行的方案)
- 如HDFS-10872 添加了NameNode锁住时间对应的Metrics。NameNode的锁队列长度堆积过高的时候,可以查看到NN全局锁对应的锁住时间和对某些锁占用时间过长的情况,然后再进行具体的分析,对很多锁优化对细节很有帮助。
网友评论