导语

Apache Pulsar 是一个多租户、高性能的服务间消息传输解决方案,支持多租户、低延时、读写分离、跨地域复制、快速扩容、灵活容错等特性。腾讯云内部 Pulsar工作组对 Pulsar 做了深入调研以及大量的性能和稳定性方面优化,目前已经在腾讯内部业务TDBank落地上线。本文是Pulsar技术系列中的一篇,主要介绍Pulsar 的 Message Deduplication 特性,供大家参考,避免在使用过程中踩坑。

Message Deduplication背景介绍

消息中间件产品设计中,对消息的投递设计,一般参照Kafka中提出的三种投递语意,分别为:

至多一次 (at-most-once)

至少一次 (at-least-once)

精确一次(或恰好一次) (exactly-once )

理解上需要注意的是,这里都是对投递行为的限定描述。

至多一次:客户端在生产消息的时候,仅会对生产的消息投递一次,这里并不保证消息一定生产成功。

至少一次:客户端在生产消息的时候,在收到一次成功的响应之前,可能会投递多次。这种场景下,服务器端可能存在多条重复的消息。

精确一次(或恰好一次):客户端在生产消息的时候,针对这次生产,服务器端保证有且仅保存一份消息。这里的 “这次生产”,一般都是指的是客户端对一次“SendMessage”的调用。这种语意下,服务器一般不会处理多次对相同消息体调用生产,产生重复消息的场景。简单而言,就是“精确一次”并不等于消息去重复。

许多系统声称提供“exactly-once”的交付语义,但仔细阅读其声明会发现,一些系统的声明可能存在一定的误导性,我们需要考虑它们在生产超时,部分副本写入成功,部分失败等场景下对语意的保证。

目前业界,绝大多数的消息中间件产品,如Kafka、RocketMQ、Pulsar、InLong-Tube、RabbitMQ、ActiveMQ等,都支持at-least-once(至少一次)的投递语意,即生产成功的消息,服务器端至少能保证存储一份,消费者至少能消费到一份消息。但是,对exactly-once(精确一次)语意支持的产品还是比较少。

下面,我们着重介绍一下Pulsar的Message Deduplication(相当于对exactly-once的一种实现)功能,可能与你想的并不一样。

Pulsar 的消息去重(Message deduplication)

功能配置

