1

关于 Apache Pulsar

Apache Pulsar 是 Apache 软件基金会顶级项目,是下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体,采用计算与存储分离架构设计,支持多租户、持久化存储、多机房跨区域数据复制,具有强一致性、高吞吐、低延时及高可扩展性等流数据存储特性。
GitHub 地址:http://github.com/apache/pulsar/

导语:本文是 StreamNative 开发工程师、Apache Pulsar Committer 丛搏在 Pulsar Summit Asia 2020 大会上的演讲《技术探究:Apache Pulsar 的事务型事件流》文字整理版本,本演讲主要对 Apache Pulsar 事务原理及规划进行了分享,请大家参考。

我叫丛搏,是来自 StreamNative 的开发工程师。今天我所带来的主题是《技术探究:Apache Pulsar 的事务型事件流》。

消息语义

大家都知道在所有的消息系统流数据平台对于消息都有不同的语义。一般语义分为三种:At-most once、At-least once、Exactly once。

  • At-most once:至多一次,不关心消息是否发送成功、不需要消息发送的返回值。
  • At-least once:至少一次,允许消息重复但必须保证消息必达。
  • Exactly once:精准一次,保证消息不丢且不会重复。

At Most Once(最多一次)

Pulsar 在 1.2.0 版本之前,已经实现 At Most Once 语义。

At Least Once(至少一次)

Pulsar 在设计之初就遵循 At Least Once 语义。发送消息失败后重试是保证 At Least Once 语义的基本方式。发送重试会导致消息重复,在某些使用场景下要求 Producer 不能发送重复消息且 Consumer 不能重复消费,因此产生了 Exactly Once 语义。

Exactly Once(精确一次)

实现 Exactly once 需要实现对消费/生产的去重。

Pulsar 中如何去重?

  • Producer: 幂等性 Producer;
  • Broker: 保证消息重复数据删除 ( PIP-6 );
  • Consumer: Reader + Checkpoints (Flink / Spark)。

如何开启 Exactly once?

对 topic 的 name space 进行 set-deduplication 的设置。通过 admin 等等的一些操作:

  • bin/pulsar-admin set-deduplication -e tenant/namespace
  • 创建 Producer 时设置 Producer name、Sequence id;
  • 产生消息时指定递增的序列 id。

限制:

  • 仅在向一个分区生成消息时有效;
  • 仅适用于产生一条消息;
  • 在一个分区或多个分区上生成多个消息时没有原子性;
  • Consumer 需要存储 message id 及其状态,并在还原状态时查找 message id。

Transaction 如何处理事件

通过转账的逻辑操作的例子来讲述流消息系统中 Transaction 是怎样处理事件的:

现在有 Alice 和 Bob 两个人。Alice 会给 Bob 转账十块钱。通过 Pulsar 如何来实现这个功能呢?

  • Transfer Topic : 记录转账的请求;
  • Cash Transfer Function: 处理转账的行为;
  • BalanceUpdate Topic: 记录余额更新请求。

Alice 转给 Bob。Transfer Function 收到这条转账消息,会向 BalanceUpdate Topic 发送一条 Bob 余额增加十块钱的消息,向 BalanceUpdate Topic 发送一条 Alice 余额减少十块钱的消息。接收到所有返回值后,Ack 这条转账消息。在所有的操作不会发生任何失败的时候它是没有问题的。但是往往事与愿违,它的所有操作都可能会发生问题。

图 1

如图 1 所示,Ack 失败后会重新再消费这条转账的消息,所带来的后果就是 Alice 再次给 Bob 转了十块钱,Alice 共给 Bob 转了二十块钱。倘若每次 Ack 都失败,有可能 Alice 的账户负债累累,Bob 成了亿万富翁。

图 2

如图 2 所示,Bob 增加余额的消息没有成功发送到所对应的 BalanceUpdate Topic 中,所带来的现象是 Bob 余额没有增加,Alice 的余额却减少了。

Pulsar Transaction

如何用 Pulsar 的 Transaction 来实现这件事情的?

