【Kafka】 Persistence

Metadata

title: 【Kafka】 Persistence
date: 2023-02-14 13:52
tags:
  - 行动阶段/完成
  - 主题场景/组件
  - 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
  - 细化主题/Module/Kafka
categories:
  - Kafka
keywords:
  - Kafka
description: 【Kafka】 Persistence

【Kafka】 Persistence

Don’t fear the filesystem!

Kafka严重依赖文件系统来存储和缓存消息。人们普遍认为“磁盘很慢”,这使得人们怀疑持久结构能否提供具有竞争力的性能。事实上,根据磁盘的使用方式,磁盘比人们预期的要慢得多,也比人们预期的快得多;合理设计的磁盘结构通常可以和网络一样快。

关于磁盘性能的关键事实是,在过去的十年中,硬盘驱动器的吞吐量已经偏离了磁盘寻道的延迟。因此,在JBOD配置的6个7200rpm SATA RAID-5阵列上,线性写的性能约为600MB/s,而随机写的性能仅为100k/s,相差超过6000X。这些线性读写是所有使用模式中最可预测的,操作系统对其进行了大量优化。现代操作系统提供了预读和后写技术,可以以大的块倍数预取数据,并将较小的逻辑写入分组为较大的物理写入。关于这个问题的进一步讨论可以在这篇ACM Queue文章中找到;他们实际上发现,顺序磁盘访问在某些情况下可能比随机内存访问更快!

为了弥补这种性能差异,现代操作系统越来越积极地使用主内存来进行磁盘缓存。现代操作系统在回收内存时,会很高兴地将所有空闲内存转移到磁盘缓存中,而不会有什么性能损失。所有磁盘读写都将通过这个统一的缓存。如果不使用直接I/O,则无法轻松关闭此功能,因此即使进程维护了数据的进程内缓存,此数据也可能会在OS pagecache中重复,从而有效地将所有数据存储两次。

此外,我们是在JVM之上构建的,任何接触过Java内存使用的人都知道两件事:

  1. 对象的内存开销非常高,通常是存储数据的两倍(甚至更糟)。
  2. 随着堆内数据的增加,Java垃圾收集变得越来越繁琐和缓慢。

由于这些因素,使用文件系统和依赖pagecache比维护内存中的缓存或其他结构要好。自动访问所有空闲内存至少会将缓存增加一倍,而存储紧凑的字节结构而不是单个对象,可能还会增加一倍。这样做将在32GB的机器上产生高达28-30GB的缓存,而不会产生GC惩罚。此外,即使服务重启,这个缓存也会保持热状态,而进程内缓存需要在内存中重新构建(对于10GB的缓存来说,可能需要10分钟),或者需要从完全冷的缓存开始(这可能意味着糟糕的初始性能)。这也大大简化了代码,因为所有用于维护缓存和文件系统之间一致性的逻辑现在都在操作系统中,这往往比进程内一次性尝试更有效、更正确。如果您的磁盘使用习惯线性读取,那么预读就是在每次磁盘读取时有效地用有用的数据预先填充这个缓存。

这就提出了一个非常简单的设计:与其在内存中维护尽可能多的数据,并在内存空间耗尽时将其全部刷新到文件系统,不如将其反转。所有数据立即写入文件系统上的持久日志,而不必刷写到磁盘。实际上,这仅仅意味着它被传输到内核的页缓存中。

这种以pagecache为中心的设计风格在一篇关于清漆设计的文章中有描述(以及适当的傲慢)。

Constant Time Suffices

消息系统中使用的持久化数据结构通常是一个每个消费者的队列,带有相关的BTree或其他通用随机访问数据结构,用于维护有关消息的元数据。b树是可用的最通用的数据结构,它使得在消息系统中支持各种各样的事务和非事务语义成为可能。不过,它们的代价相当高:Btree操作的时间开销是O(log N)。通常O(log N)被认为本质上等同于常数时间,但对于磁盘操作则不是这样。磁盘寻道一次10毫秒,每个磁盘一次只能进行一次寻道,因此并行性受到了限制。因此,即使是少量的磁盘寻道也会导致非常高的开销。由于存储系统混合了非常快的缓存操作和非常慢的物理磁盘操作,因此当数据使用固定缓存增加时,树结构的性能通常是超线性的。数据加倍会比速度加倍更糟糕。

直观地说,持久队列可以构建在简单的读取和添加到文件的基础上,这通常是日志解决方案的情况。这种结构的优点是所有操作的时间复杂度都是O(1),读操作不会阻塞写操作,也不会相互阻塞。这具有明显的性能优势,因为性能与数据大小完全解耦——一台服务器现在可以充分利用大量廉价、低转速的1+TB SATA驱动器。虽然它们的seek性能很差,但对于大型读写来说,这些驱动器的性能是可以接受的,价格是它们的1/3,容量是它们的3倍。

在没有任何性能损失的情况下访问几乎无限的磁盘空间意味着我们可以提供一些消息传递系统中通常不存在的特性。例如,在Kafka中,我们可以将消息保留一段相对较长的时间(比如一周),而不是在消息被使用后立即删除它们。这为消费者带来了极大的灵活性,我们将对此进行描述。