Pulsar提供的Message Deduplication 功能,默认是关闭的。开启时,需要修改Broker 端的配置,另外客户端也需要添加少许的配置。(详情可参考pulsar的官网

开启Message Deduplictiaon能力,首先,Broker 端需要变更如下配置:

#是否开启message deduplication功能
brokerDeduplicationEnabled#deduplication功能下,生产者的数量限制
brokerDeduplicationMaxNumberOfProducers
#broker端生成deduplication 快照信息的间隔
brokerDeduplicationEntriesInterval
#生产者断链后,broker端deduplication信息保存的时长
brokerDeduplicationProducerInactivityTimeoutMinutes

其次,生产者客户端需要做如下变更:

1、为生产者指定一个名称。

2、配置消息生产超时为0(默认为30s)。

代码示例如下:

PulsarClient pulsarClient = PulsarClient.builder()
.serviceUrl("pulsar://localhost:6650")
.build();
Producer producer = pulsarClient.newProducer()
.producerName("producer-1")
.topic("persistent://public/default/topic-1")
.sendTimeout(0, TimeUnit.SECONDS)
.create();

功能原理

客户端对每一个发送的消息请求,都会采用递增方式生成一个唯一的Sequence ID编号,这个信息会被放置在Message 的元数据中,传输到Broker端。同时,客户端Producer 也会维护一个发送的PendingMessages队列,当收到Broker端返回的发送Ack 信息后,将PendingMessages中相同Sequence ID的信息移除,客户端认为发送的这个消息生产成功。

当Broker开启Message Deduplication 功能后,Broker对对每个收到的消息请求进行是否重复的判断。

判断的逻辑如下:

1、Broker端针对每个生产者,以生产者名字为key,分当前接收到的和已经处理完成的两个维度保存生产消息的最大Sequence ID信息:

/*当前已经接受不了到的*/
ConcurrentOpenHashMap<String, Long> highestSequencedPushed
/*当前已经存储处理过的*/
ConcurrentOpenHashMap<String, Long> highestSequencedPersisted

2、Broker端每收到一个生产Message的请求,会进行是否重复的判断,即收到的最新的Sequence ID是否大于Broker 端保存的两维度下相同ProducerName下的Sequence ID,如果大于则不重复,如果小于或等于则消息重复。消息重复时,Broker端会直接返回,不会继续走后续的存储处理流程。

由上面Pulsar 的Message Depulication feature 相关的配置和实现原理的介绍。可知,Pulsar Broker端的Message Depulication 功能,并不是对消息体的去重,而是客户端在不配置超时时间的前提下,Broker 端在一定的时间范围内,对同一个生产者名称下的客户端投递的具有相同Sequence id的消息的唯一行保证。

总结

Kafka 在0.11.0.0版本之后,针对Topic之内和多个Topic之间两种场景下的exactly-once语意,分别提供了支持传递幂等性处理的选项和类事物消息的处理方式进行保证。有兴趣的同学可以参展kafka的源码和官网介绍

Pulsar的Message Deduplication feature与Kafka的单Topic下对exaxtly-once语意的保证在实现方式上类似,也可以认为是对exaxtly-once语意的一种实现。

这里需要着重注意的是,exaxtly-once不等于消息去重。在实际的开发中,生产和消费部分都有可能产生重复的消息。

消息的生产者,在收到明确的消息生产成功的确认之前,消息在服务器端的存储状态是不确定的。

例如,在一定时间内,生产者没有收到生产的响应,选择了重发,这时,服务器端就可能有两份甚至多份消息的副本。

此外,消费部分在如下几个场景也有可能获取到重复推送的消息:

1、消费者重启时,已经消费,但是Broker端未收到Ack或消费者没有触发Ack;

2、Broker重启,因为消费者的Ack信息并不是实时保存的,Broker重启后可能会有少量的已经消费的消息会被重复推送;

3、消费出现异常,客户端使用reconsumerLater或negativeAck方式进行确认,这时Broker会重新推送消息。

因此,大家在选用消息中间件的特性时,需要注意相关的场景和限制。避免因为重复消息对业务产生不必要的影响。

one more thing

腾讯云基于 Apache Pulsar 自研的消息中间件--TDMQ Pulsar 版,具备极好的云原生和 Serverless 特性,兼容 Pulsar 的各个组件与概念,具备计算存储分离,灵活扩缩容的底层优势。目前TDMQ Pulsar 版已开始商业化,对Pulsar感兴趣的用户可以进入官网了解详情。

Message deduplication 这里的去重与你想的可能不一样|Apache Pulsar 技术系列的更多相关文章

  1. ☕【难点攻克技术系列】「海量数据计算系列」如何使用BitMap在海量数据中对相应的进行去重、查找和排序

    BitMap(位图)的介绍 BitMap从字面的意思,很多人认为是位图,其实准确的来说,翻译成基于位的映射,其中数据库中有一种索引就叫做位图索引. 在具有性能优化的数据结构中,大家使用最多的就是has ...

  2. WPF MVVM UI分离之《交互与数据分离》 基础才是重中之重~delegate里的Invoke和BeginInvoke 将不确定变为确定系列~目录(“机器最能证明一切”) 爱上MVC3系列~全局异常处理与异常日志 基础才是重中之重~lock和monitor的区别 将不确定变成确定~我想监视我的对象,如果是某个值,就叫另一些方法自动运行 将不确定变成确定~LINQ DBML模型可以对

    WPF MVVM UI分离之<交互与数据分离>   在我们使用WPF过程中,不可避免并且超级喜欢使用MVVM框架. 那么,使用MVVM的出发点是视觉与业务逻辑分离,即UI与数据分离 诸如下 ...

  3. Scrapy框架——介绍、安装、命令行创建,启动、项目目录结构介绍、Spiders文件夹详解(包括去重规则)、Selectors解析页面、Items、pipelines(自定义pipeline)、下载中间件(Downloader Middleware)、爬虫中间件、信号

    一 介绍 Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速.简单.可扩展的方式从网站中提取所需的数据.但目前Scrapy的用途十分广泛,可 ...

  4. JavaScript数组去重的四种方法

    今天,洗澡的想一个有趣的问题,使用js给数组去重,我想了四种方法,虽然今天的任务没有完成,5555: 不多说,po代码: //方法一:简单循环去重    Array.prototype.unique1 ...

  5. Lambda如何实现条件去重distinct List,如何实现条件分组groupBy List

    条件去重 我们知道, Java8 lambda自带的去重为 distinct 方法, 但是只能过滤整体对象, 不能实现对象里的某个值进行判定去重, 比如: List<Integer> nu ...

  6. Apache Spark 2.2.0 中文文档 - Structured Streaming 编程指南 | ApacheCN

    Structured Streaming 编程指南 概述 快速示例 Programming Model (编程模型) 基本概念 处理 Event-time 和延迟数据 容错语义 API 使用 Data ...

  7. [Pulsar系列] 10分钟学会Pulsar消息系统概念

    Apache Pulsar Pulsar是一个支持多租户的.高性能的服务与服务之间消息通讯的解决方案,最初由雅虎开发,现在由Apache软件基金会管理. Pulsar的主要特性如下: Pulsar实例 ...

  8. 97、爬虫框架scrapy

    本篇导航: 介绍与安装 命令行工具 项目结构以及爬虫应用简介 Spiders 其它介绍 爬取亚马逊商品信息   一.介绍与安装 Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, ...

  9. python爬取youtube视频 多线程 非中文自动翻译

    声明:我写的所有文章都是发在博客园的,我看到其他复制粘贴过去的 连个出处也不写,直接打上自己的水印...真是没的说了. 前言:前段时间搞了一些爬视频的项目,代码都写好了,这里写文章那就在来重新分析一遍 ...

随机推荐

  1. java 多线程 读写互斥锁ReentrantReadWriteLock:读读不互斥,读写互斥,写写互斥

    ReentrantReadWriteLock: 类ReentrantLock具有相互互斥的排他效果,也就是说,同一时间,只有一个线程执行lock()方法后面的任务.这样做虽然可以解决问题,但是效率非常 ...

  2. 为dokcer中最简版ubuntu(70M)增加apt安装能力

    如果要在docker中安装软件,除了直接找对应的软件镜像,一般我们会先安装Linux系统,然后再在里面安装各种需要的软件. 比如我想安装乌班图,直接下载官方的版本: 这个目前是Ubuntu20.04, ...

  3. 【LeetCode】35. Search Insert Position 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 二分查找 日期 题目地址:https://leetc ...

  4. AI实战分享 | 基于CANN的辅助驾驶应用案例

    摘要:什么是辅助驾驶?简而言之,就是借助汽车对周围环境的自动感知和分析,让驾驶员预先察觉可能发生的危险,有效增加汽车驾驶的舒适性和安全性. 导读:基于昇腾AI异构计算架构CANN的辅助驾驶AI应用实战 ...

  5. Log4自定义Appender介绍

    最初想要在执行一段业务逻辑的时候调用一个外部接口记录审计信息,一直找不到一个比较优雅的方式,经过讨论觉得log4j自定义的appender或许可以实现此功能.后来就了解了一下log4j的这部分. Ap ...

  6. uniapp h5中解决跨域问题

    "h5": { "title": "互联云玺", "template": "", "rou ...

  7. [炼丹术]UNet图像分割模型相关总结

    UNet图像分割模型相关总结 1.制作图像分割数据集 1.1使用labelme进行标注 (注:labelme与labelImg类似,都属于对图像数据集进行标注的软件.但不同的是,labelme更关心对 ...

  8. 编写Java程序,在硬盘中选取一个 txt 文件,读取该文档的内容后,追加一段文字“[ 来自新华社 ]”,保存到一个新的 txt 文件内

    查看本章节 查看作业目录 需求说明: 在硬盘中选取一个 txt 文件,读取该文档的内容后,追加一段文字"[ 来自新华社 ]",保存到一个新的 txt 文件内 实现思路: 创建 Sa ...

  9. 编写Java程序,测试包的使用和成员的访问权限

    返回本章节 返回作业目录 需求说明: 测试包的使用和成员的访问权限: 分别创建两个包,在这两个包下分别建立两个类. 其中某个类的某个方法需要引用用另一个包中某个类的某些成员属性. 被引用成员属性分别使 ...

  10. JavaScript 钩子

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <script s ...