MapReduce源碼註釋-MapTask.MapOutputBuffer.Buffer

    public class Buffer extends OutputStream {
      private final byte[] scratch = new byte[1];
      public synchronized void write(int v) throws IOException {
        scratch[0] = (byte)v;
        write(scratch, 0, 1);
      }
      /**Attempt to write a sequence of bytes to the collection buffer. This method will block if the spill thread is running and it cannot write. */
      public synchronized void write(byte b[], int off, int len) throws IOException {
        boolean buffull = false;		// 緩衝區連續寫是否寫滿標誌
        boolean wrap = false;		// 緩衝區非連續情況下有足夠寫空間標誌
        spillLock.lock();
        try {
          do {
            // sufficient buffer space? 是否有足夠的緩衝區
            // 連續的空間, 在還沒開始寫入len個字節到kvbuffer時, 要判斷如果寫入len個字節後, 會不會使緩衝區滿, 如果滿需要spill
            // 1. bufstart=bufend<bufindex 正常寫入緩衝區, 由於還沒有開始spill, bufstart=bufend. 連續區間, 所以bufstart=bufend<bufindex
            // 2. bufstart < bufend < bufindex 正在溢寫(bufstart<bufend=bufindex)的同時又有數據寫入緩衝區(bufstart<bufend<bufindex)
            if (bufstart <= bufend && bufend <= bufindex) {  // bufindex不會在bufend前面. 即bufindex不會寫完緩衝區後,再繞到bufend前面
              buffull = bufindex + len > bufvoid;	// 一有數據寫入kvbuffer,bufindex就後移. 判斷再寫入len個字節後,是否到達緩衝區尾部
              wrap = (bufvoid - bufindex) + bufstart > len; // 緩衝區中非連續性區間包括兩部分, buindex剩餘的和bufstart之前的
            } else {	// 非連續的空間(中間部分爲即將寫入的數據): 基本條件爲bufindex < bufstart. 
              // bufindex <= bufstart <= bufend 寫入緩衝區尾部後不夠, 需要寫到緩衝區頭部bufindex<bufstart=bufend
              // bufend <= bufindex <= bufstart	溢寫bufend=bufindex<bufstart,同時又有數據寫入bufend<bufindex<bufstart
              wrap = false;  // 非連續情況下總設置爲false,一旦緩衝區即將滿就必須溢寫. 這樣在複製到kvbuffer時,保證不會執行if(buffull)的邏輯
              buffull = bufindex + len > bufstart;	// bufindex現在已經重頭開始了,再寫入len,不能超過bufstart.如果超過則說明緩衝區寫滿了
            }
            if (kvstart == kvend) {		// spill thread not running 溢寫線程還沒運行. 只要kvstart!=kvend,纔開始將緩衝區數據寫入磁盤
              if (kvend != kvindex) {	// we have records we can spill 還沒溢寫時, 沒有賦值kvend=kvindex. 數組kvoffsets中有記錄
            	// bufindex>bufend, 連續的空間, 判斷是否需要溢寫. [bufend,bufindex)之間的爲已經寫入的數據,判斷是否超過80%的限制
                final boolean bufsoftlimit = (bufindex > bufend) ? bufindex - bufend > softBufferLimit
                // 非連續空間分成兩段,bufvoid-bufend後面一段, bufindex爲前面一段. 兩者之和即爲已寫入的數據bufvoid-bufend+bufindex>limit
                  : bufend - bufindex < bufvoid - softBufferLimit;
                if (bufsoftlimit || (buffull && !wrap)) {
                  LOG.info("Spilling map output: buffer full= " + bufsoftlimit);
                  startSpill();			// 緩衝區中已使用的內存利用率超過80%, 或者在寫入之前緩衝區已經滿了, 則立即spill
                }
              } else if (buffull && !wrap) {  // kvoffsets數組中沒有記錄, 但是kvbuffer空間還是不夠當前寫入的數據
                // We have no buffered records, and this record is too large to write into kvbuffer. We must spill it directly from collect
                final int size = ((bufend <= bufindex) ? bufindex - bufend : (bufvoid - bufend) + bufindex) + len;  // 已使用+即將寫入的len
                bufstart = bufend = bufindex = bufmark = 0;
                kvstart = kvend = kvindex = 0;
                bufvoid = kvbuffer.length;
                throw new MapBufferTooSmallException(size + " bytes");
              }
            }
            if (buffull && !wrap) {		// 如果空間不足同時spill在運行, 等待spillDone
              try {
                while (kvstart != kvend) {
                  reporter.progress();
                  spillDone.await();
                }
              } catch (InterruptedException e) {throw (IOException)new IOException("Buffer interrupted while waiting for the writer").initCause(e);}
            }
          } while (buffull && !wrap);
        } finally {
          spillLock.unlock();
        }
        // here, we know that we have sufficient space to write 緩衝區有足夠的空間用來寫入數據
        // 前面判斷buffull有兩種情況即連續性和非連續性. 非連續性一定不會執行下面的if語句. 
        // 因爲非連續性設置wrap=false,如果緩衝區滿的話, 一定會執行spill溢寫. 
        // 否則如果非連續性執行下面的buffull邏輯. gaplen=bufvoid-bufindex就有問題.
        if (buffull) {
          final int gaplen = bufvoid - bufindex;   // 寫到緩衝區尾部
          System.arraycopy(b, off, kvbuffer, bufindex, gaplen);
          len -= gaplen;	// 剩餘部分len=len-gaplen會寫到緩衝區頭部
          off += gaplen;	// 要寫入數據的起始位置也要相應變化
          bufindex = 0;	// 從緩衝區頭部開始寫
        }
        System.arraycopy(b, off, kvbuffer, bufindex, len);
        bufindex += len;	// 寫入len個字節, 緩衝區的bufindex就後移len個字節
      }
    }

 

