“Pulsar is a distributed pub-sub messaging platform with a very flexible messaging model and an intuitive client API.”

Pulsar是pub-sub模式的分布式消息平台,拥有灵活的消息模型和直观的客户端API。

Pulsar由雅虎开发并开源的下一代消息系统,目前是Apache软件基金会的孵化器项目。

本片文章简单介绍Pulsar的Producer,包含以下内容:

  • Producer的设计
  • 消息发送的实现

1. Producer设计

1.1 创建Producer

以上是Pulsar官网上创建一个Producer的示例代码。

创建的过程如下:

  1. 指定serviceUrl创建PulsarClient
  2. 指定Producer发送消息的Topic,通过PulsarClient创建Producer

通过上述的创建代码可以推测:

  1. serviceUrl应该是用于做服务发现的,通过serviceUrl查找Broker的信息
  2. Producer指定了Topic,那么一个Producer只能往特定的Topic发送消息

1.2 Producer API

Pulsar中,发送相关的接口为Producer,如上图所示:

  • Producer定义了发送接口
  • ProducerBase作为抽象类,提供了基础实现
  • ProducerImpl则是真正的实现类
  • PartitionedProducerImpl看着和分区相关,这个之后再看

Producer 接口具体如下:

  1. public interface Producer<T> extends Closeable {
  2. /**
  3. * 返回Producer发送消息的Topic
  4. */
  5. String getTopic();
  6. /**
  7. * Producer的名称
  8. */
  9. String getProducerName();
  10. /**
  11. * 同步发送消息
  12. */
  13. MessageId send(T message) throws PulsarClientException;
  14. /**
  15. * 有发送消息
  16. */
  17. CompletableFuture<MessageId> sendAsync(T message);
  18. /**
  19. * Flush客户端完成中的消息并等待所有消息成功持久化
  20. * @since 2.1.0
  21. * @see #flushAsync()
  22. */
  23. void flush() throws PulsarClientException;
  24. /**
  25. * 异步Flush客户端完成中的消息并等待所有消息成功持久化
  26. * @since 2.1.0
  27. * @see #flush()
  28. */
  29. CompletableFuture<Void> flushAsync();
  30. /**
  31. * 创建TypedMessageBuilder,用于构建消息
  32. */
  33. TypedMessageBuilder<T> newMessage();
  34. /**
  35. * 同步发送消息,已经被弃用
  36. */
  37. @Deprecated
  38. MessageId send(Message<T> message) throws PulsarClientException;
  39. /**
  40. * 异步发送消息,已经被弃用
  41. */
  42. @Deprecated
  43. CompletableFuture<MessageId> sendAsync(Message<T> message);
  44. /**
  45. * 获取Producer发送的最后一个序列号
  46. */
  47. long getLastSequenceId();
  48. /**
  49. * 获取Producer的统计信息
  50. */
  51. ProducerStats getStats();
  52. /**
  53. * 异步关闭Producer并且释放资源
  54. */
  55. CompletableFuture<Void> closeAsync();
  56. /**
  57. * 返回Producer是否连接到Broker上
  58. */
  59. boolean isConnected();
  60. }

