HBase中MVCC的實現機制及應用情況

本文轉載自:http://www.cnblogs.com/panfeng412/p/mvcc-implementation-mechanism-in-hbase.html

MVCC(Multi-Version Concurrent Control),即多版本併發控制協議,廣泛使用於數據庫系統。本文將介紹HBase中對於MVCC的實現及應用情況。

MVCC基本原理

在介紹MVCC概念之前,我們先來想一下數據庫系統裏的一個問題:假設有多個用戶同時讀寫數據庫裏的一行記錄,那麼怎麼保證數據的一致性呢?一個基本的解決方法是對這一行記錄加上一把鎖,將不同用戶對同一行記錄的讀寫操作完全串行化執行,由於同一時刻只有一個用戶在操作,因此一致性不存在問題。但是,它存在明顯的性能問題:讀會阻塞寫,寫也會阻塞讀,整個數據庫系統的併發性能將大打折扣。

MVCC(Multi-Version Concurrent Control),即多版本併發控制協議,它的目標是在保證數據一致性的前提下,提供一種高併發的訪問性能。在MVCC協議中,每個用戶在連接數據庫時看到的是一個具有一致性狀態的鏡像,每個事務在提交到數據庫之前對其他用戶均是不可見的。當事務需要更新數據時,不會直接覆蓋以前的數據,而是生成一個新的版本的數據,因此一條數據會有多個版本存儲,但是同一時刻只有最新的版本號是有效的。因此,讀的時候就可以保證總是以當前時刻的版本的數據可以被讀到,不論這條數據後來是否被修改或刪除。

更多關於MVCC基本思想的介紹,參考Wikipedia

一個MVCC實現類

見org.apache.hadoop.hbase.regionserver.MultiVersionConsistencyControl,用於控制Memstore中讀寫的一致性,其中維護兩個long型的變量:

1)memstoreRead:用於記錄當前全局可讀的readPoint,同時爲了每個客戶端讀請求能夠記錄自己發起請求時刻的readPoint,還有一個ThreadLocal的perThreadReadPoint變量,以及相關的set和get方法;

2)memstoreWrite:用於記錄當前全局最大的writePoint,根據它爲下個事務生成新的writePoint。

MultiVersionConsistencyControl中關鍵的實現方法如下:

1)WriteEntry beginMemstoreInsert():開始一個更新操作,將memstoreWrite加1,創建writeQueue並插入到writeQueue,並返回WriteEntry對象;

2)void completeMemstoreInsert(WriteEntry e):完成當前更新操作,將WriteEntry對象標記爲可讀,具體分兩步:

  • boolean advanceMemstore(WriteEntry e):從頭開始遍歷writeQueue,移除所有已完成的WriteEntry對象,最後將memstoreRead更新爲最新已完成的memstoreWrite;
  • void waitForRead(WriteEntry e):阻塞當前線程,直到memstoreRead等於當前WriteEntry的memstoreWrite,至此表明當前WriteEntry之前的所有更新事務都已經完成。

MVCC使用場景

見org.apache.hadoop.hbase.regionserver.HRegion.java,每個Region包含一個Memstore,維護一個MultiVersionConsistencyControl對象。

寫操作

見HRegion.java中的以下寫操作的方法:

1)put

2)checkAndPut

3)delete

4)checkAndDelete

5)internalFlushcache

6)mutateRow

7)mutateRowsWithLocks

8)batchMutate

最終會調用到applyFamilyMapToMemstore方法使用MVCC進行寫操作:

複製代碼
  /**
   * Atomically apply the given map of family->edits to the memstore.
   * This handles the consistency control on its own, but the caller
   * should already have locked updatesLock.readLock(). This also does
   * <b>not</b> check the families for validity.
   *
   * @param familyMap Map of kvs per family
   * @param localizedWriteEntry The WriteEntry of the MVCC for this transaction.
   *        If null, then this method internally creates a mvcc transaction.
   * @return the additional memory usage of the memstore caused by the
   * new entries.
   */
  private long applyFamilyMapToMemstore(Map<byte[], List<KeyValue>> familyMap,
    MultiVersionConsistencyControl.WriteEntry localizedWriteEntry) {
    long size = 0;
    boolean freemvcc = false;

    try {
      if (localizedWriteEntry == null) {
        localizedWriteEntry = mvcc.beginMemstoreInsert();
        freemvcc = true;
      }

      for (Map.Entry<byte[], List<KeyValue>> e : familyMap.entrySet()) {
        byte[] family = e.getKey();
        List<KeyValue> edits = e.getValue();

        Store store = getStore(family);
        for (KeyValue kv: edits) {
          kv.setMemstoreTS(localizedWriteEntry.getWriteNumber());
          size += store.add(kv);
        }
      }
    } finally {
      if (freemvcc) {
        mvcc.completeMemstoreInsert(localizedWriteEntry);
      }
    }

     return size;
   }
複製代碼

讀操作

HRegion.java中通過private ConcurrentHashMap<RegionScanner, Long> scannerReadPoints;維護各個查詢請求的readPoint。

以get或scan請求爲例,最終會通過getScanner方法需要構造RegionScannerImpl對象:

org.apache.hadoop.hbase.regionserver.HRegion.RegionScannerImpl

1)根據Scan對象構造時設置好readPoint,scan.getIsolationLevel()分爲READ_UNCOMMITTED和READ_COMMITTED,只有當READ_COMMITTED時根據MultiVersionConsistencyControl.resetThreadReadPoint(mvcc);設置當前scanner線程的readPoint,並插入到scannerReadPoints維護起來。

2)根據scan需要讀取的column family,創建StoreScanner(根據bloom filter、time range、ttl篩選需要的MemStoreScanner和StoreFileScanner),添加到scanners中,並最終根據scanners構造出一個KeyValueHeap

下面看下RegionScannerImpl中的next方法是每次查詢時需要調用的函數:

boolean org.apache.hadoop.hbase.regionserver.HRegion.RegionScannerImpl.next(List<KeyValue> outResults, int limit) throws IOException

而上述方法會通過KeyValueHeap的next方法讀取下一條數據:先定位到當前KeyValueScanner(即之前構造KeyValueHeap時傳入的MemStoreScanner或StoreScanner),然後調用next方法。

StoreFileScanner和MemStoreScanner均爲KeyValueScanner,通過其中的next()接口方法,分別調用到StoreFileScanner.java的skipKVsNewerThanReadpoint方法、Memstore.java中MemStoreScanner對象的getNext方法。

1)StoreFileScanner.java的skipKVsNewerThanReadpoint方法:

 View Code

2)  Memstore.java中MemStoreScanner對象的getNext方法:

複製代碼
    protected KeyValue getNext(Iterator<KeyValue> it) {
      long readPoint = MultiVersionConsistencyControl.getThreadReadPoint();
    
      while (it.hasNext()) {
        KeyValue v = it.next();
        if (v.getMemstoreTS() <= readPoint) {
          return v;
        }
      }

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