以上的this.syncRunners就是SyncRunner线程池。可以看到,通过计算syncRunnerIndex,采用了简单的轮循提交算法。
另外,WAL日志消费线程,会尝试收集一批SyncFuture对象(即sync操作),一次提交给SyncRunner。
所以,在以上代码中,可以看到传入offer()方法的,是this.syncFutures这一SyncFutures[]数组,而不是单个SyncFuture对象。
收集一批次再提交,性能比较好。但是单个批次需要积攒的SyncFuture对象越多,则Sync的及时性越差,会导致前台Region Server RPC服务线程阻塞在SyncFuture.get()上的时间就越长。
因此,这里存在吞吐量和及时性之间的平衡。HBase为了支持海量数据的写入,在这里更倾向于 高吞吐量 ,体现在了以下注释中。具体多少个SyncFuture构成一个批次,有一定的策略,在此不再累述。
SyncRunner线程
1. 从队列中获取一个由WAL日志消费线程提交的SyncFuture(下图红框中的代码)。
2. 调用文件系统API,执行sync()操作(下图蓝框中的代码)
合并多次频繁的sync()操作,提高性能。
上文提到,WAL日志消费线程一次会提交多个SyncFuture。对此,SyncRunner线程只会落实执行其中最新的SyncFuture(也就是Sequence ID最大的那个)所代表的Sync操作。而忽略之前的SyncFuture。
这就是下图绿框中的代码。
3. 如果sync()完成,或者因为上面提到的合并忽略了某一个SyncFuture,那么会调用releaseSyncFuture() ==> Object.notify()来通知SyncFuture阻塞退出。
之前阻塞在SyncFuture.get()上的Region Server RPC服务线程就可以继续往下执行了。
至此,整个WAL写入流程完成。
总结
我觉得对线程并发写入文件时,用队列来协调,保证日志写入的顺序,这还是比较容易想到的。
但是,提供Sync() API确保日志写入的可靠性,同时避免频繁的Sync()操作影响性能。——这是HBase WAL实现的一大亮点。