Hbase WAL 线程模型源码分析

作者介绍:熊训德 腾讯云工程师

Hbase 的 WAL 机制是保证 hbase 使用 lsm 树存储模型把随机写转化成顺序写,并从内存 read 数据,从而提高大规模读写效率的关键一环。wal 的多生产者单消费者的线程模型让wal的写入变得安全而高效。

在文章《WAL在RegionServer调用过程》中从代码层面阐述了一个 client 的“写”操作是如何到达Hbase的RegionServer,又是如何真正地写入到 wal(FSHLog) 文件,再写入到 memstore 。但是 hbase 是支持 mvcc 机制的存储系统,本文档将说明 RegionServer 是如何把多个客户端的“写”操作安全有序地落地日志文件,又如何让 client 端优雅地感知到已经真正的落地。

wal 为了高效安全有序的写入,笔者认为最关键的两个机制是 wal 中使用的线程模型和多生产者单消费者模型。

线程模型

其线程模型主要实现实在FSHLog中,FSHLog是WAL接口的实现类,实现了最关键的apend()和sync()方法,其模型如图所示:

这个图主要描述了HRegion中调用append和sync后,hbase的wal线程流转模型。最左边是有多个client提交到HRegion的append和sync操作。

当调用append后WALEdit和WALKey会被封装成FSWALEntry类进而再封装成RinbBufferTruck类放入一个线程安全的Buffer(LMAX Disruptor RingBuffer)中。

当调用sync后会生成一个SyncFuture进而封装成RinbBufferTruck类同样放入这个Buffer中,然后工作线程此时会被阻塞等待被notify()唤醒。在最右边会有一个且只有一个线程专门去处理这些RinbBufferTruck,如果是FSWALEntry则写入hadoop sequence文件。因为文件缓存的存在,这时候很可能client数据并没有落盘。所以进一步如果是SyncFuture会被批量的放到一个线程池中,异步的批量去刷盘,刷盘成功后唤醒工作线程完成wal。

源码分析

下面将从源码角度分析其中具体实现过程和细节。

工作线程中当HRegion准备好一个行事务“写”操作的,WALEdit,WALKey后就会调用FSHLog的append方法:

FSHLog的append方法首先会从LAMX Disruptor RingbBuffer中拿到一个序号作为txid(sequence),然后把WALEdit,WALKey和sequence等构建一个FSALEntry实例entry,并把entry放到ringbuffer中。而entry以truck(RingBufferTruck,ringbuffer实际存储类型)通过sequence和ringbuffer一一对应。

如果client设置的持久化等级是USER_DEFAULT,SYNC_WAL或FSYNC_WAL,那么工作线程的HRegion还将调用FSHLog的sync()方法:

追踪代码可以分析出Sync()方法会往ringbuffer中放入一个SyncFuture对象,并阻塞等待完成(唤醒)。

像模型图中所展示的多个工作线程封装后拿到由ringbuffer生成的sequence后作为生产者放入ringbuffer中。在FSHLog中有一个私有内部类RingBufferEventHandler类实现了LAMX Disruptor的EventHandler接口,也即是实现了OnEvent方法的ringbuffer的消费者。Disruptor通过 java.util.concurrent.ExecutorService 提供的线程来触发 Consumer 的事件处理,可以看到hbase的wal中只启了一个线程,从源码注释中也可以看到RingBufferEventHandler在运行中只有单个线程。由于消费者是按照sequence的顺序刷数据,这样就能保证WAL日志并发写入时只有一个线程在真正的写入日志文件的可感知的全局唯一顺序。

RingBufferEventHandler类的onEvent()(一个回调方法)是具体处理append和sync的方法。在前面说明过wal使用RingBufferTruck来封装WALEntry和SyncFuture(如下图源码),在消费线程的实际执行方法onEvent()中就是被ringbuffer通知一个个的从ringbfer取出RingBufferTruck,如果是WALEntry则使用当前HadoopSequence文件writer写入文件(此时很可能写的是文件缓存),如果是SyncFuture则简单的轮询处理放入SyncRunner线程异步去把文件缓存中数据刷到磁盘。

这里再加一个异步操作去真正刷文件缓存的原因wal源码中有解释:刷磁盘是很费时的操作,如果每次都同步的去刷client的回应比较快,但是写效率不高,如果异步刷文件缓存,写效率提高但是友好性降低,在考虑了写吞吐率和对client友好回应平衡后,wal选择了后者,积累了一定量(通过ringbuffer的sequence)的缓存再刷磁盘以此提高写效率和吞吐率。这个决策从hbase存储机制最初采用lsm树把随机写转换成顺序写以提高写吞吐率,可以看出是目标一致的。

