上一章介绍了利用pipelining技术在不改变任何服务端实现的情况下提升性能,这样的提升非常有限。本章将要做的是深入 RocksDB 内部,借助它的批量写入功能来给我们的缓存服务 Set 操作提速。
批量写入能够提升写入性能的原理
批量写入的原理和pipelining 的原理很接近,它是在服务端将收到的 Set 操作请求积攒起来,然后一次性写入磁盘。这样做的好处有 3 点
- 通过把多次小的写操作合并成一个大的写操作,减少了磁盘的寻道时间和旋转延时,提升了磁盘IO的效率
- 写入的内容会被集中放在连续的内存里,减少了 CPU 载入内存的次数和 cache miss 的概率(这里的 cache 指的是 CPU和内存之间的缓存)
- 缓存的Set 操作现在可以尽快返回而不需要等待磁盘操作的结果,这意味着我们的缓存服务可以在相同的时间里处理更多的请求。
批量写入也有两个缺陷
- 我们不再可以知晓每一次缓存 Set操作的真实结果,缓存服务总是把成功的响应返回给客户端,但是等到真正进行批量写入的时候却有可能失败,那时我们已经没有办法把这个错误通知到客户端了。
- 我们不再能够保证 Set 操作的实时一致性了,当客户端 Set 操作返回后,客户端会认为键值对已经进入了缓存,但是当它下次打算来获取这个键值对时,它可能还在批量写入的队列里没有被真正写入磁盘。
这两个缺陷成因不同,但结果都是写入的键值对暂时或永久丢失。
好在本书实现的是缓存服务,不是存储服务。缓存的设计从一开始就明白数据是可以丢失的,所以客户端不会对获取不到成功 Set 的键值对感到惊讶。也就是说,这两个缺陷都是客户端可以容忍的。
RocksDB 批量写入性能测试
本书源码的rocksdb_performance/子目录中还有一个test batch write测试程序之前没有提到过。这个程序是专门用来测试 RocksDB 批量写入性能的,用法如下:
$ ./test batch write --help
Allowed options:
-h [ --help] produce help message
-t[--total]arg (=10000) total record number
-s[--size ]arg (=1000) value size
batch write option:
-b [ --batch size ] arg (=l) batch size
它比 test basic多了一个-b 命令行参数,该参数用来指定批量 (batch)的大小,默认为 1,也就是没有批量操作。
默认情况下的写入效率:10万次写入操作,操作平均耗时 8us,rps 达到12万。
当我们设置batch 大小为100,也就是说每 100 次Set 操作只写一次 db 的结果:10万次写入操作,操作平均耗时 3us,rps 达到33万,性能几乎是直接写入的3倍。
小结
批量写入的原理是尽量将多次写入操作合并成一次完成。磁盘 IO 的速度相比内存操作的速度要慢好几个数量级(微秒级 vs 毫秒级),减少磁盘操作的次数就意味着提升写入的性能。特别是当结合了 pipelining 技术或多客户端并发时,批量写入可以带来非常显著的写入性能提升。