爱有来生剧照:map的环形内存缓冲区

来源:百度文库 编辑:中财网 时间:2024/04/30 03:07:06

map的环形内存缓冲区

    博客分类:
  • hadoop源码解读
HadoopMapreduceApache工作 hadoop在执行MapReduce任务时,在map阶段,map函数产生的输出,并不是直接写入磁盘的。为了提高效率,它将输出结果先写入到内存中(即环形内存缓冲区,默认大小100M),再从缓冲区(溢)写入磁盘。

下面我们就来看看这段代码。

1、找到环形内存缓冲区

在运行job时,有条输出:
09/04/07 12:34:35 INFO mapred.MapTask: io.sort.mb = 100

上面的io.sort.mb,即map环形内存缓冲区的大小。

在org.apache.hadoop.mapred.MapTask中的第764行找到“io.sort.mb”

第781行:
Java代码  
  1. kvbuffer = new byte[maxMemUsage - recordCapacity];  

kvbuffer是在第715行定义的:
Java代码  
  1. private byte[] kvbuffer;           // main output buffer  

看,这个内存缓冲区竟然是个byte数组!!


2、什么时候溢写到磁盘的?
Java代码  
  1. 第762行:  
  2.     final float spillper = job.getFloat("io.sort.spill.percent",(float)0.8);  
  3.     // 溢写比:默认是0.8,就是说,缓存区80%满了的时候,就要将数据从内存溢写到磁盘了  
  4.   
  5.   
  6. 这100M,还分成2块:数据缓存和记录缓存  
  7.     第707行:  
  8.     private final int[] kvoffsets;     // indices into kvindices  
  9.     // 这个int型的数组就是记录缓存  
  10.   
  11.   
  12. 第941行:  
  13.   
  14.      // sort by key  
  15.       return comparator.compare(kvbuffer,  
  16.           kvindices[ii + KEYSTART],  
  17.           kvindices[ii + VALSTART] - kvindices[ii + KEYSTART],  
  18.           kvbuffer,  
  19.           kvindices[ij + KEYSTART],  
  20.           kvindices[ij + VALSTART] - kvindices[ij + KEYSTART]);  
  21.     // 在内存缓冲区中按key进行排序  
综上,溢写发生在:
1) 溢写比设置了<1的值,并且该值到了的时候
2) 溢写比为1,缓存满了的时候


3、缓冲区怎么成环形的?


答:通过折行。

----------------------------------
“折行写”

Java代码  
  1. 第1038行:  
  2.     boolean buffull = false; // 缓存是否满了  
  3.                  // 这里的“满”,有2种情况:  
  4.                     1) bufindex + len > bufvoid  
  5.                         // 就是说,达到了末尾  
  6.                         // 但是这种情况,可能不是真的满了  
  7.                         // 因为,在数组0-bufstart之间,可能还有很大的空置空间  
  8.                     2) bufindex + len > bufstart  
  9.                         // 由于缓冲区已经成“环”,这种情况,是真的满了。  
  10. 第1039行:  
  11.     boolean wrap = false;   // 是否需要“折行写”  
  12.   
  13.                 // “折行写”的条件:  
  14.                     1) bufstart <= bufend && bufend <= bufindex  
  15.                         // buffer是一段连续的区域,还没有形成“环”  
  16.                     2) (bufvoid - bufindex) + bufstart > len  
  17.                         // 数组末尾,加上数组开头的空间能够存储当前数据  
  18.   
  19.                 // 真正执行“折行写”的代码(Line1101-1107):  
  20.   
  21.                 if (buffull) { // 这里满足上面第1种buffull = true的条件,否则  
  22.                         // 将先溢写至磁盘后,再到达这里。  
  23.                       final int gaplen = bufvoid - bufindex;  
  24.                       System.arraycopy(b, off, kvbuffer, bufindex, gaplen);  
  25.                       len -= gaplen;  
  26.                       off += gaplen;  
  27.                       bufindex = 0;  
  28.                     }  
  29.                       

----------------------------------
“折行写”后的reset

