9.4.4 IO实现

OceanBase没有使用操作系统本身的页面缓存(page cache)机制,而是自己实现缓存。相应地,IO也采用Direct IO实现,并且支持磁盘IO与CPU计算并行化。

ChunkServer采用Linux的Libaio[1]实现异步IO,并通过双缓冲区机制实现磁盘预读与CPU处理并行化,实现步骤如下:

1)分配当前(current)以及预读(ahead)两个缓冲区;

2)使用当前缓冲区读取数据,当前缓冲区通过Libaio发起异步读取请求,接着等待异步读取完成;

3)异步读取完成后,将当前缓冲区返回上层执行CPU计算,同时,原来的预读缓冲区变为新的当前缓冲区,发送异步读取请求将数据读取到新的当前缓冲区。CPU计算完成后,原来的当前缓冲区变为空闲,成为新的预读缓冲区,用于下一次预读。

4)重复步骤3),直到所有数据全部读完。

例9-5 假设需要读取的数据范围为(1,150],分三次读取:(1,50],(50,100],(100,150],当前和预读缓冲区分别记为A和B。实现步骤如下:

1)发送异步请求将(1,50]读取到缓冲区A,等待读取完成;

2)对缓冲区A执行CPU计算,发送异步请求,将(50,100]读取到缓冲区B;

3)如果CPU计算先于磁盘读取完成,那么,缓冲区A变为空闲,等到(50,100]读取完成后将缓冲区B返回上层执行CPU计算,同时,发送异步请求,将(100,150]读取到缓冲区A;

4)如果磁盘读取先于CPU计算完成,那么,首先等待缓冲区A上的CPU计算完成,接着,将缓冲区B返回上层执行CPU计算,同时,发送异步请求,将(100,150]读取到缓冲区A;

5)等待(100,150]读取完成后,将缓冲区A返回给上层执行CPU计算。

双缓冲区广泛用于生产者/消费者模型,ChunkServer中使用了双缓冲区异步预读的技术,生产者为磁盘,消费者为CPU,磁盘中生产的原始数据需要给CPU计算消费掉。

所谓“双缓冲区”,顾名思义就是两个缓冲区(简称A和B)。这两个缓冲区,总是一个用于生产者,另一个用于消费者。当两个缓冲区都操作完,再进行一次切换,先前被生产者写入的被消费者读取,先前消费者读取的转为生产者写入。为了做到不冲突,给每个缓冲区分配一把互斥锁(简称La和Lb)。生产者或者消费者如果要操作某个缓冲区,必须先拥有对应的互斥锁。

双缓冲区包括如下几种状态:

●双缓冲区都在使用的状态(并发读写)。大多数情况下,生产者和消费者都处于并发读写状态。不妨设生产者写入A,消费者读取B。在这种状态下,生产者拥有锁La;同样地,消费者拥有锁Lb。由于两个缓冲区都是处于独占状态,因此每次读写缓冲区中的元素都不需要再进行加锁、解锁操作。这是节约开销的主要来源。

●单个缓冲区空闲状态。由于两个并发实体的速度会有差异,必然会出现一个缓冲区已经操作完,而另一个尚未操作完。不妨假设生产者快于消费者。在这种情况下,当生产者把A写满的时候,生产者要先释放La(表示它已经不再操作A),然后尝试获取Lb。由于B还没有被读空,Lb还被消费者持有,所以生产者进入等待(wait)状态。

●缓冲区的切换。过了若干时间,消费者终于把B读完。这时候,消费者也要先释放Lb,然后尝试获取La。由于La刚才已经被生产者释放,所以消费者能立即拥有La并开始读取A的数据。而由于Lb被消费者释放,所以刚才等待的生产者会苏醒过来(wakeup)并拥有Lb,然后生产者继续往B写入数据。

[1]Oracle公司实现的Linux异步IO库,开源地址:https://oss.oracle.com/projects/libaio-oracle/