BlockBuffer.reset

      protected synchronized void reset() throws IOException {
        // spillLock unnecessary; If spill wraps, then
        // bufindex < bufstart < bufend so contention is impossible
        // a stale value for bufstart does not affect correctness, since
        // we can only get false negatives that force the more conservative path
    	// 寫入key時,發生跨界現象. 即寫入某個key時,緩衝區尾部剩餘空間不足以容納整個key值,因此需要將key值分開存儲,其中一部分存到緩衝區末尾,->key上半部分①
    	// 另一部分存到緩衝區頭部->key下半部分②. 由於key是排序的關鍵字,需要保證連續性. 因此需要將跨界的key值重新存儲到緩衝區的頭部位置.
    	// 發生跨界的key在緩衝區末尾的長度=bufvoid-bufmark. 其中bufmark爲最後(上一次)寫入的一個完整的key/value的結束位置 
        int headbytelen = bufvoid - bufmark;  	
        // 將尾部key插入到頭部之後, bufvoid要設置爲bufmark. 那麼bufvoid開始,長度爲headbytelen的就是key的尾部部分了
        bufvoid = bufmark;
        // 緩衝區前半段有足夠的空間容納整個key值. 即將尾部的key插入到頭部後, 不會使得這一個key超過bufstart
        if (bufindex + headbytelen < bufstart) {
          // bufindex爲當前緩衝區的位置. 不管寫入key或者value, bufindex都表示下一個可寫的初始位置
          // [0, bufindex]在調整之前的頭部① -> [headbytelen, headbytelen+bufindex] 即將原先頭部後移headbytelen用來準備第二次拷貝
          System.arraycopy(kvbuffer, 0, kvbuffer, headbytelen, bufindex);
          // [bufvoid, bufvoid+headbytelen]調整之前的尾部② -> [0, headbytelen] 將尾部②插入到原先的頭部①
          System.arraycopy(kvbuffer, bufvoid, kvbuffer, 0, headbytelen);
          bufindex += headbytelen;  // bufindex也要跟隨移動到原先頭部的下一個位置
        } else {   // 緩衝區前半段沒有足夠的空間容納整個key值. 在將key值移動到緩衝區開始位置時觸發一次spill操作
          byte[] keytmp = new byte[bufindex];	// bufindex爲在未調整之前的緩衝區頭部
          System.arraycopy(kvbuffer, 0, keytmp, 0, bufindex);  // 將原先的頭部②複製到臨時緩衝區中
          bufindex = 0;  	// 重置緩衝區
          out.write(kvbuffer, bufmark, headbytelen);	// 首先將[bufmark, bufmark+headbytelen]即尾部①寫入輸出流
          out.write(keytmp);// 然後將緩衝區頭部,即key的下半部分也寫入輸出流	
        }
      }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章