如果你使用了像Kafka这样的流式处理平台,就要搞清楚一件事情:你需要用到哪些主题?特别是如果你要将一堆不同的事件作为消息发布到Kafka,是将它们放在同一个主题中,还是将它们拆分到不同的主题中?

Kafka主题最重要的一个功能是可以让消费者指定它们想要消费的消息子集。在极端情况下,将所有数据放在同一个主题中可能不是一个好主意,因为这样消费者就无法选择它们感兴趣的事件——它们需要消费所有的消息。另一种极端情况,拥有数百万个不同的主题也不是一个好主意,因为Kafka的每个主题都是有成本的,拥有大量主题会损害性能。

实际上,从性能的角度来看,分区数量才是关键因素。在Kafka中,每个主题至少对应一个分区,如果你有n个主题,至少会有n个分区。不久之前,Jun Rao写了一篇博文,解释了拥有多个分区的成本(端到端延迟、文件描述符、内存开销、发生故障后的恢复时间)。根据经验,如果你关心延迟,那么每个节点分配几百个分区就可以了。如果每个节点的分区数量超过成千上万个,就会造成较大的延迟。

相关厂商内容

实时监控业务质量现状

机器学习在大规模服务器治理复杂场景的实践

如何构建微服务下的性能监控

基于NEO区块链的专家网络应用实践

2018年,你应该关注这些运维技术热点

相关赞助商

CNUTCon全球运维技术大会,11月16日-17日,上海,|https://cnutcon2018.geekbang.org/?utm_source=infoq&utm_medium=vcrbox]]

关于性能的讨论为设计主题结构提供了一些指导:如果你发现自己有数千个主题,那么将一些细粒度、低吞吐量的主题合并到粗粒度主题中可能是个明智之举,这样可以避免分区数量蔓延。

然而,性能并不是我们唯一关心的问题。在我看来,更重要的是主题结构的数据完整性和数据模型。我们将在本文的其余部分讨论这些内容。

主题等于相同类型事件的集合?

人们普遍认为应该将相同类型的事件放在同一主题中,不同的事件类型应该使用不同的主题。这种思路让我们联想到关系型数据库,其中表是相同类型记录的集合,于是我们就有了数据库表和Kafka主题之间的类比。

Confluent Avro Schema Registry进一步强化了这种概念,因为它鼓励你对主题的所有消息使用相同的Avro模式(schema)。模式可以在保持兼容性的同时进行演化(例如通过添加可选字段),但所有消息都必须符合某种记录类型。稍后我会再回过头来讨论这个问题。

对于某些类型的流式数据,例如活动事件,要求同一主题中所有消息都符合相同的模式,这是合理的。但是,有些人把Kafka当成了数据库来用,例如事件溯源,或者在微服务之间交换数据。对于这种情况,我认为是否将主题定义为具有相同模式的消息集合就不那么重要了。这个时候,更重要的是主题分区中的消息必须是有序的。

想象一下这样的场景:你有一个实体(比如客户),这个实体可能会发生许多不同的事情,比如创建客户、客户更改地址、客户向帐户中添加新的信用卡、客户发起客服请求,客户支付账单、客户关闭帐户。

这些事件之间的顺序很重要。例如,我们希望其他事件必须在创建客户之后才能发生,并且在客户关闭帐户之后不能再发生其他事件。在使用Kafka时,你可以将它们全部放在同一个主题分区中来保持它们的顺序。在这个示例中,你可以使用客户ID作为分区的键,然后将所有事件放在同一个主题中。它们必须位于同一主题中,因为不同的主题对应不同的分区,而Kafka是不保证分区之间的顺序的。

顺序问题

如果你为customerCreated、customerAddressChanged和customerInvoicePaid事件使用了不同的主题,那么这些主题的消费者可能就看不到这些事件之间的顺序。例如,消费者可能会看到一个不存在的客户做出的地址变更(这个客户尚未创建,因为相应的customerCreated事件可能发生了延迟)。

