背景
HTable作爲HBase的CRUD的客戶端底層是怎麼實現的,雖然HBase-The-Definitive-Guide 這本書的作者推薦在生產環境使用HTablePool
但瞭解HTable還是很有必要的
下面以一個簡單的例子來說明
- protected static String TEST_TABLE_NAME = "testtable";
- protected static String ROW1_STR = "row1";
- protected static String COLFAM1_STR = "colfam1";
- protected static String QUAL1_STR = "qual1";
- private final static byte[] ROW1 = Bytes.toBytes(ROW1_STR);
- private final static byte[] COLFAM1 = Bytes.toBytes(COLFAM1_STR);
- private final static byte[] QUAL1 = Bytes.toBytes(QUAL1_STR);
- @Test
- public void testHTable() throws IOException {
- Configuration conf = HBaseConfiguration.create();
- HTable table = new HTable(conf, TEST_TABLE_NAME);
- Get get = new Get(ROW1);
- Result result = table.get(get);
- byte[] val = result.getValue(COLFAM1, QUAL1);
- assertThat(Bytes.toString(val), is("val1"));
- }
代碼非常簡單,就是初始化HTable和調用HTable實例的get獲取ROW1的值
在介紹下面的內容之前先簡單的說下HBase server
HBase server 基本上由zookeeper和HRegion server組成
前者主要是用於監控和管理HRegion server,後者纔是提供數據服務的
HRegion server又分爲master+server,master用於維護meta信息,
master和server的確定使用了leader follow方式,任何HRegion server都有可能成爲Master
HTable模型
- HTable: 客戶端API
- HConnection: HTable對zookeeper的網絡連接
- ServerCallable:抽象一次和Region Server的交互,因此它需要有HConnection和HRegionLocation,同時需要有一個訪問Region Server的接口
- HRegionInterface HRegionLocation: Region Server的地址
- HRegionInfo: Region Server的信息
- HRegionInterface:訪問Region Server的接口的動態代理
- WritableRpcEngine$Invoker:動態代理的相關handle
- HBaseClient:Region Server的客戶端
- 其他的就不解釋了
HTable runtime
1)HTable的初始化step1
- 初始化對zookeeper的連接
- 會調用zookeeper的api,啓動兩個線程,一個是sendthread,一個是eventhtread,
- sendthread專門維護對zookeeper的連接,並且會監聽zookeeper 的事件,比如節點變化的通知,一旦接到通知會將通知轉發給eventhtread
- eventhtread處理zookeeper的變化
- 從zookeeper獲取到root HRegionLocation[region=-ROOT-,,0.70236052, hostname=localhost, port=33032] 並cache
- 從root HRegion中獲取到meta HRegionLocation[region=.META.,,1.1028785192, hostname=localhost, port=33032] 並cache
- 從meta HRegion 取更多hregion信息緩存到本地(見HConnectionManager$HConnectionImplementation.prefetchRegionCache)
- 從meta HRegion中獲取表所在的HRegionLocation[region=testtable,,1332256150817.5c8417fb964377174f649af04a70ad5e., hostname=localhost, port=33032]並cache
3) HTable.get到底幹了什麼
- 從cache中獲取表對應的HRegionLocation
- 從cache中獲取HRegionLocation對應的Region server【一個實現了HRegionInterface的動態代理,使用了WritableRpcEngine$Invoker作爲InvocationHandler】
- 調用動態代理的get,解下來就會調用WritableRpcEngine$Invoker的invoke
- 調用HBaseClient.call
- 初始化一個Call實例(封裝了請求和響應)
- 連接到Region server(見HBaseClient.getConnection)得到HBaseClient.Connection實例,此時會啓動一個讀線程(每個region一個),專門讀取服務端的響應,也會將請求寫入讀線程的隊列中
- 發請求,見【HBaseClient.Connection.sendParam(call)】,如果是多個請求線程,由於此時共用一個連接,還存在同步問題,且網絡寫也是阻塞的,寫完了還得等通知
- 讀線程會不斷的讀取響應,讀取一個相應響應就將請求隊列中對應的請求刪除,並通知主線程即客戶端線程,如果讀線程空閒超時會自動回收
- 客戶端線程和讀線程採用傳統的wait + notify 通信,這也意味着客戶端線程會阻塞
小結
- 從HTable 的實現中看到了所謂的傳說中的RPC到底是咋回事
- 一個服務接口
- 實現了服務接口的動態代理
- 動態代理對應的invoke handle
- 當然還有底層網絡客戶端
- 另外也看到的HTable的io模型
- 多個客戶線程同步+阻塞寫,並將寫註冊到讀線程的隊列中
- 單線程綁定單連接(基本上是每個region對應一個),讀取響應,刪除隊列中的請求標示,並通知客戶線程
- 並沒有使用NIO
- 和傳統的連接池以及時髦的使用select的事件驅動比起來以及是一個比較怪異的實現