在 IT 领域,Apache Kafka(下文简称 Kafka)是目前针对分布式消息传递或流数据的最流行平台。使用任意类型的数据(日志、事件等)且需要传输该数据的所有应用程序,以及可能在其组件之间转换数据的所有应用程序,都可以从 Kafka 中获益。Kafka 最初是 LinkedIn 中的一个项目,后来为了方便采用而开放了源码。过去几年里,它一直是一个开源项目,而且成熟了许多。一些著名的 IT公司都在自己的生产环境中使用它。
Kafka 中的一些基本组件包括:
代理:Kafka 代理是存储发送给 Kafka 的数据的地方。代理负责在数据到达时进行接收和存储。代理还会在收到请求时提供这些数据。许多 Kafka 代理可以共同构建一个 Kafka 集群。Kafka 使用 Apache ZooKeeper 来存储有关集群的元数据。代理使用此元数据来检测故障(比如代理故障)并从中恢复。
生产者:生产者是向代理发送数据的实体。有许多不同类型的生产者。Kafka 提供了自己用 Java 编写的生产者,但其他许多 Kafka 客户端库支持使用 C/C++、Go、Python、REST 等语言。
使用者:使用者是从代理请求数据的实体。类似于生产者,除了内置的 Java 使用者之外,还有其他开源使用者可供对非 Java API 感兴趣的开发人员使用。
Kafka 将数据存储在主题中。生产者将数据发送给特定的 Kafka 主题,使用者从特定的主题读取数据。每个主题都有一个或多个分区。发送给主题的数据最终存储且仅存储在一个分区中。每个分区由一个代理托管,无法扩展到多个代理。
Kafka 在业界得到持续流行和采用有以下几个原因:
可伸缩性:Kafka 的两个重要特性造就了它的可伸缩性。
Kafka 集群在运行期间可以轻松地扩展或收缩(可以添加或删除代理),而不会宕机。
可以扩展一个 Kafka 主题来包含更多的分区。由于一个分区无法扩展到多个代理,所以它的容量受到代理磁盘空间的限制。能够增加分区和代理的数量意味着单个主题可以存储的数据量是没有限制的。
容错性和可靠性:Kafka 的设计方式使某个代理的故障能够被集群中的其他代理检测到。由于每个主题都可以在多个代理上复制,所以集群可以在不中断服务的情况下从此类故障中恢复并继续运行。
吞吐量:代理能够以超快的速度有效地存储和检索数据。
图 1 展示了一个包含 4 个代理的简单 Kafka 集群。此集群中存储了 3 个主题:t1、t2 和 t3。t1 有一个分区且被复制了 3 次,t2 和 t3 各有两个分区且被复制了两次。从此图中可以清楚地看到,这个集群可以在一个代理发生故障的情况下继续工作,而不会丢失任何数据。只有在代理 1 和 4 或代理 3 和 4 是故障对时,该集群才能在发生无损的双代理故障后继续工作。其他任何故障对都会导致部分数据丢失。
各种各样的生产者和使用者配置都可以使用此集群。例如:
客户端 1 可以生成到主题 1(充当生产者)
客户端 2 可以生成到主题 2(充当生产者)
客户端 3 可以从主题 1 和 2 读取数据,并将数据写到主题 3(同时充当使用者和生产者)
客户端 4 可以从主题 3 读取数据
在一些用例中,我们可以实时持续向一些主题传入数据。例如,主题 1 包含来自工厂中不同传感器的温度读数,而主题 2 拥有关于这些传感器的详细信息。然后,上述配置中的客户端 3 会持续接收温度读数,并与最新的传感器规格进行交叉检查,检测异常并在主题 3 中报告它们。在这个场景中,客户端 3 是一个简单的流应用程序,它从一个或多个 Kafka 主题中读取数据,执行一些处理,并将输出写到另一个 Kafka 主题,所有这些都是实时的。
实时分析来自 IoT 设备的数据或网站上的用户操作是 Kafka Streams 可以轻松处理的两个基本示例。本文末尾引用的 Kafka Streams 文档中列出了其他一些用例。
由于上面描述的特性,Kafka 成为了数据传输和 ETL 场景的一种流行选择。事实上,Kafka Streams API 是 Kafka 的一部分,它简化了流应用程序的编写,该程序负责处理传输中的数据。平心而论,Kafka 是一个批处理消息传递平台,现在它已成为一个受欢迎的流处理平台。Kafka Streams 甚至得到了另一个名为 KSQL 的开源项目的增强,该项目极大地简化了使用 SQL 类声明来编写 Kafka Streams 应用程序的过程。
Kafka JCA 适配器的设计方法为标准企业 Java 解决方案提供了 JMS 与 Kafka 事件处理平台的"即插即用式"集成。此设计支持将 Kafka 与现有企业应用程序无缝集成,而无需实施补偿逻辑。通过该适配器,应用程序服务器还可以提供企业应用程序所依赖的基础架构和运行时环境,以用于建立 Kafka 连接并执行事务管理。
世界已经迈进"移动"时代,现在应用程序必须能够实时提供数据,这不仅包括数据库表中存储的重要最终结果,还包括用户使用应用程序时执行的所有操作。任何可用信息,例如,用户点击量、日志数据或传感器数据都可用于改善用户体验、生成报告、向机器学习系统提供数据,等等。现如今,开发者必须关注基于实时事件流的系统。
下图展示了基于事件流处理的架构示例。
Apache Kafka 已成为构建基于事件的高度可扩展系统的首选解决方案。Kafka 可为事件流平台提供快速升级换代的功能,以供开发者在现代化业务解决方案中使用。然而,开发者通常需要将现有 Java EE 业务解决方案(基于 IBM MQ 或 IBM WebSphere Application Platform 之类的技术而构建的)集成到这些新的事件流架构中。
考虑以下示例: 某个网上商店拥有一个移动应用程序,该应用程序使用 Kafka 向企业 Java 中实施的分布式支付系统发送支付请求数据。该解决方案必须绝对保证一次性完成支付请求的处理(避免多次向买家收费)。但是,在分布式系统中,故障是不可避免的,因此该解决方案需要采用稳妥的方式来处理故障。
Apache Kafka 是一种用于事件流处理的分布式系统,广泛应用于微服务架构和基于云的环境中。它在同一个平台内提供事件的消息传递、存储和处理。
下图展示了 Apache Kafka 组件的基本拓扑,其中包括通过 Kafka 集群基础架构交换消息的生产者和使用者。
即使 Kafka 具有诸多优势,但 Kafka 仍面临如下问题:
消息处理失败时需要实施手动补偿逻辑,这可能导致无法处理消息
不支持 XA 事务处理
确保在使用者应用程序中仅执行一次交付处理
需要完成额外的开发和可维护性工作才能将其集成到企业解决方案中
要解决 Kafka 集成问题,您可以应用传统消息传递拓扑概念,例如,事务日志、恢复日志和 XA 事务。 您可以实施基于 Java EE Connector Architecture (JCA) 的资源适配器。利用此 JCA 资源适配器,您可以为应用程序服务器提供 ACID 功能,以便进行 Kafka 消息处理。此 JCA 资源适配器随后可提供与企业 Java 应用程序的无缝 Kafka 集成。
Java EE Connector Architecture 可定义一组可扩展且十分安全的事务性机制。您可以将 JCA 资源适配器安装到兼容 Java EE 的任意应用程序服务器中,例如,IBM Websphere Application Server、IBM Business Process Manager、JBoss、WebSphere Liberty、Glassfish 或 Weblogic。
Java EE Connector Architecture 规范还提供了一组标准合约,用于支持企业应用程序与企业信息系统(如 Kafka)之间的通信。JCA 资源适配器可以插入到应用程序服务器,可通过处理所有系统级别的机制(事务、连接管理、崩溃恢复、错误跟踪和日志记录)来支持 Kafka 集成。JCA 资源适配器将对需要与之集成的企业应用程序隐藏所有 Kafka 通信逻辑。通过实施 JCA 资源适配器,企业应用程序提供商可以集中精力实施业务和演示逻辑,而无需担心与 Kafka 集成相关的低级别逻辑。因此,JCA 资源适配器只需开发一次,即可供各种应用程序复用。
让我们将它与网上商店支付场景联系起来,观察下图,它展示了指定的解决方案系统上下文。
移动应用程序向 Kafka发送支付请求数据, 该Kafka 已通过资源适配器与企业支付应用程序进行了集成。此外,还可以使用此适配器向 Kafka 推送支付通知。此适配器会启动 XA事务,该事务将传递到企业支付应用程序和通知系统。因此,与支付请求处理相关的所有任务都将在同一个全局事务内运行,并且同时完成或者同时失败。除了从中读取数据或向其中写入数据的主题外,该设计还在Kafka 上设置了重试、死信和事务日志主题。
现在,我们来更详细地探索与移动应用程序往来的消息的处理过程。
在我们的支付场景中,传入流表示由网上商店移动应用程序发起的通信,该应用程序会向 Kafka 发送支付请求数据。资源适配器提供了 Kafka 连接,并向应用程序服务器上存在的消息端点异步传递消息。可使用 JCA 规范所定义的消息传入流合约来实现这一点。
Kafka JCA 资源适配器会实施激活规范 JavaBean,其中包含一组用于端点激活配置的配置属性。这些配置详细信息将作为应用程序服务器配置的一部分来进行定义。
资源适配器会定期从传入 Kafka 主题轮询一批支付请求。成功完成数据轮询后,它会迭代数据批次,并异步向端点实例传递消息。每个消息端点可能存在多个端点实例,因此能够并行使用消息并提供高吞吐量。
Kafka 使用者偏移在安排消息送达后立即落实,从而避免了批次受阻的问题。这种设计是可行的,因为该资源适配器通过需要在 Kafka 上设置的重试、死信和事务日志主题来实施故障转移过程。在我们的例子中,端点需要支持 XA 事务,并且需要在向端点发送数据之前创建事务上下文,从而提供原子消息使用。
如果应用程序服务器异常终止了事务,那么由端点实例执行的所有工作都应回滚,并且消息应转发到 Kafka 重试主题。
适配器使用来自 Kafka 重试主题的消息,并对其进行重新处理。超出已配置的消息处理重试次数后,该适配器会将此消息传递到 Kafka 死信主题。发送到死信主题的消息包含有价值的业务数据,因此监视该主题至关重要。
传出流表示由企业应用程序发起的 Kafka 通信。在我们的例子中,这是用于向移动应用程序发送支付确认的通知系统。JCA 规范定义了一个连接管理合约,可让应用程序服务器合并 Kafka 连接,从而提供支持大量客户端的可扩展环境。
Kafka 传出连接配置详细信息是使用 Managed Connection Factory JavaBean 进行定义的。利用这些配置详细信息,管理员和开发者可使用适配器来配置 Kafka 生产者,并决定所需的功能,例如,可靠性、可用性、吞吐量、延迟和事务支持。这些配置详细信息将作为应用程序服务器配置的一部分来进行定义。
Kafka JCA 资源适配器将公开用于实施公共客户端接口 (CCI) 和 Java 消息服务 (JMS) 接口的 Kafka Connection Factory 和 Kafka Connection。应用程序组件会使用 Java 命名和目录接口 (JNDI) 名称来查找连接工厂。成功获取连接工厂后,应用程序会使用它来获取连接,以便访问 Kafka。这样,您就可以为通知系统应用程序无缝添加 Kafka 集成,该应用程序当前将数据发送到 JMS 消息传递提供程序(如 IBM MQ 或 Active MQ)。
资源适配器传出流会封装低级别的 Kafka 通信逻辑,并提供:
连接合并
使用 Kafka 事务性机制来保证仅传递一次
采用稳妥的方式来识别、记录和处理 Kafka 故障
实施 XA 事务,从而在分布式系统中通过 Kafka 提供可靠的消息处理
要在传出流中管理事务,Kafka 资源适配器可使用由 JCA 规范定义的事务管理合约。
在我们的例子中,连接工厂需要设置为支持 XA 事务,该适配器需要在客户端获取连接时启动 Kafka 事务。无论应用程序服务器何时回滚 Kafka 事务,该事务都会异常终止。如果发生 XA 事务落实,那么事务管理器会在正在运行的事务所使用的所有资源上执行两阶段落实协议。这可保证对受管资源的所有读写访问权要么全部落实,要么全部回滚。
最后,该资源适配器会通过向 Kafka 事务日志主题写入事务数据来跟踪正在运行的事务。写入事务日志主题的数据将用于崩溃恢复处理,以便在分布式系统中提供可靠的消息处理。
消息队列技术是分布式应用间交换信息的一种技术。消息队列可驻留在内存或磁盘上, 队列存储消息直到它们被应用程序读走。通过消息队列,应用程序可独立地执行--它们不需要知道彼此的位置、或在继续执行前不需要等待接收程序接收此消息。在分布式计算环境中,为了集成分布式应用,开发者需要对异构网络环境下的分布式应用提供有效的通信手段。为了管理需要共享的信息,对应用提供公共的信息交换机制是重要的。常用的消息队列技术是 Message Queue。
Message Queue 的通讯模式
点对点通讯:点对点方式是最为传统和常见的通讯方式,它支持一对一、一对多、多对多、多对一等多种配置方式,支持树状、网状等多种拓扑结构。
多点广播:MQ 适用于不同类型的应用。其中重要的,也是正在发展中的是"多点广播"应用,即能够将消息发送到多个目标站点 (Destination List)。可以使用一条 MQ 指令将单一消息发送到多个目标站点,并确保为每一站点可靠地提供信息。MQ 不仅提供了多点广播的功能,而且还拥有智能消息分发功能,在将一条消息发送到同一系统上的多个用户时,MQ 将消息的一个复制版本和该系统上接收者的名单发送到目标 MQ 系统。目标 MQ 系统在本地复制这些消息,并将它们发送到名单上的队列,从而尽可能减少网络的传输量。
发布/订阅 (Publish/Subscribe) 模式:发布/订阅功能使消息的分发可以突破目的队列地理指向的限制,使消息按照特定的主题甚至内容进行分发,用户或应用程序可以根据主题或内容接收到所需要的消息。发布/订阅功能使得发送者和接收者之间的耦合关系变得更为松散,发送者不必关心接收者的目的地址,而接收者也不必关心消息的发送地址,而只是根据消息的主题进行消息的收发。
群集 (Cluster):为了简化点对点通讯模式中的系统配置,MQ 提供 Cluster(群集) 的解决方案。群集类似于一个域 (Domain),群集内部的队列管理器之间通讯时,不需要两两之间建立消息通道,而是采用群集 (Cluster) 通道与其它成员通讯,从而大大简化了系统配置。此外,群集中的队列管理器之间能够自动进行负载均衡,当某一队列管理器出现故障时,其它队列管理器可以接管它的工作,从而大大提高系统的高可靠性。
Kafka 是一个消息系统,原本开发自 LinkedIn,用作 LinkedIn 的活动流(Activity Stream)和运营数据处理管道(Pipeline)的基础。现在它已被多家公司作为多种类型的数据管道和消息系统使用。活动流数据是几乎所有站点在对其网站使用情况做报表时都要用到的数据中最常规的部分。活动数据包括页面访问量(Page View)、被查看内容方面的信息以及搜索情况等内容。这种数据通常的处理方式是先把各种活动以日志的形式写入某种文件,然后周期性地对这些文件进行统计分析。运营数据指的是服务器的性能数据(CPU、IO 使用率、请求时间、服务日志等等数据),总的来说,运营数据的统计方法种类繁多。
Kafka 专用术语
Broker:Kafka 集群包含一个或多个服务器,这种服务器被称为 broker。
Topic:每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上,但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)。
Partition:Partition 是物理上的概念,每个 Topic 包含一个或多个 Partition。
Producer:负责发布消息到 Kafka broker。
Consumer:消息消费者,向 Kafka broker 读取消息的客户端。
Consumer Group:每个 Consumer 属于一个特定的 Consumer Group(可为每个 Consumer 指定 group name,若不指定 group name 则属于默认的 group)。
Kafka 交互流程
Kafka 是一个基于分布式的消息发布-订阅系统,它被设计成快速、可扩展的、持久的。与其他消息发布-订阅系统类似,Kafka 在主题当中保存消息的信息。生产者向主题写入数据,消费者从主题读取数据。由于 Kafka 的特性是支持分布式,同时也是基于分布式的,所以主题也是可以在多个节点上被分区和覆盖的。
信息是一个字节数组,程序员可以在这些字节数组中存储任何对象,支持的数据格式包括 String、JSON、Avro。Kafka 通过给每一个消息绑定一个键值的方式来保证生产者可以把所有的消息发送到指定位置。属于某一个消费者群组的消费者订阅了一个主题,通过该订阅消费者可以跨节点地接收所有与该主题相关的消息,每一个消息只会发送给群组中的一个消费者,所有拥有相同键值的消息都会被确保发给这一个消费者。
Kafka 设计中将每一个主题分区当作一个具有顺序排列的日志。同处于一个分区中的消息都被设置了一个唯一的偏移量。Kafka 只会保持跟踪未读消息,一旦消息被置为已读状态,Kafka 就不会再去管理它了。Kafka 的生产者负责在消息队列中对生产出来的消息保证一定时间的占有,消费者负责追踪每一个主题 (可以理解为一个日志通道) 的消息并及时获取它们。基于这样的设计,Kafka 可以在消息队列中保存大量的开销很小的数据,并且支持大量的消费者订阅。
示例:网络游戏
假设我们正在开发一个在线网络游戏平台,这个平台需要支持大量的在线用户实时操作,玩家在一个虚拟的世界里通过互相协作的方式一起完成每一个任务。由于游戏当中允许玩家互相交易金币、道具,我们必须确保玩家之间的诚信关系,而为了确保玩家之间的诚信及账户安全,我们需要对玩家的 IP 地址进行追踪,当出现一个长期固定 IP 地址忽然之间出现异动情况,我们要能够预警,同时,如果出现玩家所持有的金币、道具出现重大变更的情况,也要能够及时预警。此外,为了让开发组的数据工程师能够测试新的算法,我们要允许这些玩家数据进入到 Hadoop 集群,即加载这些数据到 Hadoop 集群里面。
对于一个实时游戏,我们必须要做到对存储在服务器内存中的数据进行快速处理,这样可以帮助实时地发出预警等各类动作。我们的系统架设拥有多台服务器,内存中的数据包括了每一个在线玩家近 30 次访问的各类记录,包括道具、交易信息等等,并且这些数据跨服务器存储。
我们的服务器拥有两个角色:首先是接受用户发起的动作,例如交易请求,其次是实时地处理用户发起的交易并根据交易信息发起必要的预警动作。为了保证快速、实时地处理数据,我们需要在每一台机器的内存中保留历史交易信息,这意味着我们必须在服务器之间传递数据,即使接收用户请求的这台机器没有该用户的交易信息。为了保证角色的松耦合,我们使用 Kafka 在服务器之间传递信息 (数据)。
Kafka 特性
Kafka 的几个特性非常满足我们的需求:可扩展性、数据分区、低延迟、处理大量不同消费者的能力。这个案例我们可以配置在 Kafka 中为登陆和交易配置同一个主题。由于 Kafka 支持在单一主题内的排序,而不是跨主题的排序,所以我们为了保证用户在交易前使用实际的 IP 地址登陆系统,我们采用了同一个主题来存储登陆信息和交易信息。
当用户登陆或者发起交易动作后,负责接收的服务器立即发事件给 Kafka。这里我们采用用户 id 作为消息的主键,具体事件作为值。这保证了同一个用户的所有的交易信息和登陆信息被发送到 Kafka 分区。每一个事件处理服务被当作一个 Kafka 消费者来运行,所有的消费者被配置到了同一个消费者群组,这样每一台服务器从一些 Kafka 分区读取数据,一个分区的所有数据被送到同一个事件处理服务器 (可以与接收服务器不同)。当事件处理服务器从 Kafka 读取了用户交易信息,它可以把该信息加入到保存在本地内存中的历史信息列表里面,这样可以保证事件处理服务器在本地内存中调用用户的历史信息并做出预警,而不需要额外的网络或磁盘开销。
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。