如果消费者暂停一段时间(比如进行维护或部署新版本),那么事件出现乱序的可能性就更高了。在消费者停止期间,事件继续发布,并且这些事件被存储在特定定的主题分区中。当消费者再次启动时,它会消费所有积压在分区中的事件。如果消费者只消费一个分区,那就没问题:积压的事件会按照它们存储的顺序依次被处理。但是,如果消费者同时消费几个主题,就会按任意顺序读取主题中数据。它可以先读取积压在一个主题上的所有数据,然后再读取另一个主题上积压的数据,或者交错地读取多个主题的数据。

因此,如果你将customerCreated、customerAddressChanged和customerInvoicePaid事件放在三个单独的主题中,那么消费者可能会在看到customerCreated事件之前先看到customerAddressChanged事件。因此,消费者很可能会看到一个客户的customerAddressChanged事件,但这个客户却未被创建。

你可能会想到为每条消息附加时间戳,并用它来对事件进行排序。如果你将事件导入数据仓库,再对事件进行排序,或许是没有问题的。但在流数据中只使用时间戳是不够的:在你收到一个具有特定时间戳的事件时,你不知道是否需要等待具有较早时间戳的事件,或者所有之前的事件是否已经在当前事情之前到达。依靠时钟进行同步通常会导致噩梦,有关时钟问题的更多详细信息,请参阅“Designing Data-Intensive Applications”的第8章。

何时拆分主题,何时合并主题?

基于这个背景,我将给出一些经验之谈,帮你确定哪些数据应该放在同一主题中,以及哪些数据应该放在不同的主题中。

  1. 首先,需要保持固定顺序的事件必须放在同一主题中(并且需要使用相同的分区键)。如果事件属于同一实体,那么事件的顺序就很重要。因此,我们可以说,同一实体的所有事件都应该保存在同一主题中。

    如果你使用事件溯源进行数据建模,事件的排序尤为重要。聚合对象的状态是通过以特定的顺序重放事件日志而得出的。因此,即使可能存在不同的事件类型,聚合所需要的所有事件也必须在同一主题中。

  2. 对于不同实体的事件,它们应该保存在相同的主题中还是不同的主题中?我想说,如果一个实体依赖于另一个实体(例如一个地址属于一个客户),或者经常需要同时用到它们,那么它们也应该保存在同一主题中。另一方面,如果它们不相关,并且属于不同的团队,那么最好将它们放在不同的主题中。

    另外,这也取决于事件的吞吐量:如果一个实体类型的事件吞吐量比其他实体要高很多,那么最好将它分成几个主题,以免让只想消费低吞吐量实体的消费者不堪重负(参见第4点)。不过,可以将多个具有低吞吐量的实体合并起来。

  3. 如果一个事件涉及多个实体该怎么办?例如,订单涉及到产品和客户,转账至少涉及到两个账户。

    我建议在一开始将这些事件记录为单个原子消息,而不是将其分成几个属于不同主题的消息。在记录事件时,最好可以保持原封不动,即尽可能保持数据的原始形式。你可以随后使用流式处理器来拆分复合事件,但如果过早进行拆分,想要重建原始事件会难得多。如果能够为初始事件分配一个唯一ID(例如UUID)就更好了,之后如果你要拆分原始事件,可以带上这个ID,从而可以追溯到每个事件的起源。

  4. 看看消费者需要订阅的主题数量。如果几个消费者都订阅了一组特定的主题,这表明可能需要将这些主题合并在一起。

    如果将细粒度的主题合并成粗粒度的主题,一些消费者可能会收到他们不需要的事件,需要将其忽略。这不是什么大问题:消费消息的成本非常低,即使最终忽略了一大半的事件,总的成本可能也不会很大。只有当消费者需要忽略绝大多数消息(例如99.9%是不需要的)时,我才建议将大容量事件流拆分成小容量事件流。

  5. 用作Kafka Streams状态存储(KTable)的变更日志主题应该与其他主题分开。在这种情况下,这些主题由Kafka Streams流程来管理,所以不应该包含其他类型的事件。

    最后,如果基于上述的规则依然无法做出正确的判断,该怎么办?那么就按照类型对事件进行分组,把相同类型的事件放在同一个主题中。不过,我认为这条规则是最不重要的。

