Kafka.06 - 数据持久化介绍
不要畏惧文件系统!
Kafka 大量依赖文件系统去存储和缓存消息。对于硬盘有个传统的观念是硬盘总是很慢,这使很多人怀疑基于文件系统的架构能否提供优异的性能。实际上硬盘的快慢完全取决于使用它的方式。设计良好的硬盘架构可以和内存一样快。
在 6 块 7200 转的 SATA RAID-5 磁盘阵列的线性写速度差不多是 600MB/s,但是随机写的速度却是 100k/s,差了差不多 6000 倍。现代的操作系统都对此做了大量的优化,使用了 read-ahead 和 write-behind 的技巧,读取的时候成块的预读取数据,写的时候将各种微小琐碎的逻辑写入组织合并成一次较大的物理写入。线性的访问磁盘,很多时候比随机的内存访问快得多。
为了提高性能,现代操作系统往往使用内存作为磁盘的缓存,现代操作系统乐于把所有空闲内存用作磁盘缓存,虽然这可能在缓存回收和重新分配时牺牲一些性能。所有的磁盘读写操作都会经过这个缓存,这不太可能被绕开,除非直接使用 I/O。所以虽然每个程序都在自己的线程里只缓存了一份数据,但在操作系统的缓存里还有一份,这等于存了两份数据。
另外再来讨论一下 JVM,以下两个事实是众所周知的:
Java 对象占用空间是非常大的,差不多是要存储的数据的两倍甚至更高。
随着堆中数据量的增加,垃圾回收回变的越来越困难。
基于以上分析,如果把数据缓存在内存里,因为需要存储两份,不得不使用两倍的内存空间,Kafka 基于 JVM,又不得不将空间再次加倍,再加上要避免 GC 带来的性能影响,在一个 32G 内存的机器上,不得不使用到 28-30G 的内存空间。并且当系统重启的时候,又必须要将数据刷到内存中( 10GB 内存差不多要用 10 分钟),就算使用冷刷新(不是一次性刷进内存,而是在使用数据的时候没有就刷到内存)也会导致最初的时候性能非常慢。但是使用文件系统,即使系统重启了,也不需要刷新数据。使用文件系统也简化了维护数据一致性的逻辑。
所以与传统的将数据缓存在内存中然后刷到硬盘的设计不同,Kafka 直接将数据写到了文件系统的日志中。
常量时间的操作效率
在大多数的消息系统中,数据持久化的机制往往是为每个 cosumer 提供一个 B 树或者其他的随机读写的数据结构。B 树当然是很棒的,但是也带了一些代价:比如 B 树的复杂度是 O(log N),O(log N)通常被认为就是常量复杂度了,但对于硬盘操作来说并非如此。磁盘进行一次搜索需要 10ms,每个硬盘在同一时间只能进行一次搜索,这样并发处理就成了问题。虽然存储系统使用缓存进行了大量优化,但是对于树结构的性能的观察结果却表明,它的性能往往随着数据的增长而线性下降,数据增长一倍,速度就会降低一倍。
直观的讲,对于主要用于日志处理的消息系统,数据的持久化可以简单的通过将数据追加到文件中实现,读的时候从文件中读就好了。这样做的好处是读和写都是 O(1) 的,并且读操作不会阻塞写操作和其他操作。这样带来的性能优势是很明显的,因为性能和数据的大小没有关系了。
既然可以使用几乎没有容量限制(相对于内存来说)的硬盘空间建立消息系统,就可以在没有性能损失的情况下提供一些一般消息系统不具备的特性。比如,一般的消息系统都是在消息被消费后立即删除,Kafka 却可以将消息保存一段时间(比如一星期),这给 consumer 提供了很好的机动性和灵活性。
评论