1. 程式人生 > >HBase原始碼分析之KeyValue

HBase原始碼分析之KeyValue

        HBase內部,單元格Cell的實現為KeyValue,它是HBase某行資料的某個單元格在記憶體中的組織形式,由Key Length、Value Length、Key、Value四大部分組成。其中,Key又由Row Length、Row、Column Family Length、Column Family、Column Qualifier、Time Stamp、Key Type七部分組成。在HBase1.0.2版本中,它的結構如圖:


        從左到右,依次為:

        1、Key Length:儲存Key的長度,佔4B;

        2、Value Length:儲存Value的長度,佔4B;

        3、Key:由Row Length、Row、Column Family Length、Column Family

              3.1、Row Length:儲存Row的長度,即rowkey的長度,佔2B;

              3.2、Row:儲存Row實際內容,即Rowkey,其大小為Row Length;

              3.3、Column Family Length:儲存列簇Column Family的長度,佔1B;

              3.4、Column Family:儲存Column Family實際內容,大小為Column Family Length;

              3.5、Column Qualifier:儲存Column Qualifier對應的資料,既然key中其他所有欄位的大小都知道了,整個key的大小也知道了,那麼這個Column Qualifier大小也是明確的了,無需再儲存其length;

              3.6、Time Stamp:儲存時間戳Time Stamp,佔8B;

              3.7、Key Type:儲存Key型別Key Type,佔1B,Type分為Put、Delete、DeleteColumn、DeleteFamilyVersion、DeleteFamily等型別,標記這個KeyValue的型別;

        4、Value:儲存單元格Cell對應的實際的值Value。

        下面,我們看下HBase中KeyValue是如何實現的。在KeyValue中,有三個十分重要的變數,如下:

  // KeyValue core instance fields.
  // KeyValyeh核心例項儲存域
  
  // KeyValue相關的不變byte[]陣列,儲存KeyValue實際內容
  protected byte [] bytes = null;  // an immutable byte array that contains the KV
  // KeyValue在陣列bytes的起始位置
  protected int offset = 0;  // offset into bytes buffer KV starts at
  // KeyValue在陣列bytes自起始位置offset後的長度
  protected int length = 0;  // length of the KV starting from offset.
        KeyValue內容是儲存在byte[]陣列bytes中的,它是一個不變的byte[]陣列,而儲存的起始位置與長度,則分別由offset和length標識。

        下面,我們看下KeyValue中獲取Key Length、Value Length、Row Length、Column Family、Value等等相關欄位的方法,來驗證下我們上面羅列出的KeyValue結構。

        1、Key Length

  /**
   * @return Length of key portion.
   */
  public int getKeyLength() {
	  
    // 從KeyValue底層byte[]陣列bytes中位置offset開始,獲取一個int,也就是4B
    return Bytes.toInt(this.bytes, this.offset);
  }
        getKeyLength()方法用於獲取KeyValue中Key長度Key Length,它從KeyValue底層byte[]陣列bytes中位置offset開始,獲取一個int,也就是4B,這也就驗證了我們上面說的,KeyValue中第一個是Key Length,大小為4B。

        2、Value Length

  /**
   * @return Value length
   */
  @Override
  public int getValueLength() {
	  
    // 從KeyValue底層byte[]陣列bytes中offset+4開始,獲取一個int,也就是4B
    // 也就是說,key length後緊跟著4B是value length
    int vlength = Bytes.toInt(this.bytes, this.offset + Bytes.SIZEOF_INT);
    return vlength;
  }
        getValueLength()方法用於獲取KeyValue中Value長度Value Length,它從KeyValue底層byte[]陣列bytes中offset+4開始,獲取一個int,也就是4B,這也就驗證了我們上面說的Key Length後緊跟著4B是Value Length。

        3、Key起始位置

  /**
   * @return Key offset in backing buffer..
   */
  public int getKeyOffset() {
	  
	// ROW_OFFSET為key length、value length之後的位置
    return this.offset + ROW_OFFSET;
  }
        ROW_OFFSET為Key Length、Value Length之後的位置,定義如下:
  // How far into the key the row starts at. First thing to read is the short
  // that says how long the row is.
  public static final int ROW_OFFSET =
    Bytes.SIZEOF_INT /*keylength*/ +
    Bytes.SIZEOF_INT /*valuelength*/;
        getKeyOffset()方法用於獲取KeyValue中Key的起始位置,它的取值為整個KeyValue的起始位置offset加上ROW_OFFSET,而ROW_OFFSET為Key Length和Value Length所佔大小,這也就驗證了Key Length和Value Length之後就是Key。

        4、Value起始位置

  /**
   * @return the value offset
   */
  @Override
  public int getValueOffset() {
	  
	// Key的起始位置,再加上Key的長度,就是Value的起始位置
    int voffset = getKeyOffset() + getKeyLength();
    return voffset;
  }
        getValueOffset()方法用於獲取KeyValue中Value的起始位置,它的值為通過getKeyOffset()方法獲取的Key的起始位置,再加上通過getKeyLength()方法獲取的Key的長度,這也就驗證了KeyValue中繼Key Length、Value Length、Key之後,就是Value。

        5、Row Length

  /**
   * @return Row length
   */
  @Override
  public short getRowLength() {
	  
	// 從KeyValue底層byte[]陣列bytes中key起始位置開始,獲取一個short,也就是2B
	// getKeyOffset()起始時獲取的key length加value length後的位置
	// 也就是說,key length後緊跟著4B是value length,而value length後就是key的開始,
	// 而key前面的2B是row length
    return Bytes.toShort(this.bytes, getKeyOffset());
  }
        getRowLength()方法用於獲取KeyValue中Row長度Row Length,它從KeyValue底層byte[]陣列bytes中key起始位置開始,獲取一個short,也就是2B,這也就證明了Row Length是Key中第一個欄位。

        5、Row起始位置

  /**
   * @return Row offset
   */
  @Override
  public int getRowOffset() {
	  
    // key的起始位置再加2B,即row length之後就是row
    return getKeyOffset() + Bytes.SIZEOF_SHORT;
  }
        getRowOffset()方法用於獲取KeyValue中Row的起始位置,它的取值為Key的起始位置再加2B,即Row Length之後就是Row,與上面所講一致!

        6、Row

  /**
   * Primarily for use client-side.  Returns the row of this KeyValue in a new
   * byte array.<p>
   *
   * If server-side, use {@link #getBuffer()} with appropriate offsets and
   * lengths instead.
   * @return Row in a new byte array.
   */
  @Deprecated // use CellUtil.getRowArray()
  public byte [] getRow() {
    return CellUtil.cloneRow(this);
  }
        getRow()方法用於獲取Row內容,它通過CellUtil的cloneRow()方法,傳入本身KeyValue例項,返回一個byte[],而cloneRow()方法如下:
  public static byte[] cloneRow(Cell cell){
	  
	// output為一個大小為row length的byte[]陣列
    byte[] output = new byte[cell.getRowLength()];
    
    // 將row從cell中copy至output
    copyRowTo(cell, output, 0);
    return output;
  }
        可以看到,先構造一個Row Length大小的byte[]陣列output,這也就意味著Row的大小是由之前的Row Length對應的值確定的。然後,呼叫copyRowTo()方法,將KeyValue中Row儲存的內容拷貝至output陣列並返回。而copyRowTo()方法,將cell(也就是KeyValue)中byte[]陣列bytes,從Row的起始位置Row Offset處開始,拷貝到目標byte[]陣列destination(也就是output),從0開始,拷貝資料的長度為Row Length,也就是會填滿整個destination(output),程式碼如下:
  public static int copyRowTo(Cell cell, byte[] destination, int destinationOffset) {
    
	// 將cell中byte[]陣列bytes,從row offset處開始,拷貝到目標byte[]陣列destination,從0開始,拷貝資料的長度為row length,
	// 也就是會填滿整個destination
	System.arraycopy(cell.getRowArray(), cell.getRowOffset(), destination, destinationOffset,
      cell.getRowLength());
	
	// 返回資料拷貝的終止點
    return destinationOffset + cell.getRowLength();
  }
        7、Family起始位置
  /**
   * @return Family offset
   */
  @Override
  public int getFamilyOffset() {
	  
	
    return getFamilyOffset(getRowLength());
  }

  /**
   * @return Family offset
   */
  private int getFamilyOffset(int rlength) {
	  
	// 獲取family的起始位置:整個KeyValue起始位置offset + ROW_OFFSET(Key Length + Value Length) + 2B(Row Length) + 實際Row大小rlength + 1B(Family Length)
    return this.offset + ROW_OFFSET + Bytes.SIZEOF_SHORT + rlength + Bytes.SIZEOF_BYTE;
  }
        getFamilyOffset()方法用於獲取KeyValue中Family的起始位置,它是整個KeyValue起始位置offset,加上ROW_OFFSET,也就是Key Length、Value Length所佔大小,然後再加上Row Length所佔大小2B,和通過getRowLength()方法獲取的實際Row大小rlength,最後加上1B,即Family Length所佔大小。這也就說明了,Key中ROw Length、Row之後就是Family Length和Family,而Family Length大小佔1B。

        8、Family Length

  /**
   * @return Family length
   */
  @Override
  public byte getFamilyLength() {
    return getFamilyLength(getFamilyOffset());
  }

  /**
   * @return Family length
   */
  public byte getFamilyLength(int foffset) {
	  
	// family起始位置減1,這個1B就是family length
    return this.bytes[foffset-1];
  }
        getFamilyLength()方法用於獲取KeyValue中Family長度Family Length,它是通過由getFamilyOffset()方法獲取的Family位置減1來獲取的,與上面得到的驗證一致,Family前面1B就是Family Length。

        9、Qualifier起始位置

  /**
   * @return Qualifier offset
   */
  @Override
  public int getQualifierOffset() {
    return getQualifierOffset(getFamilyOffset());
  }

  /**
   * @return Qualifier offset
   */
  private int getQualifierOffset(int foffset) {
	  
	// Family起始位置加上Family長度Family Length
    return foffset + getFamilyLength(foffset);
  }
        getQualifierOffset()方法用於獲取KeyValue中Qualifier的起始位置,它實際上是通過Family的起始位置再加上Family的長度Family Length,這也就說明了Family後就是Qualifier。

        10、Qualifier長度

  /**
   * @return Qualifier length
   */
  @Override
  public int getQualifierLength() {
    return getQualifierLength(getRowLength(),getFamilyLength());
  }

  /**
   * @return Qualifier length
   */
  private int getQualifierLength(int rlength, int flength) {
	// Key長度減去Row長度、Family長度、Row Length長度、Family Length長度、Time Stamp長度、Key Type長度
    return getKeyLength() - (int) getKeyDataStructureSize(rlength, flength, 0);
  }
        getQualifierLength()方法,用於獲取KeyValue中Qualifier長度,KeyValue中並沒有直接儲存Qualifier長度,而是通過Key的總長度減去Key中除Qualifier外其它各部分長度來得到的,實際上是一個計算的過程,為Key長度減去Row長度、Family長度、Row Length長度、Family Length長度、Time Stamp長度、Key Type長度的和。

        11、Timestamp起始位置

  /**
   * @return Timestamp offset
   */
  public int getTimestampOffset() {
    return getTimestampOffset(getKeyLength());
  }

  /**
   * @param keylength Pass if you have it to save on a int creation.
   * @return Timestamp offset
   */
  private int getTimestampOffset(final int keylength) {
	// Key的起始位置加上Key的長度,再減去Time Stamp和Key Type所佔大小
    return getKeyOffset() + keylength - TIMESTAMP_TYPE_SIZE;
  }
        getTimestampOffset()方法用於獲取KeyValue中Time Stamp的起始位置,它是通過Key的起始位置加上Key的長度,再減去Time Stamp和Key Type所佔大小來計算得到的。這意味著,在Key中,Time Stamp處於倒數第二個位置,也就是在Qualifier之後,在Key Type之前,而Key Type則居於最後。
        12、獲取TimeStamp
  /**
   *
   * @return Timestamp
   */
  @Override
  public long getTimestamp() {
    return getTimestamp(getKeyLength());
  }

  /**
   * @param keylength Pass if you have it to save on a int creation.
   * @return Timestamp
   */
  long getTimestamp(final int keylength) {
	  
	// 獲取TimeStamp起始位置tsOffset
    int tsOffset = getTimestampOffset(keylength);
    
    // 從bytes中tsOffset位置開始讀取一個Long,即8B
    return Bytes.toLong(this.bytes, tsOffset);
  }
        getTimestamp()方法是用來獲取KeyValue中TimeStamp的,它先獲取TimeStamp起始位置tsOffset,然後從bytes中tsOffset位置開始讀取一個Long,即8B,這與上面提到的TimeStamp佔8B是一致的。

        13、獲取Key Type

  /**
   * @return Type of this KeyValue.
   */
  @Deprecated
  public byte getType() {
    return getTypeByte();
  }

  /**
   * @return KeyValue.TYPE byte representation
   */
  @Override
  public byte getTypeByte() {
	  
	// 整個KeyValue的位置offset + Key長度 - 1 + Key Length所佔長度和Value Length所佔長度和
	// 即Key Type位於整個Key的最後一個1B
    return this.bytes[this.offset + getKeyLength() - 1 + ROW_OFFSET];
  }
        getType()和getTypeByte()方法用來獲取KeyValue中Key Type值,它是通過在bytes中,從整個KeyValue的位置offset + Key長度 - 1 + Key Length所佔長度和Value Length所佔長度和位置處獲取的一個Byte來得到的,即Key Type位於整個Key的最後一個1B,這與上面所述也是一致的。

        14、獲取Value值

  /**
   * Returns value in a new byte array.
   * Primarily for use client-side. If server-side, use
   * {@link #getBuffer()} with appropriate offsets and lengths instead to
   * save on allocations.
   * @return Value in a new byte array.
   */
  @Deprecated // use CellUtil.getValueArray()
  public byte [] getValue() {
    return CellUtil.cloneValue(this);
  }
        getValue()方法是用來從KeyValue中獲取Value值得,它是整個Cell實際儲存的內容,通過CellUtil的cloneValue()方法,傳入KeyValue自身實力來獲得。我們來看下這個cloneValue()方法:
  public static byte[] cloneValue(Cell cell){
	  
	// 建立Value Length大小的byte[]陣列output
    byte[] output = new byte[cell.getValueLength()];
    
    // 將cell中的value值copy至output
    copyValueTo(cell, output, 0);
    return output;
  }
        cloneValue()方法首先建立Value Length大小的byte[]陣列output,然後呼叫copyValue()方法,將cell中的value值copy至output。而copyValue()方法很簡單,從bytes陣列的Value Offset處開始拷貝Value Length大小,至destination,程式碼如下:
  public static int copyValueTo(Cell cell, byte[] destination, int destinationOffset) {
    
	// 從bytes陣列的Value Offset處開始拷貝Value Length大小,至destination
	System.arraycopy(cell.getValueArray(), cell.getValueOffset(), destination, destinationOffset,
        cell.getValueLength());
    return destinationOffset + cell.getValueLength();
  }