模式管理

如果你的数据是普通文本(如JSON),而且没有使用静态的模式,那么就可以轻松地将不同类型的事件放在同一个主题中。但是,如果你使用了模式编码(如Avro),那么在单个主题中保存多种类型的事件则需要考虑更多的事情。

如上所述,基于Avro的Kafka Confluent Schema Registry假设了一个前提,即每个主题都有一个模式(更确切地说,一个模式用于消息的键,一个模式用于消息的值)。你可以注册新版本的模式,注册表会检查模式是否向前和向后兼容。这样设计的一个好处是,你可以让不同的生产者和消费者同时使用不同版本的模式,并且仍然保持彼此的兼容性。

Confluent的Avro序列化器通过subject名称在注册表中注册模式。默认情况下,消息键的subject为<topic>-key,消息值的subject为<topic>-value。模式注册表会检查在特定subject下注册的所有模式的相互兼容性。

最近,我为Avro序列化器提供了一个补丁(https://github.com/confluentinc/schema-registry/pull/680),让兼容性检查变得更加灵活。这个补丁添加了两个新的配置选项:key.subject.name.strategy(用于定义如何构造消息键的subject名称)和value.subject.name.strategy(用于定义如何构造消息值的subject名称)。它们的值可以是如下几个:

  • io.confluent.kafka.serializers.subject.TopicNameStrategy(默认):消息键的subject名称为<topic>-key,消息值为<topic>-value。这意味着主题中所有消息的模式必须相互兼容。
  • io.confluent.kafka.serializers.subject.RecordNameStrategy:subject名称是Avro记录类型的完全限定名。因此,模式注册表会检查特定记录类型的兼容性,而不管是哪个主题。这个设置允许同一主题包含不同类型的事件。
  • io.confluent.kafka.serializers.subject.TopicRecordNameStrategy:subject名称是<topic>-<type>,其中<topic>是Kafka主题名,<type>是Avro记录类型的完全限定名。这个设置允许同一主题包含不同类型的事件,并进一步对当前主题进行兼容性检查。

有了这个新特性,你就可以轻松地将属于特定实体的所有不同类型的事件放在同一个主题中。现在,你可以自由选择主题的粒度,而不仅限于一个类型对应一个主题。

英文原文:http://martin.kleppmann.com/2018/01/18/event-types-in-kafka-topic.html

Kafka实践:到底该不该把不同类型的消息放在同一个主题中相关推荐

  1. Kafka实践指南:快速掌握部署使用与常用命令

    Kafka部署使用 Kafka部署使用 Kafka定义和特性 Kafka架构和组成部分 Kafka工作原理和消息传递过程 Kafka安装与配置 安装Kafka 配置Kafka集群 Kafka的主题和分 ...

  2. 刨根问底,Kafka消息中间件到底会不会丢消息

    大型互联网公司一般都会要求消息传递最大限度的不丢失,比如用户服务给代金券服务发送一个消息,如果消息丢失会造成用户未收到应得的代金券,最终用户会投诉. 为避免上面类似情况的发生,除了做好补偿措施,更应该 ...

  3. kafka 重复消费和数据丢失_刨根问底,Kafka消息中间件到底会不会丢消息

    大型互联网公司一般都会要求消息传递最大限度的不丢失,比如用户服务给代金券服务发送一个消息,如果消息丢失会造成用户未收到应得的代金券,最终用户会投诉. 为避免上面类似情况的发生,除了做好补偿措施,更应该 ...

  4. kafka python教程_由Flink与Kafka实践探究Kafka的两个问题

    笔者在某次实践过程中,搭建了一个Flink监控程序,监控wikipedia编辑,对编辑者编辑的字节数进行实时计算,最终把数据sink到kafka的消费者中展示出来,监控程序本身比较简单,只要在程序中指 ...

  5. kafka实践(十五): 滴滴开源Kafka管控平台 Logi-KafkaManager研究

    滴滴开源了其Kafka 监控与管控平台 Logi-KafkaManager,因为有30+个集群的维护经验,使用过kafka-manager,kafka-eagle,kafka-mirrorkaker工 ...

  6. 大数据中台之Kafka,到底好在哪里?

    来自:架构之美 Hello,大家好,今天给大家分享一个大数据里面很火的技术--Kafka,Kafka 是一个分布式的消息系统,其高性能在圈内很出名.本人阅读过多个大数据生态的开源技术的源码,个人感觉 ...

  7. kafka 实践指南

    综述 kafka 在使用中的的基本概念包括,zookeeper,broker,主题,分区,生产者,消费者,消费者群组. 其中zookeeper用于协调broker中的元数据,对整个kafka状态以及元 ...

  8. 对 Kafka 和 Pulsar 进行性能测试后,拉卡拉将消息平台统一换成了 Pulsar

    拉卡拉支付成立于 2005 年,是国内领先的第三方支付企业,致力于整合信息科技,服务线下实体,从支付切入,全维度为中小微商户的经营赋能.2011 年成为首批获得<支付业务许可证>企业的一员 ...

  9. Kafka主题中的分区数越多吞吐量就越高?BULLSHIT!!!

    欢迎跳转到本文原文地址:https://honeypps.com/mq/is-that-the-more-partitions-in-kafka-topic-the-higher-throughout ...

最新文章

  1. three.js 弹出二维图片
  2. 【tensorflow】tf.nn.conv2d的使用
  3. 字节码中的两个方法init,clinit
  4. 88. Leetcode 剑指 Offer 14- I. 剪绳子 (动态规划-基础题)
  5. java security 详解_Spring Security入门教程 通俗易懂 超详细 【内含案例】
  6. oracle outln用户,Oracle用户解锁
  7. Linux服务器jps报process information unavailable
  8. java paint绘图添加组件不能显示_java – 为什么paintComponent没有在面板上绘图?
  9. web-4. 装饰页面的图像
  10. C中取得数组的地址,赋值给数组结构的字段
  11. 存储器的分类整理(SRAM/DRAM/NOR FLASH/Nand FLASH)
  12. 利用python3将word批量转换成pdf
  13. 大数据营销在电商领域的应用案例
  14. 【Unity】 HTFramework框架(十四)Audio音频管理器
  15. 支付宝APP支付——支付流程说明及示例
  16. 华为手机android7价格,华为7怎么样?报价多少?
  17. 如何读取远程4G网络摄像头的视频流?
  18. 【感恩】为做运维的重病老同事李静波寻求帮助
  19. 【openjudge 计算概论(A)】[基础编程练习(运算成分)]
  20. java基础知识总结,javaweb参考资料大全

热门文章

  1. shell脚本每日一练(三)
  2. 如何把python可视化到前端_python数据可视化的效果如何在web页面中展示_北京可视化股票...
  3. python整商运算符_python中的运算符
  4. ajax带来的主要问题有哪些,ajax面试题
  5. hexo 菜单_Hexo 搭建个人博客教程 - 6 - 设置菜单,发布博客 - 2018
  6. android contacts电话查询头像,android透过查询电话号码获取联系人头像
  7. mysql select内部原理_数据库SQL SELECT查询的工作原理
  8. java url no protocol_httpurlconnection 新人使用遇到错误java.net.MalformedURLException: no protocol...
  9. @echo off是什么意思_为什么执行自己的程序要在前面加./
  10. callablestatement.setstring会不会将字符串trim_Java String:重要到别人只能当老二的字符串类