9.5 消除更新瓶颈

UpdateServer单点看起来像是OceanBase架构的软肋,然而,经过OceanBase团队持续不断地性能优化以及旁路导入功能的开发,单点的架构在实践过程中经受住了线上考验。每年淘宝网“双十一”光棍节,OceanBase系统都承载着核心的数据库业务,系统访问量出现5到10倍的增长,而OceanBase只需简单地增加机器即可。

当然,UpdateServer单点架构并不是不可突破。虽然目前UpdateServer单点架构还不是瓶颈,但是OceanBase系统设计时已经留好了“后门”,以后可以通过对系统打补丁的方式支持UpdateServer线性扩展。当然,这里可能会做一些牺牲,比如短期内暂不支持全局事务,只支持针对单个用户的事务操作。

本节首先回顾OceanBase已经实现的优化工作,包括读写性能优化以及旁路导入功能,接着介绍一种数据分区实现UpdateServer线性扩展的方法。

9.5.1 读写优化回顾

OceanBase UpdateServer相当于一个内存数据库,其架构设计和“世界上最快的内存数据库”MemSQL比较类似,能够支持每秒数百万次单行读写操作,这样的性能对于目前关系数据库的应用场景都是足够的。为了达到这样的性能指标,我们已经完成或正在进行的工作如下。

1.网络框架优化

9.2.2 节中提到,如果不经过优化,单机每秒最多能够接收的数据包个数只有10万个左右,而经过优化后的libeasy框架对于千兆网卡每秒最多收包个数超过50万,对于万兆网卡则超过100万。另外,UpdateServer内部还会在软件层面实现多块网卡的负载均衡,从而更好地发挥多网卡的优势。通过网络框架优化,使得单机支持百万次操作成为可能。

2.高性能内存数据结构

UpdateServer的底层是一颗高性能内存B树。为了最大程度地发挥多核的优势,B树实现时大部分情况下都做到了无锁(lock-free)。测试数据表明,即使在普通的16核机器上,OceanBase B树每秒支持的单行修改操作都超过150万次。

3.写操作日志优化

在软件层面,写操作日志涉及的工作主要有如下几点:

1)成组提交。将多个写操作聚合在一起,一次性刷入磁盘中。

2)降低日志缓冲区的锁冲突。多个线程同时往日志缓冲区中追加数据,实现时需要尽可能地减少追加过程的锁冲突。追加过程包含两个阶段:第一个阶段是占位,第二个阶段是拷贝数据,相比较而言,拷贝数据比较耗时。实现的关键在于只对占位操作互斥,而允许多线程并发拷贝数据。例如,有两个线程,线程1和线程2,他们分别需要往缓冲区追加大小为100字节和大小为300字节的数据。假设缓冲区初始为空,那么,线程1可以首先占住位置0~100,线程2接着占住100~300。最后,线程1和线程2并发将数据拷贝到刚才占住的位置。

3)日志文件并发写入。UpdateServer中每个日志缓冲区的大小一般为2MB,如果写入太快,那么,很快会产生多个日志缓冲区需要刷入磁盘,可以并发地将这些日志缓冲区刷入不同的磁盘。当然,UpdateServer目前并没有实现2和3这两个优化点。在硬件层面,UpdateServer机器需要配置较好的RAID卡。这些RAID卡自带缓存,而且容量比较大(例如1GB),从而进一步提升写磁盘性能。

4.内存容量优化

随着数据不断写入,UpdateServer的内存容量将成为瓶颈。因此,有两种解决思路。一种思路是精心设计UpdateServer的内存数据结构,尽可能地节省内存使用;另外一种思路就是将UpdateServer内存中的数据很快地分发出去。

OceanBase实现了这两种思路。首先,UpdateServer会将内存中的数据编码为精心设计的格式,从9.3.1节中可以看出,100以内的64位整数在内存中只需要占用两个字节。这种编码格式不仅能够有效地减少内存占用,而且往往使得CPU缓存能够容纳更多的数据,从而弥补编码和解码操作造成的性能损失。另外,当UpdateServer的内存使用量到达一定大小时,OceanBase会自动触发数据分发操作,将UpdateServer的数据分发到集群中的ChunkServer中,从而避免UpdateServer的内存容量成为瓶颈。当然,随着单机内存容量变得越来越大,普通的2U服务器已经具备1TB内存的扩展能力,数据分发也可能只是一种过渡方案。