背景
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的事件驱动比起来以及是一个比较怪异的实现