大规模集群下Hadoop NameNode如何承载每秒上千次的高并发访问

欢迎关注我的公众号:石杉的架构笔记(ID:shishan100)node

周一至五早8点半!精品技术文章准时送上!面试


往期文章
安全



目录

1、写在前面
分布式

2、问题源起微服务

3、HDFS优雅的解决方案:

(1)分段加锁机制+内存双缓冲机制

(2)多线程并发吞吐量的百倍优化

(3)缓冲数据批量刷磁盘+网络优化



1、写在前面


上篇文章咱们已经初步给你们解释了Hadoop HDFS的总体架构原理,相信你们都有了必定的认识和了解。

若是没看过上篇文章的同窗能够看一下:《兄弟,用大白话告诉你小白都能听懂的Hadoop架构原理》这篇文章。

本文咱们来看看,若是大量客户端对NameNode发起高并发(好比每秒上千次)访问来修改元数据,此时NameNode该如何抗住?




2、问题源起


咱们先来分析一下,高并发请求NameNode会遇到什么样的问题。

你们如今都知道了,每次请求NameNode修改一条元数据(好比说申请上传一个文件,那么就须要在内存目录树中加入一个文件),都要写一条edits log,包括两个步骤:

  • 写入本地磁盘。

  • 经过网络传输给JournalNodes集群。

可是若是对Java有必定了解的同窗都该知道多线程并发安全问题吧?

NameNode在写edits log时的第一条原则:

必须保证每条edits log都有一个全局顺序递增的transactionId(简称为txid),这样才能够标识出来一条一条的edits log的前后顺序。

那么若是要保证每条edits log的txid都是递增的,就必须得加锁。

每一个线程修改了元数据,要写一条edits log的时候,都必须按顺序排队获取锁后,才能生成一个递增的txid,表明此次要写的edits log的序号。

好的,那么问题来了,你们看看下面的图。

若是每次都是在一个加锁的代码块里,生成txid,而后写磁盘文件edits log,网络请求写入journalnodes一条edits log,会咋样?




不用说,这个绝对完蛋了!

NameNode自己用多线程接收多个客户端发送过来的并发的请求,结果多个线程竟然修改完内存中的元数据以后,排着队写edits log!

并且你要知道,写本地磁盘 + 网络传输给journalnodes,都是很耗时的啊!性能两大杀手:磁盘写 + 网络写!

若是HDFS的架构真要是这么设计的话,基本上NameNode能承载的每秒的并发数量就不多了,可能就每秒处理几十个并发请求处理撑死了!

3、HDFS优雅的解决方案

因此说,针对这个问题,人家HDFS是作了很多的优化的!

首先你们想一下,既然我们不但愿每一个线程写edits log的时候,串行化排队生成txid + 写磁盘 + 写JournalNode,那么是否是能够搞一个内存缓冲?

也就是说,多个线程能够快速的获取锁,生成txid,而后快速的将edits log写入内存缓冲。

接着就快速的释放锁,让下一个线程继续获取锁后,生成id + 写edits log进入内存缓冲。

而后接下来有一个线程能够将内存中的edits log刷入磁盘,可是在这个过程当中,仍是继续容许其余线程将edits log写入内存缓冲中。

可是这里又有一个问题了,若是针对同一块内存缓冲,同时有人写入,还同时有人读取后写磁盘,那也有问题,由于不能并发读写一块共享内存数据!

因此HDFS在这里采起了double-buffer双缓冲机制来处理!将一块内存缓冲分红两个部分:

  • 其中一个部分能够写入

  • 另一个部分用于读取后写入磁盘和JournalNodes。

你们可能感受文字叙述不太直观,老规矩,我们来一张图,按顺序给你们阐述一下。




(1)分段加锁机制 + 内存双缓冲机制

首先各个线程依次第一次获取锁,生成顺序递增的txid,而后将edits log写入内存双缓冲的区域1,接着就立马第一次释放锁了。

趁着这个空隙,后面的线程就能够再次立马第一次获取锁,而后当即写本身的edits log到内存缓冲。

写内存那么快,可能才耗时几十微妙,接着就立马第一次释放锁了。因此这个并发优化绝对是有效果的,你们有没有感觉到?

