我们在HDFS - 什么是元数据中提到了,元数据会存在与内存和磁盘中,内存为了提高响应速度,磁盘是为了持久化保证数据的安全,但是写磁盘的速度相对于内存是慢了几个数量级的,如果NameNode每次都要把数据落到磁盘上那是没办法处理那么多客户端的请求的,所以NameNode用了双缓冲机制以及分段加锁。
所谓双缓冲就是定义了两个内存块,一个是bufCurrent,用于当前写入元数据的,一个是bufReady,用于写入磁盘的,两个内存块都是512k。
image.png

第一把锁

线程1会先获取锁,然后会看看当前有没有在交换缓存(这里通过isAutoSyncScheduled来判断),如果没有,就去获取一个全局唯一的事务ID,这个ID是递增的。拿到事务ID后,就开始写入bufCurrent,写完后判断bufCurrent是否超过了512k,如果没有,线程1的流程就结束了(后面结束的背景是白色的)。
由于线程2,3,4在线程1没释放锁的时候,都会一直等到,如果线程2,3,4拿到了锁,也会继续上面线程1的操作。
由于这个锁里的操作都是基于内存的,所以速度就会非常块。
image.png
我们假设线程4写完后,发现bufCurrent内存超过了512k,此时就会把isAutoSyncScheduled设置为true,说明要开始交换内存了,其他线程就不能做以上的操作了。这里的bufCurrent就是线程1,2,3,4写入的数据。
image.png
这个时候,线程5进来并拿到锁,我们给拿锁的进程颜色标深一点,发现要开始交换内存了,于是他就是wait状态。
image.png

第二把锁

我们假设接下来获取到锁的是线程4。他发现此时并没有其他线程交换内存(这里通过isSyncRunning判断),于是他就开始bufCurrent和bufReady的内存进行了交换,交换后,bufCurrent的数据就清空了。在这里还会把isSyncRunning设置true,然后isAutoSyncScheduled设置为false,最后就唤醒wait的线程。
image.png
线程4唤醒其他线程后,他就开始把bufReady的数据写入磁盘,这个操作是很耗时的,所以并没有加锁,但是他是运行状态,所以下图标记为绿色。
这里的bufReady就是线程1,2,3,4写入的数据。
此时是线程5获取了锁,发现内存交换完毕了,开始往bufCurrent写数据。
image.png
此后线程6拿到了锁,此时bufCurrent又超过了512k了,就会把isAutoSyncScheduled设置为true,说明开始交换内存,其他线程就不能往bufCurrent写入数据了。但是他发现isSyncRunning为true,说明有其他线程在写磁盘了,所以他就开始wait。
image.png

第三把锁

此时线程4写完了磁盘,然后他获取到锁,就会把synctxid更改为自己的事务ID,然后赋值isSyncRunning为false,说明磁盘写入完成了。最后唤醒其他wait的线程。
image.png
此时线程6重新拿到了锁,他发现isSyncRunning为false了,那就是说明其他线程已经把自己的内容刷入磁盘了,所以线程6开始了上面线程4的操作,交换内存,写入磁盘。

总结

第一把锁,主要是判断isAutoSyncScheduled以及对isAutoSyncScheduled的赋值,这个主要是说明bufCurrent和bufReady开始交换内存了。
第二把锁,主要是判断isSyncRunning以及对isSyncRunning和isAutoSyncScheduled的赋值。isSyncRunning是用来判断是否在写磁盘,isAutoSyncScheduled用来判断是否在交换内存,如果在交换,就不能写入bufCurrent,如果在写磁盘,那就不能写磁盘。
第三把锁,赋值isSyncRunning,说明磁盘写入完成。
这期间最耗时的操作并没有加锁,其他内存操作的加锁,但是速度比较快,采用在这种分段加锁的方式和双缓冲机制,大大提高了性能。


大军
847 声望183 粉丝

学而不思则罔,思而不学则殆