这部分源码可以看到RingBufferTruck类的结构,从注释可以看到选择SyncFuture和FSWALEntry一个放入ringbuffer中。

这部分源码可以看到append的最终归属就是根据sequence有序的把FSWALEntry实例entry写入HadoopSequence文件。这里有序的原因是多工作线程写之前通过ringbuffer线程安全的CAS得到一个递增的sequence,ringbuffer会根据sequence取出FSWALEntry并落盘。这样做其实只有在得到递增的sequence的时候需要保证线程安全,而java的CAS通过轮询并不用加锁,所以效率很高。具体有关ringbuffer说明和实现可以参考LMAX Disruptor文档

这部分源码是说明sync操作的SyncFuture会被提交到SyncRunner中,这里可以注意SyncFuture实例其实并不是一个个提交到SyncRunner中执行的,而是以syncFutures(数组,多个SyncFuture实例)方式提交的。下面这部分源码是注释中说明批量刷盘的决策。

SyncRunner是一个线程,wal实际有一个SyncRunner的线程组,专门负责之前append到文件缓存的刷盘工作。

SyncRunner的线程方法(run())负责具体的刷写文件缓存到磁盘的工作。首先去之前提交的synceFutues中拿到其中sequence最大的SyncFuture实例,并拿到它对应ringbuffer的sequence。再去比对当前最大的sequence,如果发现比当前最大的sequence则去调用releaseSyncFuture()方法释放synceFuture,实际就是notify通知正被阻塞的sync操作,让工作线程可以继续往下继续。

前面解释了sequence是根据提交顺序过来的,并且解释了append到文件缓存的时候也是全局有序的,所以这里取最大的去刷盘,只要最大sequence已经刷盘,那么比这个sequence的也就已经刷盘成功。最后调用当前HadoopSequence文件writer刷盘,并notify对应的syncFuture。这样整个wal写入也完成了。

小结

Hbase的WAL机制是保证hbase使用lsm树存储模型把随机写转化成顺序写,并从内存read数据,从而提高大规模读写效率的关键一环。wal的多生产者单消费者的线程模型让wal的写入变得安全而高效,本文档从源码入手分析了其线程模型为以后更好开发和研究hbase其他相关知识奠定基础。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程

我是如何利用CSRF Get DedeCms Shell的

说实话,有一两个月没有审计大厂了,然后随便看到群里有人问dede最新有没有漏洞,就下了一套最新的dede,结果我一看还真发现了。 ? 我们发现后台添加广告的地方...

2037
来自专栏一只程序汪的自我修养

使用requirejs编写模块化代码

1845
来自专栏北京马哥教育

高可用集群基本概念与heartbeat文本配置接口

一、高可用集群基本概念: 什么是高可用集群: 所谓高可用集群,就是在出现故障时,可以把业务自动转移到其他主机上并让服务正常运行的集群构架 > 高...

3307
来自专栏静晴轩

Gulp探究折腾之路(I)

前言: gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器;她不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完...

3658
来自专栏Spark学习技巧

Kafka源码系列之以kafka为例讲解分布式存储系统

Kafka源码系列,浪尖是以kafka 0.8.2.2为例给大家讲解。由于公众号阅读不适大量文字,所以浪尖会尽量精简文字。目标是大家读完kafka源码系列能彻底...

3035
来自专栏我的小碗汤

6个最好的Go语言Web框架

原文:Top 6 web frameworks for Go as of 2017

1071
来自专栏Spark学习技巧

HBase原理和设计

作者:Jiang Hongxiang 来源:比特科技 简介 HBase —— Hadoop Database的简称,Google BigTable的另一种开...

34710
来自专栏lulianqi

超高性能管线式HTTP请求(实践·原理·实现)

这里的高性能指的就是网卡有多快请求发送就能有多快,基本上一般的服务器在一台客户端的压力下就会出现明显延时。

1091
来自专栏代码世界

Python中的logger和handler到底是个什么鬼

最近的任务经常涉及到日志的记录,特意去又学了一遍logging的记录方法。跟java一样,python的日志记录也是比较繁琐的一件事,在写一条记录之前,要写好多...

3139
来自专栏社区的朋友们

TAF 必修课(六):容错

上一节简单提到了客户端在选取 Invoker 节点时,会对 Invoker 列表执行死活检查,屏蔽掉一定时间内异常的节点,从而达到容灾的目的。下面对 TAF 容...

2930

扫码关注云+社区