Transaction 语义:

  • 保证多分区原子性消息写入;
  • 保证原子性确认多个订阅;
  • 一个事务中进行的所有操作全部成功或全部失败;
  • 允许 Consumers 读取已提交的消息。

没有 Transaction API 如何实现上述的例子?

Message<String> message = inputConsumer.receive();
 
CompletableFuture<MessageId> sendFuture1 =
producer1.newMessage().value(“output-message-1”).sendAsync();
CompletableFuture<MessageId> sendFuture2 =
producer2.newMessage().value(“output-message-2”).sendAsync();
 
inputConsumer.acknowledgeAsync(message.getMessageId());

如图 3 所示:

图 3

从 Input Consumer 中接收到消息之后,Producer1 会发送消息到 topic1 中,Producer2 会发送一条消息到 topic2,然后 Ack 接收到的消息。

Pulsar 的 Transaction API 其实很简单,对于原有需实现的逻辑没有太大的改变:

Message<String> message = inputConsumer.receive();
Transaction txn = client.newTransaction().withTransactionTimeout(…).build().get();
 
CompletableFuture<MessageId> sendFuture1 =
producer1.newMessage(txn).value(“output-message-1”).sendAsync();
CompletableFuture<MessageId> sendFuture2 =
producer2.newMessage(txn).value(“output-message-2”).sendAsync();
inputConsumer.acknowledgeAsync(message.getMessageId(), txn);
 
txn.commit().get();
 
MessageId msgId1 = sendFuture1.get();
MessageId msgId2 = sendFuture2.get();
 
inputConsumer.acknowledgeAsync(message.getMessageId(), txn);
 
txn.commit().get();

Pulsar Transaction 有如下三大组件:

  • TC (Transaction Coordinator)负责管理 Transaction 元数据。
  • TB (Transaction Buffer)负责处理发送带有 Transaction 的消息。
  • TP (Transaction Pending Ack)负责处理带有 Transaction 的 Ack 请求。

图 4

如图 4 所示:创建 Transaction 的操作记录在 TC 中。

图 5

如图 5 所示:Pulsar Client 已成功创建 Txn1,并向 TC 请求 Txn1 将发送消息到 Topic1 和 Topic2,TC 收到发送请求后记录发送元数据并响应 Client。Client 向 Topic1,Topic2 分别发送一条消息。

图 6

如图 6 所示:与图 5 所描述基本相同。仅发送与签收的区别。

图 7
图 8

如图 7、图 8 所示:Pulsar Client 等待所有 ACK 和 Produce 完成后 Commit Transaction,TC 接收到 Commit 请求后 Txn1 的状态更改成 Committing 后会处理 TP 和 TB 中 Txn1 的信息。

图 9

如图 9 所示:处理 TP、TB 完成后 TC 会更改 Txn1 的状态为 Committed。

以上是一个 Transaction 完整的生命周期。

再来看一下转账的例子:

图 10

当有 Pulsar Transaction 的支持后,所有的操作要么成功,要么都失败。就保证了对 Alice 和 Bob 余额操作的正确性。

Pulsar Transaction 的未来规划

Pulsar Transaction 的设计目的是让事件流系统变得更加简单、可靠性更加强。对于许多业务场景来说,在处理业务场景时就可能会少了很多处理幂等性的操作等等。

那么,以下就是 Pulsar Transaction 未来的开发规划:

  • Transaction support in other languages (e.g. C++, Go)
  • Transaction in Pulsar Functions & Pulsar IO
  • Transaction in Kafka-on-Pulsar (KOP)
  • Transaction for Flink / Spark job
  • Transaction for State storage in Pulsar Functions

大家对于上方的内容感兴趣欢迎扫描下方二维码回复「入群」随时在 Pulsar 交流群中与我们一起讨论。

对于文中的介绍想要了解更多,请扫描下方小程序码查看完整版视频:

相关阅读

点击链接 ,获取 Apache Pulsar 硬核干货资料!


ApachePulsar
192 声望939 粉丝

Apache软件基金会顶级项目,下一代云原生分布式消息系统