Java代码  
  1. Line996-1014的reset()方法:  
  2.   
  3.     这个方法被调用的地点:第895行,  
  4.   
  5.             第893行,在collect方法中:  
  6.             if (bufindex < keystart) {  
  7.               // wrapped the key; reset required  
  8.               bb.reset();  
  9.               keystart = 0;  
  10.             }  
  11.   
  12.         // 如果key被“折写”成2段,则reset缓冲区  
  13.         // 这时候:一个key有一半写在了数组末尾,另一半写在了数组列头时候  
  14.   
  15.   
  16.     这个方法被调用的时候(bufindex < keystart == true):  
  17.   
  18.         一定是,序列化后的key被写入缓存区,而且是被wrap(折行)写入的!  
  19.   
  20.   
  21.     这个方法里的解释:  
  22.   
  23.       protected synchronized void reset() throws IOException {  
  24.         // spillLock unnecessary; If spill wraps, then  
  25.         // bufindex < bufstart < bufend so contention is impossible  
  26.         // a stale value for bufstart does not affect correctness, since  
  27.         // we can only get false negatives that force the more  
  28.         // conservative path  
  29.         int headbytelen = bufvoid - bufmark;  
  30.         // headbytelen的意思是:被“折行”写入的key的前段部分  
  31.         // bufvoid是缓冲区的右边界  
  32.         // bufmark是缓冲区中上次存值后的右边界  
  33.         // bufvoid - bufmark :就是被“折行”写入的key的前半段  
  34.         bufvoid = bufmark;  
  35.         if (bufindex + headbytelen < bufstart) {  
  36.       // 基本上这个条件成立的可能性比较大,  
  37.       // 它的意思是说:整个key的长度(前半段的长度是headbytelen,存在缓冲区最后面;后半段的长度是bufindex,存在缓冲区的最前面)在bufstart之间的空间能存得下  
  38.       // 那么接下来的2行代码:把这个key的两端,都移到缓冲区的最前面!  
  39.           System.arraycopy(kvbuffer, 0, kvbuffer, headbytelen, bufindex);  
  40.           System.arraycopy(kvbuffer, bufvoid, kvbuffer, 0, headbytelen);  
  41.           bufindex += headbytelen;  
  42.         } else {  
  43.       // 这种情况真的很难达到:要求溢写比(io.sort.spill.percent)为1,并且bufstart很靠近0的时候  
  44.       // 这种情况是:buffer真的很满了(bufstart-bufindex
  45.           byte[] keytmp = new byte[bufindex];  
  46.           System.arraycopy(kvbuffer, 0, keytmp, 0, bufindex);  
  47.           bufindex = 0;  
  48.       // 把这个key分2次写入out  
  49.       // 也就是说:  
  50.       //     1) 这个key,先从kvbuffer缓存中删除  
  51.       //     2) 接下来,应该是将缓存中数据溢写到磁盘上  
  52.       //     3) out中的这个key再次写入清空后的缓存里!  
  53.       //        估计在清空缓存前,这个都会被阻塞。  
  54.           out.write(kvbuffer, bufmark, headbytelen);  
  55.           out.write(keytmp);  
  56.         }  
  57.       }  
  58.     }  


4、“成环”示意图

上面的代码一定看的眼花缭乱吧?呵呵,我一开始看的时候,也被弄得很糊涂。请看下面的示意图,就会对这个环形缓冲有个好的理解了。


5、“溢写”过程
Java代码  
  1. bufend = bufmark; // 在startSpill方法中  
  2. sortAndSpill();  
  3. bufstart = bufend;  

即:
溢写完毕后,原来的bufmark变成了bufstart


6、缓存为什么要设计成环形的?有什么好处?

答:使输入输出并行工作,即“写缓冲”可以和“溢写”并行。“溢写”工作由单独的线程来做。

Java代码  
  1. 解读“溢写”代码:  
  2. bufend = bufmark; // 在startSpill方法中  
  3. sortAndSpill();  
  4. bufstart = bufend;  
  5.   
  6. 1)溢写前:  
  7.     bufend = bufmark;  
  8.   
  9. 则溢写的范围是:从bufstart到bufend。  
  10.   
  11. 2)在溢写的过程中,bufmark还是有可能增长的!  
  12. 3)溢写完毕,bufstart = bufend;  



*** THE END***