消息系统端到端Exactly-Once支持

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: 消息系统对于消息的写入和消费提供多种模式,如 at-most-once,at-least-once 和 exactly-once,本文探讨如何在一个消息提供中高效支持exactly-once的写入和消费

在消息系统中,消息的生成和消费通常支持三种模式: at-most-once,at-least-once 和 exactly-once。 这几种模式的区别主要在于当系统发生错误的时候,系统表现何种行为。前两种模式,非常容易理解,出错后不处理或者不停重试直到成功为止,对于exactly-once的模式,系统在出错的时候,则需要进行特殊处理来保证一条消息只被处理一次。

image

发生错误的场景

上图是常见的消息系统(如Kafka/Plusar/sls loghub等)的架构,消息(message)由生产者(Producer)发送至消息系统的接收者(Broker),Broker将Message持久化到特定分区(Partition)后,供消费者(Consumer)进行消费。在这个过程中,任意阶段都可能出错。

  • Broker 错误:Broker作为系统的主要组成部分,负责数据的接收和读取,当单个broker fail的时候,不同的系统表现行为也不同,可能会重新选举Leader或进行broker的failover,在这个过程中,消息的读写可能发生失败
  • Producer到Broker RPC错误 : Producer将数据发送至Broker后,需要收到Broker的ack才能确定消息写入成功,在Broker出错,或Producer到Broker网络异常等情况下,Producer可能无法收到ack信息,这时,Producer无法确认消息是否已经正确持久化,如果Producer忽略错误,这表现为at-most-once模式,进行重试直到成功,则可能是at-least-once
  • Producer :消息的生产者本身也可能会因为程序异常、机器宕机等行为导致异常,在Producer内存中尚未发送的消息,如果不做特殊处理,则会丢失
  • Consumer :作为消息的消费者,在发生错误的时候如何恢复消费的状态以及从哪个位置再次开始消费数据,则决定消费情况系统表现那种模式

写入模式下Exactly-Once

从上面的错误场景可以看到,错误在任意阶段都可能发生,要做到任意情况下完全的exactly-once写入代价极其昂贵,在线上大规模生产系统中,很难承受:

  • 每条消息有一个唯一的ID
  • Broker在接收到一条消息后,进行全局校验ID是否重复
  • Broker对于消息ID的校验和写入原子操作

在分布式场景下,做全局全量数据的原子排他性操作,成本无法接受的。那退而求其次,在一定限定条件下,则可以更高效达到exactly-once的效果。可以从以下两方面进行限定:

  1. Producer 发送的一条消息,在各种错误情况下,限定只到某个Broker下的确定Partition。这样消息无需做全局的校验,只需要在Partition级别即可;
  2. 单个Producer 到单个Partition的消息ID(Sequence ID)单调递增,这样虽然降低了单个Producer到单个Partition的吞吐(串行),但是每个Partition对于Producer消息的ID校验大大简化,只需要效验一个ID即可,无需维护历史所有ID

image

通过以上两个简化,在Partition级的exactly-once的写入操作,只需要额外一次HashMap的查询即可,而持久化的数据,也只会增加极少量的字段(ProducerID, Sequence ID)。

Broker Failover 处理

在Broker Failover的时候,必须将Broker内存中各个Producer当前写入的SequenceID完全恢复出来,才能保证数据exactly-once写入。在持久化的数据中,有每条消息的ProducerID和SequenceID信息,可以通过扫描持久化信息进行恢复,同时为了加快恢复速度,可以定期将Broker内存中的HashMap作为snapshot保存下来,在恢复的时候,首先恢复snapshot,然后只需要读取少量的信息完成ProducerID和SequenceID映射重建。

Producer Failover 处理

当Producer Failover的时候,对于部分数据源和SequenceID可映射的场景,可以根据ProducerID从Broker中获取最新的SequenceID,根据该ID对数据源进行重置(如Producer的数据源是文件,SequenceID 和文件行号能进行映射),Producer可以从上次最后写入成功的位置继续写入。

消费模式下 Exactly-Once

在完成exactly-once写入后,为了支持端到端的exactly-once处理, 同样需要消费端的配合,这里主要指在消费端failover时,如何确保每条消息只被处理一次。 虽然不少消息系统提供ack机制,消费者只需关系数据的处理即可,如Plusar提供的consumer会自动拉取消息供应用消费,应用只需要关心消息的处理,在处理完毕后,对消息进行ack即可。

