shuffle中环形缓冲区使用于map shuffle阶段存放map的缓存数据,当缓冲区的数据达到一定比率(80%)就会将缓冲区的数据刷写到磁盘文件中,在刷盘之前,会对数据分区、排序、合并,对缓冲区的操作是边写入边读取的过程,二者互不影响,提升写入的速率,读写过程就是一个生产者、消费者模式,生产者向环形缓冲区中写入数据,消费者从环形缓冲区中读取数据并且写入磁盘。环形缓冲区在物理上是一组连续的空间地址,在逻辑上是首尾相连的环形空间,通过使用下标实现环形,初始read=write=index=0,read下一个读取位置,write下一次写入位置,index 刷盘的结束位置,每一次写入write ,当缓存达到一定比率,执行读取线程开启,将index=write,那么将读取read~index-1区间的数据写入磁盘,此时write继续接受数据写入,当数据读取完read=index,继续进行下一次读取操作,需要注意当下标达到临界点即缓冲区数组的大小时需要进行下标索引的转换,例如当read=array.length,需要read=0。
缓冲区的读写请求使用java Lock(spillLock)与Condition(spillDone、spillReady)来控制, 使用spillLock锁控制资源的并发访问,spillDone、spillReady两个信号灯,控制写入读取操作。
- 初始 spillThread开启,spillDone.signal()唤醒写入线程,spillReady.await() 表示等待spill, spill线程阻塞;
2. 当写入写入缓冲区的数据达到一定比率,spillReady.signal() 唤醒spill线程, 后台执行spill操作。这个过程不会阻塞数据的继续写入,若spill过慢,在此期 间写入过快,那么就是调用spillDone.wait(), 等待spill结束调用spillDone.signal()唤醒写入线程。
当bufferRemaining存放下一个数据后<=0, 那么就要执行spill操作,调用startSpill方法。
在startSpill方法中调用spillReady.signal(), 唤醒spill线程
如果没有足够内存进行数据写入,那么就调用spillDone.wait(), 等待spill结束调用spillDone.single().
缓冲区包含两部分数据:元数据,包含分区信息、key/value位置、value的长度;真实数据,包含真实的kv信息,元数据与真实数据都存放在唤醒缓冲区中,分别占据着不同的下标范围,spill过程会读取元数据信息,将同一个分区的数据进行排序、合并写入文件,并且同时将元数据写入SpillRecord对象,包含的分区信息、分区数据在数据文件中的起止位置,当内存不够时,会将索引文件写入磁盘中,在最后的文件合并中,根据SpillRecord对象或者索引文件获取对应的分区数据进行并归排序、合并写入本地磁盘文件,同时生成索引文件。
在java 中BlockingQueue 阻塞队列也使用了相同的生产者、消费者模式,items数组存放实际的数据,takeIndex指向下一个读取位置,putIndex指向下一个存放位置,notEmpty信号控制读线程,notFull信号控制写线程。
数据写入:
数据读取: