hbase-2.4.0源码阅读
Hbase的主要操作有put,get和scan,这里主要针对这三种操作进行源码的解读.整体上看hbase是简单的,因为不涉及复杂的查询逻辑,作为数据库只能算是极简级别的.
1 调试类及代码更改
Hbase的测试代码比较清晰,主要的测试工具类是以TestingUtility结尾的类,在这些工具类中模拟了迷你hbase集群等各种测试用环境,其使用了真实的源码,因此适合直接调试.
为简单起见,此处主要从server端来调试代码,从客户端也可以,因为hbase的rpc和hadoop的一样,都是使用protocol来定义服务端的方法,因此如果使用client端进行调试,在server的相应方法上打上断点即可,这里不再详述.
这里使用hbase-server子工程下的测试目录中的regionserver部分,测试类名为TestParallelPut,此类一共有两个方法,一个是测试单个put,一个是测试多个put,这里把testParallelPuts()方法上的@Test注解去掉,只使用testPut()方法,也就是只使用单个put方法,然后添加get和scan方法,如下所示
2 put方法
Put方法调用的方法树为:
其核心方法为doBatchMutate,其调用的方法树为
调用的核心方法为doMiniBatchMutate,进入该方法,可以看到作者添加了许多注释,一共分为6大步:
第一步:获取各种锁
第二步:更新最新的时间戳
第三和第四步:写wal
第五步:写memstore
第六步:收尾
其中重要的是wal的写入和memstore的写入,wal的写入比较简单,这里不再分析,主要是看一看memstore的写入.
2.1 写入memstore的方法writeMiniBatchOperationsToMemStore
此方法主要是向memstore中写入要put的数据,此cell为单位写入,其调用的方法树为
可以看到,最终是类似map的操作,使用了upsert方法,不难猜测出其与排序有关.其实该memstore的主要作用就是排序,然后顺序写入硬盘,所以调大该memstore并不能提升hbase的写入速度,因为hbase的写入速度是由顺序写硬盘决定的,是硬件决定了hbase的写入速度.
3 get方法
Get方法最终调用的也是scan方法,其调用的方法树为
可以看到其最终也是调用的scan方法.
4 scan方法
在getScanner这一方法上打上断点,这很重要,因为所有执行scan的操作就是在这一行内完成的.
通过执行断点,可以看到其实际的调用方法树为
进入RegionScannerImpl的构造方法,其调用的方法树为
可以看到核心方法是initializeScanners方法,其调用的方法树为
这里有两个重要的方法,一是getScanner方法,另一个是initializeKVHeap.从这两个方法不难推测出,查找数据的逻辑首先是构造扫描器scanner,接着使用扫描器对存储结果的堆KVHeap进行初始化,也就是把堆填上根据扫描器扫描的数据.
4.1 getScanner方法
其主要是获取storeFile的scanner,调用树为
核心方法是getScanners,调用的方法树为
可以看到,主要是获取两类scanner,一类是内存memstore的scanner,另一类是硬盘storeFile的scanner,内存的比较简单,硬盘的scanner也比较简单,重要的是在获取时把文件的读取流打开了,这也为之后初始化堆做准备.
getScannersForStoreFiles的调用树为
可以看到,其打开了HStoreFile的读文件流.
4.2 initializeKVHeap方法
此方法就是利用扫描器scanner来获取数据并放在堆上,其调用的方法树为
在初始化方法中的KeyValueHeap的构造方法调用的的方法树为
重要方法是pollRealKV方法,其调用的方法树为
可以看到,此方法是真正的执行扫描数据获取结果的逻辑,各种扫描器在进行扫描数据.
5 总结
这里主要从server端直接使用已有的测试类来解读了主要的操作put和scan,也就是写入和读取的操作.不难发现hbase的设计比较繁琐,相比较传统的数据库,在读和写两方面处理的都很复杂.
5.1 写和读数据的逻辑流程
写数据主要是先写wal,以防挂机或断电造成内存数据丢失,再写内存,目的是告诉顺序的向硬盘写数据.
读数据的流程是先准备好内存和硬盘的扫描器scanner,这里主要是硬盘的扫描器,每一个hfile文件对应一个扫描器,再利用这些扫描器进行扫描,把扫描到的数据存入内置的堆中,共后续使用.
5.2 不高效的写数据流程
不高效的方面有:
1 首先要写入wal,而且是调用的hdfs的写流程,这个就很繁琐
2 其次还要定时合并lsm树,这是因为要和已有的硬盘文件保持数据的一致更新
3 最后还需要合并各个server上的hfile文件,这又是一个繁琐的流程
5.3 不高效的读数据流程
反映在如下方面
1 从内存中读取数据,在jvm中维护了一个至少258M的类map结构的数据,虽然在内存中,但此数据结构的主要功能并不是用来读数据的,相反是为了排好序写数据用的
2 从各个节点的硬盘上读数据时在内部初始化一个堆,这个堆可以很大,当读取大量数据时会发生明显的full-gc过程,因为这个很大的内部堆不会在短时间内把数据都排出
3 虽然使用了较多的索引加跳表,但是随机查询的效率依然很低
可以预见,未来hbase会大概率的被淘汰掉,其使用场景正在逐步消失.