上一篇主要梳理了Flush的大致流程,包括prepare,flush,commit三个阶段,其中最耗时,涉及磁盘IO的阶段是将memstore的快照写入临时文件的flush阶段,这篇深入flush阶段进行梳理。
Store通过调用flushCache() 方法,将快照写入临时文件,实际是调用storeEngine中,StoreFlusher的flushSnapshot方法:
StoreFlusher flusher = storeEngine.getStoreFlusher();
IOException lastException = null;
for (int i = 0; i < flushRetriesNumber; i++) {
try {
List<Path> pathNames =
flusher.flushSnapshot(snapshot, logCacheFlushId, status, throughputController);
StoreFlusher是一个抽象类,目前HBase有两种实现,一种是DefaultStoreFlusher, 一种是StripeStoreFlusher。
StoreFlusher
StoreFlusher 包含3个非抽象方法finalizeWriter(), createScanner(), performFlush() 和一个抽象方法flushSnapshot()。
CreateScanner() 方法
protected InternalScanner createScanner(KeyValueScanner snapshotScanner,
long smallestReadPoint) throws IOException {
InternalScanner scanner = null;
if (store.getCoprocessorHost() != null) {
scanner = store.getCoprocessorHost().preFlushScannerOpen(store, snapshotScanner,
smallestReadPoint);
}
if (scanner == null) {
Scan scan = new Scan();
scan.setMaxVersions(store.getScanInfo().getMaxVersions());
scanner = new StoreScanner(store, store.getScanInfo(), scan,
Collections.singletonList(snapshotScanner), ScanType.COMPACT_RETAIN_DELETES,
smallestReadPoint, HConstants.OLDEST_TIMESTAMP);
}
assert scanner != null;
if (store.getCoprocessorHost() != null) {
try {
return store.getCoprocessorHost().preFlush(store, scanner);
} catch (IOException ioe) {
scanner.close();
throw ioe;
}
}
return scanner;
}
创建一个scanner用来扫描snapshot,由于仅扫描内存,所以scan速度会比较快,如果有coprocessor,会执行其preFlush方法。
performFlush() 方法
protected void performFlush(InternalScanner scanner, Compactor.CellSink sink,
long smallestReadPoint, ThroughputController throughputController) throws IOException {
int compactionKVMax =
conf.getInt(HConstants.COMPACTION_KV_MAX, HConstants.COMPACTION_KV_MAX_DEFAULT);
ScannerContext scannerContext =
ScannerContext.newBuilder().setBatchLimit(compactionKVMax).build();
List<Cell> kvs = new ArrayList<Cell>();
boolean hasMore;
String flushName = ThroughputControlUtil.getNameForThrottling(store, "flush");
boolean control = throughputController != null && !store.getRegionInfo().isSystemTable();
if (control) {
throughputController.start(flushName);
}
try {
do {
hasMore = scanner.next(kvs, scannerContext);
if (!kvs.isEmpty()) {
for (Cell c : kvs) {
sink.append(c);
int len = KeyValueUtil.length(c);
if (control) {
throughputController.control(flushName, len);
}
}
kvs.clear();
}
} while (hasMore);
} catch (InterruptedException e) {
throw new InterruptedIOException("Interrupted while control throughput of flushing "
+ flushName);
} finally {
if (control) {
throughputController.finish(flushName);
}
}
}
一开始获取超参"hbase.hstore.compaction.kv.max",限制scanner一个batch包含的最多KV数量,默认是10。
finalizeWriter() 方法将对应hfile的log 序号写入metadata中。
DefaultStoreFlusher
DefaultStoreFlusher中,flushSnapshot方法逻辑很简单,首先创建memstore的scanner,再创建一个StoreFile.Writer进行写入,最后调用finalizeWriter方法。
StripStoreFlusher
对应于底层StoreFile使用了StripCompaction的情况,从HBase官方文档,该特性还处于实验阶段,详解介绍参考HBase官网文档:
https://hbase.apache.org/book.html