learn.lianglianglee.com/专栏/Kafka核心技术与实战/40 Kafka Streams与其他流处理平台的差异在哪里?.md.html
2022-05-11 18:57:05 +08:00

1039 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>40 Kafka Streams与其他流处理平台的差异在哪里.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/Kafka核心技术与实战/00 开篇词 为什么要学习Kafka.md.html">00 开篇词 为什么要学习Kafka.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/01 消息引擎系统ABC.md.html">01 消息引擎系统ABC.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/02 一篇文章带你快速搞定Kafka术语.md.html">02 一篇文章带你快速搞定Kafka术语.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/03 Kafka只是消息引擎系统吗.md.html">03 Kafka只是消息引擎系统吗.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/04 我应该选择哪种Kafka.md.html">04 我应该选择哪种Kafka.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/05 聊聊Kafka的版本号.md.html">05 聊聊Kafka的版本号.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/06 Kafka线上集群部署方案怎么做.md.html">06 Kafka线上集群部署方案怎么做.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/07 最最最重要的集群参数配置(上).md.html">07 最最最重要的集群参数配置(上).md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/08 最最最重要的集群参数配置(下).md.html">08 最最最重要的集群参数配置(下).md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/09 生产者消息分区机制原理剖析.md.html">09 生产者消息分区机制原理剖析.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/10 生产者压缩算法面面观.md.html">10 生产者压缩算法面面观.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/11 无消息丢失配置怎么实现?.md.html">11 无消息丢失配置怎么实现?.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/12 客户端都有哪些不常见但是很高级的功能?.md.html">12 客户端都有哪些不常见但是很高级的功能?.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/13 Java生产者是如何管理TCP连接的.md.html">13 Java生产者是如何管理TCP连接的.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/14 幂等生产者和事务生产者是一回事吗?.md.html">14 幂等生产者和事务生产者是一回事吗?.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/15 消费者组到底是什么?.md.html">15 消费者组到底是什么?.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/16 揭开神秘的“位移主题”面纱.md.html">16 揭开神秘的“位移主题”面纱.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/17 消费者组重平衡能避免吗?.md.html">17 消费者组重平衡能避免吗?.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/18 Kafka中位移提交那些事儿.md.html">18 Kafka中位移提交那些事儿.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/19 CommitFailedException异常怎么处理.md.html">19 CommitFailedException异常怎么处理.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/20 多线程开发消费者实例.md.html">20 多线程开发消费者实例.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/21 Java 消费者是如何管理TCP连接的.md.html">21 Java 消费者是如何管理TCP连接的.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/22 消费者组消费进度监控都怎么实现?.md.html">22 消费者组消费进度监控都怎么实现?.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/23 Kafka副本机制详解.md.html">23 Kafka副本机制详解.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/24 请求是怎么被处理的?.md.html">24 请求是怎么被处理的?.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/25 消费者组重平衡全流程解析.md.html">25 消费者组重平衡全流程解析.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/26 你一定不能错过的Kafka控制器.md.html">26 你一定不能错过的Kafka控制器.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/27 关于高水位和Leader Epoch的讨论.md.html">27 关于高水位和Leader Epoch的讨论.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/28 主题管理知多少.md.html">28 主题管理知多少.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/29 Kafka动态配置了解下.md.html">29 Kafka动态配置了解下.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/30 怎么重设消费者组位移?.md.html">30 怎么重设消费者组位移?.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/31 常见工具脚本大汇总.md.html">31 常见工具脚本大汇总.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/32 KafkaAdminClientKafka的运维利器.md.html">32 KafkaAdminClientKafka的运维利器.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/33 Kafka认证机制用哪家.md.html">33 Kafka认证机制用哪家.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/34 云环境下的授权该怎么做?.md.html">34 云环境下的授权该怎么做?.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/35 跨集群备份解决方案MirrorMaker.md.html">35 跨集群备份解决方案MirrorMaker.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/36 你应该怎么监控Kafka.md.html">36 你应该怎么监控Kafka.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/37 主流的Kafka监控框架.md.html">37 主流的Kafka监控框架.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/38 调优Kafka你做到了吗.md.html">38 调优Kafka你做到了吗.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/39 从0搭建基于Kafka的企业级实时日志流处理平台.md.html">39 从0搭建基于Kafka的企业级实时日志流处理平台.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kafka核心技术与实战/40 Kafka Streams与其他流处理平台的差异在哪里.md.html">40 Kafka Streams与其他流处理平台的差异在哪里.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/41 Kafka Streams DSL开发实例.md.html">41 Kafka Streams DSL开发实例.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/42 Kafka Streams在金融领域的应用.md.html">42 Kafka Streams在金融领域的应用.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/加餐 搭建开发环境、阅读源码方法、经典学习资料大揭秘.md.html">加餐 搭建开发环境、阅读源码方法、经典学习资料大揭秘.md.html</a>
</li>
<li>
<a href="/专栏/Kafka核心技术与实战/结束语 以梦为马,莫负韶华!.md.html">结束语 以梦为马,莫负韶华!.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>40 Kafka Streams与其他流处理平台的差异在哪里</h1>
<p>你好我是胡夕。今天我要和你分享的主题是Kafka Streams 与其他流处理平台的差异。</p>
<p>近些年来,开源流处理领域涌现出了很多优秀框架。光是在 Apache 基金会孵化的项目,关于流处理的大数据框架就有十几个之多,比如早期的 Apache Samza、Apache Storm以及这两年火爆的 Spark 以及 Flink 等。</p>
<p>应该说,每个框架都有自己独特的地方,也都有自己的缺陷。面对这众多的流处理框架,我们应该如何选择呢?今天,我就来梳理几个主流的流处理平台,并重点分析一下 Kafka Streams 与其他流处理平台的差异。</p>
<h2>什么是流处理平台?</h2>
<p>首先,我们有必要了解一下流处理平台的概念。<a href="https://www.oreilly.com/library/view/streaming-systems/9781491983867/ch01.html">“Streaming Systems”</a>一书是这么定义“流处理平台”的:<strong>流处理平台Streaming System是处理无限数据集Unbounded Dataset的数据处理引擎而流处理是与批处理Batch Processing相对应的。</strong></p>
<p>所谓的无限数据是指数据永远没有尽头。流处理平台是专门处理这种数据集的系统或框架。当然这并不是说批处理系统不能处理这种无限数据集只是通常情况下它更擅长处理有限数据集Bounded Dataset</p>
<p>那流处理和批处理究竟该如何区分呢?下面这张图应该能帮助你快速且直观地理解它们的区别。</p>
<p><img src="assets/2f8e72ce532cf1d05306cb8b78510bb2.png" alt="img" /></p>
<p>好了,现在我来详细解释一下流处理和批处理的区别。</p>
<p>长期以来,流处理给人的印象通常是低延时,但是结果不准确。每来一条消息,它就能计算一次结果,但由于它处理的大多是无界数据,可能永远也不会结束,因此在流处理中,我们很难精确描述结果何时是精确的。理论上,流处理的计算结果会不断地逼近精确结果。</p>
<p>但是,它的竞争对手批处理则正好相反。批处理能提供准确的计算结果,但往往延时很高。</p>
<p>因此,业界的大神们扬长避短,将两者结合在一起使用。一方面,利用流处理快速地给出不那么精确的结果;另一方面,依托于批处理,最终实现数据一致性。这就是所谓的<strong>Lambda 架构</strong></p>
<p>延时低是个很好的特性但如果计算结果不准确流处理是无法完全替代批处理的。所谓计算结果准确在教科书或文献中有个专属的名字叫正确性Correctness。可以这么说<strong>目前难以实现正确性是流处理取代批处理的最大障碍</strong>而实现正确性的基石是精确一次处理语义Exactly Once SemanticsEOS</p>
<p>这里的精确一次是流处理平台能提供的一类一致性保障。常见的一致性保障有三类:</p>
<ul>
<li>至多一次At most once语义消息或事件对应用状态的影响最多只有一次。</li>
<li>至少一次At least once语义消息或事件对应用状态的影响最少一次。</li>
<li>精确一次Exactly once语义消息或事件对应用状态的影响有且只有一次。</li>
</ul>
<p>注意,我这里说的都是<strong>对应用状态的影响</strong>。对于很多有副作用Side Effect的操作而言实现精确一次语义几乎是不可能的。举个例子假设流处理中的某个步骤是发送邮件操作当邮件发送出去后倘若后面出现问题要回滚整个流处理流程已发送的邮件是没法追回的这就是所谓的副作用。当你的流处理逻辑中存在包含副作用的操作算子时该操作算子的执行是无法保证精确一次处理的。因此我们通常只是保证这类操作对应用状态的影响精确一次罢了。后面我们会重点讨论 Kafka Streams 是如何实现 EOS 的。</p>
<p>我们今天讨论的流处理既包含真正的实时流处理也包含微批化Microbatch的流处理。<strong>所谓的微批化,其实就是重复地执行批处理引擎来实现对无限数据集的处理</strong>。典型的微批化实现平台就是<strong>Spark Streaming</strong></p>
<h2>Kafka Streams 的特色</h2>
<p>相比于其他流处理平台,<strong>Kafka Streams 最大的特色就是它不是一个平台</strong>至少它不是一个具备完整功能Full-Fledged的平台比如其他框架中自带的调度器和资源管理器就是 Kafka Streams 不提供的。</p>
<p>Kafka 官网明确定义 Kafka Streams 是一个<strong>Java 客户端库</strong>Client Library<strong>你可以使用这个库来构建高伸缩性、高弹性、高容错性的分布式应用以及微服务</strong></p>
<p>使用 Kafka Streams API 构建的应用就是一个普通的 Java 应用程序。你可以选择任何熟悉的技术或框架对其进行编译、打包、部署和上线。</p>
<p>在我看来,这是 Kafka Streams 与 Storm、Spark Streaming 或 Flink 最大的区别。</p>
<p>Java 客户端库的定位既可以说是特色,也可以说是一个缺陷。目前 Kafka Streams 在国内推广缓慢的一个重要原因也在于此。毕竟,很多公司希望它是一个功能完备的平台,既能提供流处理应用 API也能提供集群资源管理与调度方面的能力。所以这个定位到底是特色还是缺陷仁者见仁、智者见智吧。</p>
<h2>Kafka Streams 与其他框架的差异</h2>
<p>接下来我从应用部署、上下游数据源、协调方式和消息语义保障Semantic Guarantees4 个方面,详细分析一下 Kafka Streams 与其他框架的差异。</p>
<h3>应用部署</h3>
<p>首先,我们从流处理应用部署方式上对 Kafka Streams 及其他框架进行区分。</p>
<p>我们刚刚提到过Kafka Streams 应用需要开发人员自行打包和部署,你甚至可以将 Kafka Streams 应用嵌入到其他 Java 应用中。因此,作为开发者的你,除了要开发代码之外,还要自行管理 Kafka Streams 应用的生命周期,要么将其打包成独立的 jar 包单独运行,要么将流处理逻辑嵌入到微服务中,开放给其他服务调用。</p>
<p>但不论是哪种部署方式,你需要自己处理,不要指望 Kafka Streams 帮你做这些事情。</p>
<p>相反地,其他流处理平台则提供了完整的部署方案。我以 Apache Flink 为例来解释一下。在 Flink 中,流处理应用会被建模成单个的流处理计算逻辑,并封装进 Flink 的作业中。类似地Spark 中也有作业的概念,而在 Storm 中则叫拓扑Topology。作业的生命周期由框架来管理特别是在 Flink 中Flink 框架自行负责管理作业,包括作业的部署和更新等。这些都无需应用开发人员干预。</p>
<p>另外Flink 这类框架都存在<strong>资源管理器</strong>Resource Manager的角色。一个作业所需的资源完全由框架层的资源管理器来支持。常见的资源管理器如 YARN、Kubernetes、Mesos 等,比较新的流处理框架(如 Spark、Flink 等)都是支持的。像 Spark 和 Flink 这样的框架,也支持 Standalone 集群的方式,即不借助于任何已有的资源管理器,完全由集群自己来管理资源。这些都是 Kafka Streams 无法提供的。</p>
<p>因此从应用部署方面来看Kafka Streams 更倾向于将部署交给开发人员来做,而不是依赖于框架自己实现。</p>
<h3>上下游数据源</h3>
<p>谈完了部署方式的差异,我们来说说连接上下游数据源方面的差异。简单来说,<strong>Kafka Streams 目前只支持从 Kafka 读数据以及向 Kafka 写数据</strong>。在没有 Kafka Connect 组件的支持下Kafka Streams 只能读取 Kafka 集群上的主题数据,在完成流处理逻辑后也只能将结果写回到 Kafka 主题上。</p>
<p>反观 Spark Streaming 和 Flink 这类框架它们都集成了丰富的上下游数据源连接器Connector比如常见的连接器 MySQL、ElasticSearch、HBase、HDFS、Kafka 等。如果使用这些框架,你可以很方便地集成这些外部框架,无需二次开发。</p>
<p>当然,由于开发 Connector 通常需要同时掌握流处理框架和外部框架因此在实际使用过程中Connector 的质量参差不齐,在具体使用的时候,你可以多查查对应的<strong>jira 官网</strong>,看看有没有明显的“坑”,然后再决定是否使用。</p>
<p>在这个方面,我是有前车之鉴的。曾经,我使用过一个 Connector我发现它在读取 Kafka 消息向其他系统写入的时候似乎总是重复消费。费了很多周折之后,我才发现这是一个已知的 Bug而且早就被记录在 jira 官网上了。因此,我推荐你多逛下 jira也许能提前避开一些“坑”。</p>
<p><strong>总之,目前 Kafka Streams 只支持与 Kafka 集群进行交互,它没有提供开箱即用的外部数据源连接器。</strong></p>
<h3>协调方式</h3>
<p>在分布式协调方面Kafka Streams 应用依赖于 Kafka 集群提供的协调功能,来提供<strong>高容错性和高伸缩性</strong></p>
<p><strong>Kafka Streams 应用底层使用了消费者组机制来实现任意的流处理扩缩容</strong>。应用的每个实例或节点,本质上都是相同消费者组下的独立消费者,彼此互不影响。它们之间的协调工作,由 Kafka 集群 Broker 上对应的协调者组件来完成。当有实例增加或退出时,协调者自动感知并重新分配负载。</p>
<p>我画了一张图来展示每个 Kafka Streams 实例内部的构造,从这张图中,我们可以看出,每个实例都由一个消费者实例、特定的流处理逻辑,以及一个生产者实例组成,而这些实例中的消费者实例,共同构成了一个消费者组。</p>
<p><img src="assets/c2ef5ebb19a75f3a16017c0c1f7a9a0b.png" alt="img" /></p>
<p>通过这个机制Kafka Streams 应用同时实现了<strong>高伸缩性和高容错性</strong>,而这一切都是自动提供的,不需要你手动实现。</p>
<p>而像 Flink 这样的框架它的容错性和扩展性是通过专属的主节点Master Node全局来协调控制的。</p>
<p>Flink 支持通过 ZooKeeper 实现主节点的高可用性,避免单点失效:<strong>某个节点出现故障会自动触发恢复操作</strong><strong>这种全局性协调模型对于流处理中的作业而言非常实用,但不太适配单独的流处理应用程序</strong>。原因就在于它不像 Kafka Streams 那样轻量级,应用程序必须要实现特定的 API 来开启检查点机制checkpointing同时还需要亲身参与到错误恢复的过程中。</p>
<p>应该这样说在不同的场景下Kafka Streams 和 Flink 这种重量级的协调模型各有优劣。</p>
<h3>消息语义保障</h3>
<p>我们刚刚提到过 EOS目前很多流处理框架都宣称它们实现了 EOS也包括 Kafka Streams 本身。关于精确一次处理语义,有一些地方需要澄清一下。</p>
<p>实际上,当把 Spark、Flink 与 Kafka 结合使用时,如果不使用 Kafka 在 0.11.0.0 版本引入的幂等性 Producer 和事务型 Producer这些框架是无法实现端到端的 EOS 的。</p>
<p>因为这些框架与 Kafka 是相互独立的,彼此之间没有任何语义保障机制。但如果使用了事务机制,情况就不同了。这些外部系统利用 Kafka 的事务机制,保障了消息从 Kafka 读取到计算再到写入 Kafka 的全流程 EOS。这就是所谓的端到端精确一次处理语义。</p>
<p>之前 Spark 和 Flink 宣称的 EOS 都是在各自的框架内实现的,无法实现端到端的 EOS。只有使用了 Kafka 的事务机制,它们对应的 Connector 才有可能支持端到端精确一次处理语义。</p>
<p>Spark 官网上明确指出了<strong>用户若要实现与 Kafka 的 EOS必须自己确保幂等输出和位移保存在同一个事务中。如果你不能自己实现这套机制那么就要依赖于 Kafka 提供的事务机制来保证</strong></p>
<p>而 Flink 在 Kafka 0.11 之前也宣称提供 EOS不过是有前提条件的即每条消息对<strong>Flink 应用状态</strong>的影响有且只有一次。</p>
<p>举个例子,如果你使用 Flink 从 Kafka 读取消息,然后不加任何处理直接写入到 MySQL那么这个操作就是无状态的此时 Flink 无法保证端到端的 EOS。</p>
<p>换句话说Flink 最后写入到 MySQL 的 Kafka 消息可能有重复的。当然Flink 社区自 1.4 版本起正式实现了端到端的 EOS其基本设计思想正是基于 Kafka 0.11 幂等性 Producer 的两阶段提交机制。</p>
<p>两阶段提交2-Phase Commit2PC机制是一种分布式事务机制用于实现分布式系统上跨多个节点事务的原子性提交。下面这张图来自于神书“Designing Data-Intensive Applications”中关于 2PC 讲解的章节。它清晰地描述了一次成功 2PC 的过程。在这张图中,两个数据库参与到分布式事务的提交过程中,它们各自做了一些变更,现在需要使用 2PC 来保证两个数据库的变更被原子性地提交。如图所示2PC 被分为两个阶段Prepare 阶段和 Commit 阶段。只有完整地执行了这两个阶段,这个分布式事务才算是提交成功。</p>
<p><img src="assets/2d3915230d70d42627b41cec375dfa4d.png" alt="img" /></p>
<p>分布式系统中的 2PC 常见于数据库内部实现或以 XA 事务的方式供各种异质系统使用。Kafka 也借鉴了 2PC 的思想,在 Kafka 内部实现了基于 2PC 的事务机制。</p>
<p>但是,对于 Kafka Streams 而言,情况就不同了。它天然支持端到端的 EOS因为它本来就是和 Kafka 紧密相连的。</p>
<p>下图展示了一个典型的 Kafka Streams 应用的执行逻辑。</p>
<p><img src="assets/1b86724a906b88921d3281a2e1032520.jpg" alt="img" /></p>
<p>通常情况下,一个 Kafka Streams 需要执行 5 个步骤:</p>
<ol>
<li>读取最新处理的消息位移;</li>
<li>读取消息数据;</li>
<li>执行处理逻辑;</li>
<li>将处理结果写回到 Kafka</li>
<li>保存位置信息。</li>
</ol>
<p>这五步的执行必须是<strong>原子性</strong>的,否则无法实现精确一次处理语义。</p>
<p>在设计上Kafka Streams 在底层大量使用 Kafka 事务机制和幂等性 Producer 来实现多分区的原子性写入,又因为它只能读写 Kafka因此 Kafka Streams 很容易地就实现了端到端的 EOS。</p>
<p>总之,虽然 Flink 自 1.4 版本也提供与 Kafka 的 EOS但从适配性来考量的话应该说 Kafka Streams 与 Kafka 的适配性是最好的。</p>
<h2>小结</h2>
<p>好了,我们来小结一下。今天,我重点分享了 Kafka Streams 与其他流处理框架或平台的差异。总的来说Kafka Streams 是一个轻量级的客户端库,而其他流处理平台都是功能完备的流处理解决方案。这是 Kafka Streams 的特色所在,但同时可能也是缺陷。不过,我认为很多情况下我们并不需要重量级的流处理解决方案,采用轻量级的库 API 帮助我们实现实时计算是很方便的情形,我想,这或许是 Kafka Streams 未来的破局之路吧。</p>
<p>在专栏后面的内容中,我会详细介绍如何使用 Kafka Streams API 实现实时计算,并跟你分享一个实际的案例,希望这些能激发你对 Kafka Streams 的兴趣,并为你以后的探索奠定基础。</p>
<p><img src="assets/d2d0b922414c3776d34523946feeae0e.jpg" alt="img" /></p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kafka核心技术与实战/39 从0搭建基于Kafka的企业级实时日志流处理平台.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kafka核心技术与实战/41 Kafka Streams DSL开发实例.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709972234a183d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>