接着各个线程竞争第二次获取锁,有线程获取到锁以后,就看看,有没有谁在写磁盘和网络?

若是没有,好,那么这个线程是个幸运儿!直接交换双缓冲的区域1和区域2,接着第二次释放锁。这个过程至关快速,内存里判断几个条件,耗时不了几微秒。

好,到这一步为止,内存缓冲已经被交换了,后面的线程能够立马快速的依次获取锁,而后将edits log写入内存缓冲的区域2,区域1中的数据被锁定了,不能写。

怎么样,是否是又感觉到了一点点多线程并发的优化?

(2)多线程并发吞吐量的百倍优化

接着,以前那个幸运儿线程,将内存缓冲的区域1中的数据读取出来(此时没人写区域1了,都在写区域2),将里面的edtis log都写入磁盘文件,以及经过网络写入JournalNodes集群。

这个过程但是很耗时的!可是不要紧啊,人家作过优化了,在写磁盘和网络的过程当中,是不持有锁的!

所以后面的线程能够噼里啪啦的快速的第一次获取锁后,立马写入内存缓冲的区域2,而后释放锁。

这个时候大量的线程均可以快速的写入内存,没有阻塞和卡顿!

怎么样?并发优化的感受感觉到了没有!

(3)缓冲数据批量刷磁盘 + 网络的优化

那么在幸运儿线程吭哧吭哧把数据写磁盘和网络的过程当中,排在后面的大量线程,快速的第一次获取锁,写内存缓冲区域2,释放锁,以后,这些线程第二次获取到锁后会干吗?

他们会发现有人在写磁盘啊,兄弟们!因此会当即休眠1秒,释放锁。

此时大量的线程并发过来的话,都会在这里快速的第二次获取锁,而后发现有人在写磁盘和网络,快速的释放锁,休眠。

怎么样,这个过程没有人长时间的阻塞其余人吧!由于都会快速的释放锁,因此后面的线程仍是能够迅速的第一次获取锁后写内存缓冲!

again!并发优化的感受感觉到了没有?

并且这时,必定会有不少线程发现,好像以前那个幸运儿线程的txid是排在本身以后的,那么确定就把本身的edits log从缓冲里写入磁盘和网络了。

这些线程甚至都不会休眠等待,直接就会返回后去干别的事情了,压根儿不会卡在这里。这里又感觉到并发的优化没有?

而后那个幸运儿线程写完磁盘和网络以后,就会唤醒以前休眠的那些线程。

那些线程会依次排队再第二次获取锁后进入判断,咦!发现没有人在写磁盘和网络了!

而后就会再判断,有没有排在本身以后的线程已经将本身的edtis log写入磁盘和网络了。

  • 若是有的话,就直接返回了。

  • 没有的话,那么就成为第二个幸运儿线程,交换两块缓冲区,区域1和区域2交换一下。

  • 而后释放锁,本身开始吭哧吭哧的将区域2的数据写入磁盘和网络。

可是这个时候没有关系啊,后面的线程若是要写edits log的,仍是能够第一次获取锁后立马写内存缓冲再释放锁。以此类推。

4、总结

其实这套机制仍是挺复杂的,涉及到了分段加锁以及内存双缓冲两个机制。

经过这套机制,NameNode保证了多个线程在高并发的修改元数据以后写edits log的时候,不会说一个线程一个线程的写磁盘和网络,那样性能实在太差,并发能力太弱了!

因此经过上述那套复杂的机制,尽最大的努力保证,一个线程能够批量的将一个缓冲中的多条edits log刷入磁盘和网络。

在这个漫长的吭哧吭哧的过程当中,其余的线程能够快速的高并发写入edits log到内存缓冲里,不会阻塞其余的线程写edits log。

因此,正是依靠以上机制,最大限度优化了NameNode处理高并发访问修改元数据的能力!

                                        END

《【性能优化的秘密】Hadoop如何将TB级大文件的上传性能提高上百倍?》敬请期待

若有收获,请帮忙转发,您的鼓励是做者最大的动力,谢谢!

一大波微服务、分布式、高并发、高可用原创系列

文章正在路上,欢迎扫描下方二维码,持续关注:


石杉的架构笔记(id:shishan100)

十余年BAT架构经验倾囊相授