Consumer consumer = client.subscribe(...);

while (true) {
    Message msg = consumer.receive();
    // Process the message...
    consumer.acknowledge(msg);
}

但是,这种简单的消费模式,无法支持exactly-once,核心在于消息的处理和ack是非原子操作。当消息处理完毕尚未进行ack时,consumer可能crash,重启后,这条消息将被重复消费(broker尚未收到这条消息的ack信息)。

因此,为了支持exactly-once消费,需要将消息处理的结果(通常使用状态表示)和消息的ID持久化到其他外部系统中,以确保failover能恢复到某个完全精确的状态继续消费,如flink使用的checkpoint机制,将flink内部某时刻状态以及消费的位置信息进行持久化。

即使如此,如果在消费过程中,会额外产生结果并写入其他下游系统时,如果这些系统不支持幂等操作,那么在failover时,consumer重复消费数据时,下游系统还可能看到at-least-once的结果(如消费信息进行短信报警的场景)。

以上是对于消息系统的端对端支持exactly-once的简单探讨,通过一定的条件限定,写入端支持相对容易,而消费端除了较复杂的checkpoint机制外,还依赖消费产出下游系统的支持。

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
EMQ
|
1月前
|
消息中间件 存储 Cloud Native
HStream Webinar: 兼容 Kafka 协议的下一代流数据平台
3 月 20 日,HStream 将举行线上分享会,介绍下一代流数据平台 HStream Platform 的技术架构与应用案例。
EMQ
33 0
|
1月前
|
SQL 存储 监控
构建端到端的开源现代数据平台
构建端到端的开源现代数据平台
220 4
|
1月前
|
消息中间件 监控 安全
【天衍系列 05】Flink集成KafkaSink组件:实现流式数据的可靠传输 & 高效协同
【天衍系列 05】Flink集成KafkaSink组件:实现流式数据的可靠传输 & 高效协同
93 5
|
1月前
|
消息中间件 网络协议 Ubuntu
实现高效消息传递:使用RabbitMQ构建可复用的企业级消息系统
RabbitMQ是一个在 AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。
|
1月前
|
消息中间件 Kafka
消息队列 MQ:构建高效、可扩展的分布式系统
消息队列 MQ:构建高效、可扩展的分布式系统
|
1月前
|
消息中间件 监控 负载均衡
Kafka 最佳实践:构建可靠、高性能的分布式消息系统
Apache Kafka 是一个强大的分布式消息系统,被广泛应用于实时数据流处理和事件驱动架构。为了充分发挥 Kafka 的优势,需要遵循一些最佳实践,确保系统在高负载下稳定运行,数据可靠传递。本文将深入探讨 Kafka 的一些最佳实践,并提供丰富的示例代码,帮助大家更好地应用这一强大的消息系统。
|
1月前
|
消息中间件 数据挖掘 Kafka
Kafka在微服务架构中的应用:实现高效通信与数据流动
微服务架构的兴起带来了分布式系统的复杂性,而Kafka作为一款强大的分布式消息系统,为微服务之间的通信和数据流动提供了理想的解决方案。本文将深入探讨Kafka在微服务架构中的应用,并通过丰富的示例代码,帮助大家更全面地理解和应用Kafka的强大功能。
|
10月前
|
消息中间件 存储 监控
消息队列设计:构建高效可靠的分布式系统
在现代分布式系统中,消息队列被广泛应用于解耦和异步处理。它提供了高可靠性、高性能的消息传递机制,使得系统组件之间可以松耦合地协作。在本文中,我们将探讨如何设计一个高效可靠的消息队列系统。 架构设计原则
105 0
|
9月前
|
消息中间件 存储 人工智能
构建高可用的消息队列系统:保障消息传递的稳定性
构建高可用的消息队列系统:保障消息传递的稳定性
72 0
|
11月前
|
消息中间件 JavaScript Java
基于WebSocket的实时消息传递设计
web管理系统中可以对业务数据执行新增和删除,现在需要当业务数据发生新增或删除操作后,尽可能实时的反应到WPF客户端上面。
171 0
基于WebSocket的实时消息传递设计