這里面說的read既包括get,也包括scan,實際底層來看這兩個操作也是一樣的。
我們將要討論的是,當我們從一張表讀取數據的時候hbase到底是怎么處理的。
分二種情況來看,第一種就是表剛創建,所有put的數據還在memstore中,并沒有刷新到hdfs上;第二種情況是,該store已經進行多次的flush操作,產生了多個storefile了。
在具體說明兩種情況前,先考慮下表的region的問題,如果表只有一個region,那么沒有說的,肯定是要掃描這個唯一的region。假設該表有多個region,此時.META.表就派上用場了,hbase會首先根據你要掃描的數據的rowkey來判斷到底該數據放在哪個region上,該region所在服務器地址,然后把數據讀取的請求發送給該region server。好了,實際對數據訪問的任務都會放在region server上執行,為了簡單起見,接下來的討論都是在單臺region server上對單個region的操作。
首先來看第一種情況,表剛創建,所有put的數據還在memstore中,并沒有刷新到hdfs上。這個時候數據是在memstore中,并沒有storefile產生,理所當然,hbase要查找memstore來獲得相應的數據。對于memstore或者storefile來說,內存中都有關于rowkey的索引的,所以對于通過rowkey的查詢速度是非常快速的。通過查詢該索引就知道是否存在需要查看的數據,已經該數據在memstore中的位置。通過索引提供的信息就很容易找得到所需要的數據。這種情況很簡單。
在來看第二種情況,該store已經進行多次的flush操作,產生了多個storefile了。那么數據應該從哪里查呢?所有的storefile?別忘記還有memstore。此時memstore中可能還會有沒來得及flush的數據呢。如果此時該region還有很多的文件,是不是所有的文件都需要查找呢?hbase在查找先會根據時間戳或者查詢列的信息來進行過濾,過濾掉那些肯定不含有所需數據的storefile或者memstore,盡量把我們的查詢目標范圍縮小。
盡管縮小了,但仍可能會有多個文件需要掃描的。storefile的內部有三維有序的,但是各個storefile之間并不是有序的。比如,storefile1中可能有rowkey為100到110的記錄,而storefile2可能有rowkey為105到115的數據,storefile的rowkey的范圍很有可能有交叉。所以查詢數據的過程也不可能是對storefile的順序查找。
hbase會首先查看每個storefile的最小的rowkey,然后按照從小到大的順序進行排序,結果放到一個隊列中,排序的算法就是按照hbase的三維順序,按照rowkey,column,ts進行排序,rowkey和column是升序,而ts是降序。
實際上并不是所有滿足時間戳和列過濾的文件都會加到這個隊列中,hbase會首先對各個storefile中的數據進行探測,只會掃描掃描那些存在比當前查詢的rowkey大的記錄的storefile。舉例來說,我當前要查找的rowkey為108,storefile1中rowkey范圍為100~104,storefile2中rowkey的范圍為105~110,那么對于storefile1最大的rowkey為104,小于105,所以不存在比所查rowkey105大的記錄,storefile并不會被加到該隊列中。根據相同的規則,storefile2則會被添加到該隊列中。
隊列有了,下面開始查詢數據,首先通過poll取出隊列的頭storefile,會從storefile讀取一條記錄返回;接下來呢,該storefile的下條記錄并不一定是查詢結果的下一條記錄,因為隊列的比較順序是比較的每個storefile的第一條符合要求的rowkey。所以,hbase會繼續從隊列中剩下的storefile取第一條記錄,把該記錄與頭storefile的第二條記錄做比較,如果前者大,那么返回頭storefile的第二條記錄;如果后者大,則會把頭storefile放回隊列重新排序,在重新取隊列的頭storefile。然后重復上面的整個過程。這個過程比較煩,語言描述不清楚,代碼會更加清晰。
這段代碼如下:
以上的代碼在KeyValueHeap.java類中。
舉個例子來說明:表sunwg01,有兩個storefile,storefile1中包括rowkey100,rowkey110;storefile2中包括rowkey104,rowkey108。我現在執行scan ‘sunwg01′掃描表sunwg01中的所有的記錄。
根據前面提到的排序規則,隊列中會有2個元素,按順序分別為storefile1,storefile2。
1,取出storefile1中的第一條記錄rowkey100,并返回該結果
2,取出storefile1中的下一條記錄rowkey110,同時取出隊列剩余storefile的第一條記錄rowkey104,經過比較rowkey110大于rowkey104,則將storefile1放回隊列中
3,因為隊列是有序的隊列,會重新對storefile進行排序,因為此時storefile1的最小rowkey為110,而storefile2的最小rowkey為104,所以排序的結果為storefile2,storefile1
4,重復上面的過程,直到查不到記錄為止。
最后查到的結果為:rowkey100,rowkey104,rowkey108,rowkey110。
順便說下block cache的事情,當從storefile中讀數據的時候會首先查看block cache中是否有該數據,如果有則直接查block cache,就沒必要查詢hdfs;如果沒有該數據,那么就只能去查hdfs了。這也是為了block cache的命中率對性能有很大影響的原因。
上面描述了從hbase中read的基本的過程,還有些細節沒有具體說,但是大概過程應該是都說到了。
我們將要討論的是,當我們從一張表讀取數據的時候hbase到底是怎么處理的。
分二種情況來看,第一種就是表剛創建,所有put的數據還在memstore中,并沒有刷新到hdfs上;第二種情況是,該store已經進行多次的flush操作,產生了多個storefile了。
在具體說明兩種情況前,先考慮下表的region的問題,如果表只有一個region,那么沒有說的,肯定是要掃描這個唯一的region。假設該表有多個region,此時.META.表就派上用場了,hbase會首先根據你要掃描的數據的rowkey來判斷到底該數據放在哪個region上,該region所在服務器地址,然后把數據讀取的請求發送給該region server。好了,實際對數據訪問的任務都會放在region server上執行,為了簡單起見,接下來的討論都是在單臺region server上對單個region的操作。
首先來看第一種情況,表剛創建,所有put的數據還在memstore中,并沒有刷新到hdfs上。這個時候數據是在memstore中,并沒有storefile產生,理所當然,hbase要查找memstore來獲得相應的數據。對于memstore或者storefile來說,內存中都有關于rowkey的索引的,所以對于通過rowkey的查詢速度是非常快速的。通過查詢該索引就知道是否存在需要查看的數據,已經該數據在memstore中的位置。通過索引提供的信息就很容易找得到所需要的數據。這種情況很簡單。
在來看第二種情況,該store已經進行多次的flush操作,產生了多個storefile了。那么數據應該從哪里查呢?所有的storefile?別忘記還有memstore。此時memstore中可能還會有沒來得及flush的數據呢。如果此時該region還有很多的文件,是不是所有的文件都需要查找呢?hbase在查找先會根據時間戳或者查詢列的信息來進行過濾,過濾掉那些肯定不含有所需數據的storefile或者memstore,盡量把我們的查詢目標范圍縮小。
盡管縮小了,但仍可能會有多個文件需要掃描的。storefile的內部有三維有序的,但是各個storefile之間并不是有序的。比如,storefile1中可能有rowkey為100到110的記錄,而storefile2可能有rowkey為105到115的數據,storefile的rowkey的范圍很有可能有交叉。所以查詢數據的過程也不可能是對storefile的順序查找。
hbase會首先查看每個storefile的最小的rowkey,然后按照從小到大的順序進行排序,結果放到一個隊列中,排序的算法就是按照hbase的三維順序,按照rowkey,column,ts進行排序,rowkey和column是升序,而ts是降序。
實際上并不是所有滿足時間戳和列過濾的文件都會加到這個隊列中,hbase會首先對各個storefile中的數據進行探測,只會掃描掃描那些存在比當前查詢的rowkey大的記錄的storefile。舉例來說,我當前要查找的rowkey為108,storefile1中rowkey范圍為100~104,storefile2中rowkey的范圍為105~110,那么對于storefile1最大的rowkey為104,小于105,所以不存在比所查rowkey105大的記錄,storefile并不會被加到該隊列中。根據相同的規則,storefile2則會被添加到該隊列中。
隊列有了,下面開始查詢數據,首先通過poll取出隊列的頭storefile,會從storefile讀取一條記錄返回;接下來呢,該storefile的下條記錄并不一定是查詢結果的下一條記錄,因為隊列的比較順序是比較的每個storefile的第一條符合要求的rowkey。所以,hbase會繼續從隊列中剩下的storefile取第一條記錄,把該記錄與頭storefile的第二條記錄做比較,如果前者大,那么返回頭storefile的第二條記錄;如果后者大,則會把頭storefile放回隊列重新排序,在重新取隊列的頭storefile。然后重復上面的整個過程。這個過程比較煩,語言描述不清楚,代碼會更加清晰。
這段代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public KeyValue next() throws IOException { if(this.current == null) { return null; } KeyValue kvReturn = this.current.next(); KeyValue kvNext = this.current.peek(); if (kvNext == null) { this.current.close(); this.current = this.heap.poll(); } else { KeyValueScanner topScanner = this.heap.peek(); if (topScanner == null || this.comparator.compare(kvNext, topScanner.peek()) >= 0) { this.heap.add(this.current); this.current = this.heap.poll(); } } return kvReturn; } |
以上的代碼在KeyValueHeap.java類中。
舉個例子來說明:表sunwg01,有兩個storefile,storefile1中包括rowkey100,rowkey110;storefile2中包括rowkey104,rowkey108。我現在執行scan ‘sunwg01′掃描表sunwg01中的所有的記錄。
根據前面提到的排序規則,隊列中會有2個元素,按順序分別為storefile1,storefile2。
1,取出storefile1中的第一條記錄rowkey100,并返回該結果
2,取出storefile1中的下一條記錄rowkey110,同時取出隊列剩余storefile的第一條記錄rowkey104,經過比較rowkey110大于rowkey104,則將storefile1放回隊列中
3,因為隊列是有序的隊列,會重新對storefile進行排序,因為此時storefile1的最小rowkey為110,而storefile2的最小rowkey為104,所以排序的結果為storefile2,storefile1
4,重復上面的過程,直到查不到記錄為止。
最后查到的結果為:rowkey100,rowkey104,rowkey108,rowkey110。
順便說下block cache的事情,當從storefile中讀數據的時候會首先查看block cache中是否有該數據,如果有則直接查block cache,就沒必要查詢hdfs;如果沒有該數據,那么就只能去查hdfs了。這也是為了block cache的命中率對性能有很大影響的原因。
上面描述了從hbase中read的基本的過程,還有些細節沒有具體說,但是大概過程應該是都說到了。