在近日召开的 Kafka 峰会上,发布了一系列值得关注的重要公告。有些改动值得尝试,有些需要慎重考虑,才能转化为真正适合自己的实践方法。本文作者认为:不推荐使用 KSQL 和 Kafka Streams,因为缺少检查点机制以及存在随机排序等问题。 本文,我将首先简要陈述自己的观点,随后深入探讨我得出这一观点的原因。
我的观点是:不建议用户使用 Kafka Streams,因为其缺少检查点机制,也不具备随机排序等功能,而 KSQL 以 Kafka Streams 为基础,因此其同样继承了后者所固有的不少问题。
Kafka 并不是数据库,而是一套非常出色的消息传递系统。确实,直到现在也有很多人认定 Kafka 就是一套数据库,由于篇幅所限,我在本文中没法具体讨论这些问题。我也承认,除非真正学习并了解过 Kafka,否则大多数开发者都很难理解这两者之间的差异。
我发现,这样的对话与争论并没能真正把观点与论据区分开来。在这里我希望做好区分,跟大家好好聊聊这个话题。
KSQL 和 Kafka Streams 存在的问题
检查点机制
我认为这个问题是客观存在的,无可辩驳,Kafka Sterams 确实存在检查点机制问题。检查点可以说是操作分布式系统的基础。那么,到底什么是检查点呢?
Kafka 是一套分布式日志记录系统。在消息处理方面,Kafka 同时支持有状态与无状态两类。在无状态处理方面,用户只会接收一条消息,然后进行实际处理,非常简单。但一旦涉及有状态处理,情况就立刻变得不同。现在,开发者面对的第一个难题就是存储状态,只有这样才能在遇到错误时配合对应状态完成系统恢复。
对于 Kafka Streams,恢复工作看似非常简单,因为拥有重构状态所需要的全部消息。理论上,甚至能够通过一些方法将保存在 Kafka 中的消息控制在每键约一条的水平。
但这就没问题了吗?当然不是。因为每键一条消息,状态量仍然相当夸张。如果大家拥有上千亿个键,那就需要在状态主题中保存超过 1 千亿条消息,毕竟所有状态变更都被放置在了对应的状态变更主题内。随着键数量的进一步增加,状态的体积也会随之膨胀。
这种运营思路总结起来就是,一旦某个节点无法正常运行,则必须从主题中重播所有消息并将其插入数据库内。只有执行完成整个流程,处理才能恢复至原有状态并继续进行。
在极端情况或者发生人为错误的前提下,一切运行 Kafka Streams 作业的设备都有可能崩溃或者宕机,这意味着所有节点都必须重播所有状态变更消息,而后才能继续正常处理新的消息。
这种大规模重播可能会带来长达数小时的停机时间。不少 Kafka Streams 的潜在用户表示,他们估算出的停机时间至少达到 4 个小时。好的,就算 4 个小时,完成恢复流程之后,接下来还有这一时段内新增的大量消息。追上这部分进度,系统才算是真正回到运行正轨之上。
有鉴于此,数据库与处理框架往往采用检查点机制(在 Flink 中,这一机制被称为快照)。所谓检查点,是将当前整体态定入至持久存储(S3/HDFS)中。因此,一旦发生大规模故障,恢复程序将直接读取前一个检查点,重播该检查点之后的所有消息(通常在 1000 秒以内),以便快速恢复后续处理能力。总体而言,检查点支持下的恢复流程一般仅耗时几秒钟到几分钟不等。
可以看到,在检查点的帮助下,系统的停机时间将由 Kafka Streams 的数小时显著缩短至几秒和几分钟水平。对于实时系统,停机时间必须尽可能短,我们也必须尽最大努力确保分布式系统能够尽快从故障中恢复过来。
随机排序
随机排序是分布式处理流程的重要组成部分,其本质是将数据与同一个键整合起来的实现方法。如果需要对数据进行分析,则很可能会接触到随机排序。
事实上,Kafka Streams 的随机排序与 Flink 或者 Spark Streaming 中的随机排序存在巨大差异。下面来看看 JavaDoc 中关于其工作原理的描述:
如果某个键变更运算符在实际使用之前发生了变化(例如 selectKey(KeyValueMapper)、map(KeyValueMapper), flatMap(KeyValueMapper) 或者 transform(TransformerSupplier, String…)),且此后没有发生数据重新分发(例如通过 through(String)),那么在 Kafka 当中创建一个内部重新分区主题。该主题将被命名为“${applicationId}-XXX-repartition”的形式,其中,“applicationId”由用户在 StreamsConfig 中通过 APPLICATION_ID_CONFIG 参数进行指定,“XXX”为内部生成的名称,而“-repartition”则为固定后缀。开发者可以通过 KafkaStreams.toString() 检索所有已生成的内部主题名称。
这意味着只要变更键(一般用于分析),Kafka Streams 就会新建一个主题来实现随机排序。这种随机排序实现方法,证实了我在与开发者沟通时做出的几个基本假设:
我曾与多位前 Kafka Streams 开发者进行过交流,他们并不清楚这种新的主题机制。他们直接在集群上执行实时分析,但这会快速增加代理上的负载与数据总量,并最终导致系统崩溃。但从使用者的角度来看,他们只是在正常执行数据处理。
如果开发者对性能并不关注,那么这种方法似乎也能接受。但是,其他一些处理框架(例如 Apache Flink)显然更加理想,它们提供更完善的内置随机排序功能,而且也能与 Kafka 配合使用。这些系统,无疑能够带来更顺畅的使用体验。
结论 由于缺少两大关键功能,Kafka Streams 实用性会大打折扣。我们不可能接受在实时生产系统上经历长达数小时的停机,无法接受随机排序功能导致集群崩溃。而且除非在每一项 KSQL 查询之前都进行解释,否则我们也弄不明白可能出现哪些随机排序操作。
怎么做?
不要过分纠结没有实际价值的架构问题
在本次召开的 Kafka 峰会上,其中不少演讲纠结于那些根本没有实际使用的架构问题。例如,他们谈到数据库是负责执行数据处理的环境,确实某些小型数据架构以及不少数据仓库方案都在使用数据库进行数据处理。但我从没见过有哪些大数据架构会采用这样的处理方式,毕竟数据库的可扩展性一直是个大问题。长久以来,我们一直使用具有可扩展能力的处理引擎解决这方面需求,这也成为大数据领域的一种客观标准。
他们还将 KSQL 硬性拔高,表示其能够完成一部分目前由大数据生态系统项目处理的任务。在他们看来,目前生态系统项目过多已经成为新的问题。大数据生态系统中包含太多技术方案,每一项技术负责解决或处理一种特定用例。组织当然可以剔除掉部分技术方案,但这会大大减慢处理速度,甚至影响处理用例的能力。话虽没错,但就算是配合这次公布的几项新功能,KSQL 也仍不足以真正满足组织的处理需求。
这不禁让我好奇,为什么要积极鼓吹这么多新的使用场景?在我看来,此次峰会中的不少发布内容并不科学。很明显,Kafka 确实不适合处理一些数据库的任务。长期存储的最佳选项,仍然是 S3 或者 HDFS。文件系统与 Kafka 无论在性能还是存储成本上,都有着巨大差别。
我鼓励各位架构师认真研究这种差别,目前只有两种方法能够通过时间戳或者提交 ID 的方式访问 Kafka 中的历史数据。一般来讲,用例需要使用 where 子句进行随机访问,这一点我们看到 KSQL 开始进行尝试处理。然而,针对随机访问读取的数据库优化绝非易事,不少大型科技企业都需要围绕这个问题建立专门的大规模工程技术团队。
Kafka 不是数据库
现实情况是,数据库要么位于代理进程中,要么处于具备稳定持久存储层的应用程序内。在我看来,使用 KSQL 通过 where 子句获取当前状态的架构并没有任何实际意义。其它成熟的架构已经能够轻松获取数据的当前状态,例如数据库或者带有检查点的其他处理程序。
在主题演讲最后,Confluent(一家围绕 Kafka 建立的创业公司)提到新功能的出现并不是要替代所有数据库。我真心希望他们能再斟酌斟酌自己的发言。毕竟在列举的众多数据库用例中,Confluent 都已经开始利用 KSQL 替换数据库。这里我要再次强调:创建数据库绝非易事,而创建分布式数据库更是难上加难。
这篇文章的目的并不是打击 Kafka。如果充分理解 Kafka 的特性与用法,它绝对是一套非常强大的发布和订阅系统。因此,最重要的是了解如何正确使用 Kafka,而不能单纯被市场宣传牵着鼻子走。请记住,供应商的利益诉求与你的并不一定始终一致。他们的关注重点在于提升收入以及产品使用率,但为此推出的全新产品使用方式,未必符合开发者的最大利益。
因此,在决定采用实时处理机制时,请首先确保拥有清晰明确的业务案例,并认真比较批量处理与实时处理之间的优劣差异。这个业务案例可以来自当前场景,也可以着眼未来需求。只有确定其具备牢固的价值主张,我们才有必要尝试新的技术实现方法;如果没有,盲目行动只会带来更多问题。
最后,我建议大家认真了解 Kafka 架构的固有局限。其中一些可能不太引人注目,也有一些要求我们对分布式系统具有深刻的理解才能体会。请确保您所在组织的架构师们真正清楚这一切对业务以及用例的实际影响。否则,您所做的一切其实都只是在为供应商做嫁衣,甚至可能因此毁掉自己苦心积累起的业务体系。
原文链接:
https://www.jesse-anderson.com/2019/10/why-i-recommend-my-clients-not-use-ksql-and-kafka-streams/
评论 1 条评论