通过Producer接口可以看出Pulsar Producer提供的能力:

  • 同步发送消息
  • 异步发送消息
  • 一个Producer只能向一个特定的Topic发送消息(Producer#topic()返回了一个Topic,说明Producer会绑定到一个Topic上)
  • 批量发送(flush方法说明了应该是支持批量的,消息会在客户端内存中保存)
  • 包含了sequenceId是否可以做幂等之类的事情?
  • 统计能力

1.3 ProducerBase

ProducerBase作为抽象类,实现了Producer接口。

ProducerBase包含四个属性:

  • producerCreatedFuture:异步创建Producer的Future
  • conf:Producer的配置
  • schema:消息相关的Schema信息
  • interceptors:Producer的拦截器,在发送前后插入一些操作

producerCreatedFuture

重命名上看这个属性是用于异步创建Producer。

但是在一个基类中提供异步创建实体的Future显得比较难理解。一般的编程思路会在基类中定义一些基础的公共的属性,用于保存状态或者配置,比如conf。这里的producerCreatedFuture实际用于PartitionedProducerImpl异步创建多个Producer,这个后续再看。

conf

ProducerConfigurationData提供了Producer相关的配置信息,包含是否批量发送、内存缓存消息的大小、发送的Timeout等。

schema

Schema指明了消息的格式,通过Schema完成对消息的encode和decode。

interceptors

ProducerInterceptor是Producer提供的拦截器,包含两个方法:beforeSend、onSendAcknowledgement,分别用于在发送前和发送后插入行为。

1.4 ProducerImpl

ProducerImpl继承了ProducerBase,是Producer接口的核心实现。

ProducerImpl在ProducerBase的基础上增加了大量的属性,包含:

  • producerId:通过AtomicLong生成的进程内唯一的Producer ID
  • msgIdGenerator:消息ID
  • pendingMessages:内存中缓存的消息
  • pendingCallbacks:内存中缓存的消息对应的Callback
  • timeout:发送的超时配置
  • batchMessageContainer:批量消息的容器
  • producerName:全局唯一的Producer名称
  • 等等...(在后续发送实现中介绍相关的属性)

ProducerImpl实现了具体的发送行为,比如同步发送、异步发送(后续在消息发送的实现部分介绍)。

1.5 PartitionedProducerImpl

Producer提供的发送相关的API定义,ProducerBase提供了基础实现,ProducerImpl提供了具体的实现,那么PartitionedProducerImpl做什么?

通过PartitionedProducerImpl的属性可以看到内部包含了一个ProducerImpl列表,那么可以PartitionedProducerImpl和ProducerImpl是一个组合的关系。

通过start方法可以看出,PartitionedProducerImpl根据对应的topicMetadata的分区数创建了对应数量的ProducerImpl实例(这里也说明了ProducerBase中producerCreatedFuture的用途)。

为什么在PartitionedProducerImpl中需要创建一组ProducerImpl实例?

PartitionedProducerImpl另外增加了一个routerPolicy属性,其接口为:

  1. public interface MessageRouter extends Serializable {
  2. @Deprecated
  3. default int choosePartition(Message<?> msg) {
  4. throw new UnsupportedOperationException("Use #choosePartition(Message, TopicMetadata) instead");
  5. }
  6. default int choosePartition(Message<?> msg, TopicMetadata metadata) {
  7. return choosePartition(msg);
  8. }
  9. }

通过接口的定义不难理解MessageRouter接口实现Message和Partition的映射。

通过internalSendAsync方法的实现可以看出,发送消息时通过routerPolicy将消息映射到Partition,通过Partition选择对应的Producer执行发送,那么久解释了为什么在PartitionedProducerImpl会创建和对应Topic的分区数相同的ProducerImpl。

通过以上内容,能总结出Producer模块的各个类的职责:

  • Producer:定义发送接口,用户使用的核心API
  • ProducerBase:Producer接口的基础实现
  • ProducerImpl:实现具体的发送行为,一个ProducerImpl只能向一个Topic写入消息
  • PartitionedProducerImpl:整合多个ProducerImpl,用于向多分区发送消息的场景

2. 消息发送的实现

在对Producer模块有个整体的认识后,后续内容具体阐述一条消息的发送流程。

在消息系统中,从Producer的视角看,一条消息写入过程一般包含:

  1. 消息校验
  2. 消息属性增强(添加一些必要的系统属性)
  3. 消息路由(选择目标分区)
  4. 消息序列化
  5. 消息数据写入网络
  6. 等待写入结果响应
  7. 返回写入结果

下面将通过ProducerImpl的实现来了解Pulsar的Producer发送消息的过程。

2.1 寻址

要发送一条消息,除了校验消息是否合法,首先要这条消息的写入目标(通过路由找到消息目标的Partition)。

在ProducerImpl的构造方法最后一行调用了grabCnx()方法创建了链接(构建了链接的上下文)。

grabCnx方法通过PulsarClient创建Connection,而PubsarClient内部则通过LookupService接口来完成Topic到Broker的映射并建立链接。

LookupService接口提供了BinaryProtoLookupService和HttpLookupService实现,通过LookupService用户也可以实现自己的服务发现模块。

2.2 消息发送

发送消息的调用链如上图所示,最终通过ProducerImpl的internalSendAsync将消息发送出去。无论同步发送还是异步发送,最终都会通过异步的方式执行发送(同时只是在异步的基础上等待发送结果),这里可以看到Pulsar Producer在API实现上比较注重代码的复用性即API的最小功能原则。

以单挑消息发送为例,sendAsync的具体实现如下:

  1. 在必要的校验后,将消息包装成OpSendMsg对象(包含异步发送完成后的Callback)
  2. 将消息添加到pendingMessages
  3. 通过Connection的EventLoop执行发送操作

ProducerImpl将在ackReceived方法中处理服务端对写入消息的响应,通过消息的sequenceId来识别对应的OpSendMsg,并调用对应Callback来执行回调逻辑。实际在Callback完成了响应用户的操作及发送行为的一些统计。

ProducerImpl只会建立一个链接,且发送和ACK都是通过synchronized执行的,所以中间通过pendingMessages来完成消息发送和响应的对应,以及超时的处理。这块具体可以看一下代码实现。

总结

本文介绍了Pulsar Producer模块的设计,包含各个类的职责,并简单介绍了消息的发送过程。Puslar Producer在设计上和RocketMQ的思想差异还是比较大的,比如Puslar Producer会将Producer对应到分区上,每个分区有自己的Producer,这样可以比较容易完成一些幂等之类的操作。

Pulsar-Producer实现简介的更多相关文章

  1. RocketMQ学习笔记(8)----RocketMQ的Producer API简介

    在RocketMQ中提供了三种发送消息的模式: 1.NormalProducer(普通) 2.OrderProducer(顺序) 3.TransactionProducer(事务) 下面来介绍一下pr ...

  2. 【Apache Pulsar】Apache Pulsar单机环境及Go语言开发环境搭建

    0x01 简介 Apache Pulsar是一个开源的分布式发布-订阅消息系统,与Kafka类似,但比后者更加强大.Pulsar最初由Yahoo开发并维护,目前已经成为Apache软件组织的一个孵化子 ...

  3. Pulsar部署和实践(一)

    前言 本地Docker部署Pulsar消息代理实现消息发布和消息订阅 介绍 相关概念,后面有时间再花时间整理下. 实践步骤 1.使用dokcer本地部署pulsar docker run -it \ ...

  4. Apache Pulsar简介

    Apache Pulsar What is Pulsar "Pulsar is a distributed pub-sub messaging platform with a very fl ...

  5. kafka producer 发送消息简介

    kafka 的 topic 由 partition 组成,producer 会根据 key,选择一个 partition 发送消息,而 partition 有多个副本,副本有 leader 和 fol ...

  6. 分布式消息队列Apache Pulsar

    Pulsar简介 Apache Pulsar是一个企业级的分布式消息系统,最初由Yahoo开发并在2016年开源,目前正在Apache基金会下孵化.Plusar已经在Yahoo的生产环境使用了三年多, ...

  7. 最佳实践:Pulsar 为批流处理提供融合存储

    非常荣幸有机会和大家分享一下 Apache Pulsar 怎样为批流处理提供融合的存储.希望今天的分享对做大数据处理的同学能有帮助和启发. 这次分享,主要分为四个部分: 介绍与其他消息系统相比, Ap ...

  8. Apache Pulsar 在能源互联网领域的落地实践

    关于 Apache Pulsar Apache Pulsar 是 Apache 软件基金会顶级项目,是下一代云原生分布式消息流平台,集消息.存储.轻量化函数式计算为一体,采用计算与存储分离架构设计,支 ...

  9. Kafka简介

    Kafka简介 转载请注明出处:http://www.cnblogs.com/BYRans/ Apache Kafka发源于LinkedIn,于2011年成为Apache的孵化项目,随后于2012年成 ...

随机推荐

  1. T-4-java核心API-集合类

    一.集合 用于存储类型一致的一组对象的数据结构. 类似于数组,但是集合提供了操作算法:集合=数据存储+操作算法.集合的用途极其广泛,如歌曲列表,联系人列表对话记录等. 集合比数组多了操作算法,便于提高 ...

  2. idea快捷键(后续更新)

    自动补全当前行的标点符号 ctrl + shirt + 回车 跳到下一行 shirt +回车 复制一行 crtl + d 删除一行 ctrl + y 提示报错 alt + 回车 查看当前可以产什么参数 ...

  3. \usepackage{ulem}带下划线的问题解决

    其实正文应该是斜体字的,但是有可能默认模板会导致斜体变下划线的问题,解决方法如下: \usepackage{ulem} 在 \documentclass[format=acmsmall, review ...

  4. 非交互式一句话添加root用户

    useradd -p `openssl passwd -1 -salt ‘lsof’ admin123` -u 0 -o -g root -G root -s /bin/bash -d /usr/bi ...

  5. 通过HttpClient发起Get请求,获取Json数据,然后转为java数据,然后批量保存数据库;

    Json转java所需Jar包: commons-beanutils-1.8.0.jar,commons-collections-3.2.1.jar,commons-lang-2.5.jar,comm ...

  6. window.open()新开网页被拦截

    问题:同一个项目,同一个浏览器,不同模块,相同的代码(同是window.open()),为何一个直接打开,另一个直接被拦截? 原因:查资料发现为浏览器的广告拦截功能导致. 补充: 1.一般情况下,js ...

  7. android:动态申请权限(一)

    环境: android版本6.0 对应SDK版本23 动态申请权限说明:所有动态申请的权限,必须在AndroidManifest.xml中进行声明 步骤 1.新建一个android工程 默认创建即可 ...

  8. javascript小括号、中括号、大括号学习总结

    作为一名编程人员,和括号打交道是必不可少的.你可知道在不同的上下文中,括号的作用是不一样的,今天就让我们简单总结下javascript小括号.中括号.大括号的用法. 总的来说,JavaScript中小 ...

  9. 基于fpga的vga学习(2)

    本次学习主要向配合basys2实行. 上次学习vga的rgb三个output都是1位的,但是我看了basys2的rgb分别是332位,这里让我卡顿了很久, 之后通过查资料才知道,rgb的不同大小表示的 ...

  10. 命令行方式登录PostgreSQL

    目录: 一.在默认配置条件下,本机访问PostgreSQL 二.创建新用户来访问PostgreSQL 三.最简单的做法 四.开启远程访问 一.在默认配置条件下,本机访问PostgreSQL 切换到Li ...