Redpanda:用C++重写的Kafka


Redpanda 是对 Kafka 的 C++ 重写,提供与 Kafka API 的 100% 兼容性。Redpanda 不需要 Zookeeper 或 JVM,因此在生产中操作起来不太复杂。因此,更广泛的开发人员受众的可访问性。

您可以在此处找到有关其他安装选项的更多信息。

Redpanda是一个基于附加日志的兼容Kafka的分布式流系统。与 Kafka 相比,Redpanda 旨在为用户提供更低的延迟和更低的操作复杂性。它在内部使用Raft 共识算法,而不是依赖于Zookeeper的单独安装。Redpanda 使用 Kafka 的有线协议:Redpanda 使用常规的 Kafka 客户端,而不是发布自己的客户端库。

像 Kafka 一样,Redpanda 提供了一组命名的、部分有序的日志,称为主题。每个主题被分片成一个或多个分区,每个分区是一个完全有序的消息日志消息在分区中的位置由唯一的单调递增整数offset标识,它提供了总顺序。偏移量可能是稀疏的:日志中的一些偏移量用于内部消息,例如事务元数据,这些对客户端是不可见的。

Kafka 客户端分为几个部分。用户使用生产者客户端向分区写入(产生)消息,并使用消费者客户端从分区中读取(轮询)消息。用户可以在手动指定的分区中产生和消费,或者允许系统自动选择消息写入哪个分区,以及消费者从哪个分区读取。

轮询消息不会删除它们。相反,消费者通过从给定分区读取连续偏移量来模拟“从队列中消费”。消费者既可以为自己分配特定的分区并自己管理偏移量,也可以订阅一个主题并允许 Redpanda 自动管理分区和偏移量。可以将偏移量提交给 Redpanda,它会持久地存储它们,以便崩溃的消费者可以从他们(或他们的前辈)停止的地方继续。

Redpanda 实现了 Kafka 的事务协议,但是这种支持在 22.1.1 版本中仍然存在一个功能标志。Redpanda 没有为用户提供有关如何使用事务的具体文档,而是依赖 Kafka 的文档。Kafka 的官方文档规定消费者可以在两个隔离级别之间进行选择:read_uncomitted和read_committed. read_uncommitted允许消费者查看“所有消息,甚至是已中止的事务消息”。该read_committed设置“将仅返回已提交的事务性消息”。

Confluence 的Transactional Messaging wiki 页面提供了 Kafka 中的事务应用程序所期望的五个简单要求。起初,对于试图理解事务语义的用户来说,这看起来像是一个很有希望的总结:

  1. 原子性:消费者的应用程序不应暴露于来自未提交事务的消息。
  2. 持久性:经纪人不能丢失任何已提交的交易。
  3. 排序:事务感知消费者应该在每个分区内看到原始事务顺序中的事务。
  4. 交错:每个分区都应该能够接受来自事务和非事务生产者的消息
  5. 事务中不应有重复的消息。

Redpanda 认为这个 wiki 页面在这两点上都是错误的。他们指出了一个有点难找的谷歌文档,它作为 Kafka 事务协议的设计文档,其中指出写入原子性是指作为一个单元的写入成功或失败,然后链接到上述wiki 页面,上面说原子性意味着预防中止读取。设计文档还提供了原子性无法保持的几种情况:

  1. 对于压缩主题,事务的某些消息可能会被较新版本覆盖。
  2. 事务可能跨越日志段。因此,当旧段被删除时,我们可能会在事务的第一部分丢失一些消息。
  3. 消费者可能会在交易中寻找任意点,因此会丢失一些初始消息。
  4. 消费者可能不会从参与事务的所有分区中消费。因此,他们将永远无法读取构成交易的所有消息。

考虑到 Kafka 的数据模型,这些都是合理的约束。但是,如果我们不使用压缩,不寻求任意偏移量,并且从参与事务的所有分区中消费,会发生什么?写入是否相互隔离?

具体来说:如果事务T 1在T 2之前提交,T 1写入的所有偏移量是否都在T 2写入的偏移量之前?许多事务系统缓冲它们的写入并在提交时或多或少地以原子方式应用它们,但仔细阅读此设计文档会发现 Kafka 并没有这样做。相反,Kafka 选择在事务中的每个请求发生时立即将写入添加到日志中——并且为了保持性能,在事务写入期间不锁定分区。这意味着来自两个不同事务的写入可能会在偏移量中交错。

那么消费者处理这些写入的顺序呢?设计文档的消费者部分解释了消息总是以偏移顺序传递。这表明 wiki 是不正确的,事务性写入应该明显交错。

熟悉其他数据库的读者可能知道,这些术语有现成的含义:至少从20世纪90年代中期开始,它们就已经被研究和形式化了。在Adya, Liskov, & O'Neil的形式主义中,读未提交防止了G0现象(写循环)。当两个(或多个)事务对一个或多个对象的写入交错进行时,就会发生写入循环。例如,事务T1在T2写X之前写了一些对象,而T2在T1写Y之前写了一些Y,给定了写X和Y的总顺序。G1a(中止读取),G1b(中间读取),和G1c(循环信息流)。终止读意味着一个事务观察到一个没有提交的事务所写的值。中间读涉及从一个事务的中间读取一个状态。循环信息流包括事务之间的依赖性循环,其中T1在T2写X之前写了一些X,或者T2读取了T1写的东西。当然,这些反常现象与寄存器上的读写历史有关,而不是日志,但我们可以想象Kafka的数据模型中的类似现象。

从所有这些来源来看,一个足够勤奋的读者可以得出结论,Kafka(因此Redpanda)事务允许G0,在read_committed时禁止G1a,并允许G1b。G1c(涉及写-写和写-读依赖关系的循环)是否会发生仍不清楚。


事务 ID
在 Kafka/Redpanda 中执行事务的每个生产者都必须选择一个事务 ID:一个含义不明确的字符串,但如果选择不正确,可能会导致事务系统表现出未定义的行为。

Kafka 事务设计文档有一个标题为“事务保证”的部分,详细说明了事务 ID 如何确保跨会话的幂等性和事务恢复:
当提供这样的 TransactionalId 时,Kafka 将保证:

  1. 跨应用程序会话的幂等生产。这是通过在具有相同 TransactionalId 的新实例上线时屏蔽旧代来实现的。
  2. 跨应用程序会话的事务恢复。如果应用程序实例死亡,则可以保证下一个实例已完成任何未完成的事务(无论是中止还是提交),从而在恢复工作之前使新实例处于干净状态。

详细点击标题