写点什么

消息传输的设计方式

2017 年 12 月 07 日

写在前面

这几天拜读了郭斯杰的《Messaging,Storage,or both?》一文,原文地址在这里,大有感触,作者分享了自己过去几年时间里在工作中使用Apache Pulsar、DistributedLog,以及BookKeeper 的实际经验。

郭斯杰7 年前作为雅虎北京的推送消息团队成员开始使用BookKeeper,大约5 年前,也就是2012 年,郭斯杰转战到了位于旧金山的Twitter 公司,开始致力于利用BookKeeper 解决分布式数据库的一致性问题,根据他的描述,这一工作内容最终促成了Apache DistriutedLog 的诞生。

郭斯杰的这篇文章主要是围绕Pulsar、DistriutedLog 两者如何与BookKeeper 协作完成实时处理的经验分享。本文我将会解读他的这篇文章,并且加入我自己的实际理解和使用经验。

预备知识

郭斯杰最早开始接触的是BookKeeper,从后面的文章介绍中我们可以知道,BookKeeper 是很多组件的基础,可以帮助进行分布式环境的信息协同管理,正是由于拥有BookKeeper 的实际工作经验,郭斯杰也有了接触Pulsar 和DistributedLog 的机会。我先谈谈自己整理的一些相关知识,介绍这三个东西究竟是什么?

Apache Pulsar

Pulsar 是分布式订阅发布消息传输系统,最早有由 Yahoo 公司开发的,并在 2016 年正式开源。

Pulsar 提供了灵活消息传输、多租户、跨地理位置数据复制等特性。Pulsar 的创始人 Joe 和 Matteo 等人认为需求是 Pulsar 项目启动的原因,如果应用程序提供实时服务,需要保证平均 5ms 以内的发布延迟,99% 的请求不会超过 15ms 的延迟,同时满足分类、强持久性以及传输保证等特征的消息传输系统,这个系统必须满足提交到各个磁盘或者节点达到 99.99% 的准确性。

Pulsar 对于消息的相关概念和角色定义与 Kafka 很相近,它们都把数据的接入方叫做生产者,都把数据的接收方叫做消费者(订阅者),如下图所示。

Pulsar 是如何实现对于多租户用例的支持的?通过属性(Property)和命名空间 (NameSpace)。属性表示系统中的租户,在 Pulsar 集群内部,一个属性可以包含多个命名空间,如下图所示。

命名空间是 Pulsar 集群的最基本管理单元,在命名空间级别,你可以设置权限、调优复制策略、管理跨集群的消息数据复制、控制消息过期,以及其他关键操作。同一个命名空间里的主题共享相同的配置。

在 Pulsar 内部存在几个一对多的关系。一个命名空间对应多个主题(Topic),一个主题对应多个订阅者(Subsribes),一个订阅者可以接收主题上的所有消息。为了提供更加灵活的订阅方式,Pulsar 提供了三种不同的订阅类型:

  • 独占式订阅:每个主题有且仅有一个消费者;
  • 共享式订阅:多个消费者可以共享一个订阅 / 主题,每个消费者可以收到订阅的某一部分内容;
  • 失败切换模式:多个消费者可以连接到一个主题,但是同时有且仅有一个消费者可以收到消息,其他的消费者只有在当前在线的那个消费者离线时才能竞争上岗(同样只会有一个竞争获胜者,其它竞争失败者还是需要继续守候下一次竞争)。

除此之外,Pulsar 支持将主题进行分区,一旦分区,数据也会被自动分区,如下图所示,和 Kafka 类似,Pulsar 也引入了“Broker”概念,每个 Broker 管理多个主题。

Apache DistributedLog

DistributedLog 的出现是数据层面抽象的必然结果。2012 年底这个时间段正好是 Twitter 公司内部的实时消息基础设施的杂乱无章阶段。Kestrel 是一款队列系统,被设计用来处理在线服务的关键消息,Kafka 则被用于进行离线服务的日志收集和分析,郭斯杰的团队则使用 BookKeeper 进行数据库备份。

DistributedLog 也被称为“共享日志基础设施”。日志存储是几乎所有分布式系统都需要解决的问题,而 DistributedLog 被设计来解决这一共有需求,也可以统一分歧,逐渐变成其他服务的基础组件,包括键值对数据库、订阅发布消息,以及跨数据中心的复制机制等等。

下面这张图诠释了 DistributedLog 和 BookKeeper 的使用案例。

DistributedLog 最初的定义就是共享的日志组件,它会跟踪分布式交易日志的改变。DistributedLog 是基于 BookKeeper 的,它利用了 BookKeeper 的低延时存储、并行复制、简单重复读一致性、快速多对多复制修复、I/O 隔离,以及简单操作性,实现了无缝连接历史和未来数据的设计目标。

Kafka 与 DistributedLog 有一些不同,Kafka 最初就是作为一个日志收集系统,进而形成了一个消息系统,但是 DistributedLog 开始是为了解决数据库一致性问题,逐渐形成了流式的存储系统。这就意味着两者有较大的设计和技术实现差异,因为 Kafka 的设计初衷就是为了实现数据的交换,它是作为消息中间件设计的,不会仅仅考虑数据一致性问题,两者看待问题的格局不一样。现在 DistributedLog 已经被作为 BookKeeper 的一个子项目在运行了。

Apache BookKeeper

BookKeeper 是一款可扩展、容错、低延时的日志存储服务,尤其针对实时任务流设计。

BookKeeper 最初是由 Yahoo 公司开发的,2011 年被合并到了 Apache ZooKeeper,作为它的子项目。2015 年单独成为 Apache 顶级项目。

BookKeeper 集群由以下两个组件组成:

  • bookies:代表一系列独立的存储服务;
  • metadata 存储:服务发现和元数据管理。

BookKeeper 客户端使用 DistributedLog 的 API 或者原生 API 提供对于 bookies 的访问方式,架构图如下所示。

BookKeeper 支持以下需求:

  1. 客户端写入和读取数据需要低于 5ms 的延时,同时确保数据不丢失(强持久性);
  2. 数据存储应该是持久化的、一致性的、容错的;
  3. 客户端写入时可以在数据的尾部写入,并支持快速存取数据;
  4. 支持存储、访问历史和实时数据。

BookKeeper 针对每一份数据需要复制和存储多份副本,对于复制算法,这一点和其他的分布式系统有所不同,BookKeeper 使用的是一种被称为“仲裁并行复制算法(quorum-vote parallel replication algorithm)”的算法,该算法确保了低延时复制数据,这一点有别于 HDFS、Ceph、Kafka 的 Master/Slave 模式,BookKeeper 更加偏向于无中心化。

如上图所示,可以理解到以下观点:

  1. bookies 是自动从 BookKeeper 集群里选择的,ledger 作为一组顺序记录的抽象,类似一个文件名;
  2. 被存储在 ledger 上的数据记录会被同时写入到集群上的其他 bookies 节点;
  3. 当客户端写入数据时,客户端需要等待一定数量的副本确认写入后才能收到成功消息。这一点和 Cassandra 类似,都是需要遵从 R+W>N 的公式约束,以确保返回的数据是最新的且数据不丢失;
  4. 集群内部支持 bookie 的容错处理。

深入理解三者关系

Pulsar 的出现是为了解决当前开源的消息系统存在的一些弱点。据郭斯杰介绍,最开始的时候使用 BookKeeper 作为针对 ActiveMQ 的持久化消息存储,随后引入了统一灵活消息传输模型(Unified Flexible Messaging Model)概念,为什么这里称之为统一呢?我们需要先来了解一下传统的消息模式。郭斯杰介绍,一般来说,我们有两种传统的消息模型:队列和发布订阅。队列是点对点的通信模式,通常是无序消息传输,用于无状态应用比较多一些。对于队列的消费设计,通常存在一个消费池概念,消费者可以从服务端读取消息,每条消息仅仅能被传递给一个消费者。这种方式允许你对跨越多个消费者实例的处理数据进行分段处理,并且也容易对处理过程通过横向扩展方式提升性能。订阅发布模式则是一种广播通信模式,消息可以被广播给所有的消费者。当前几乎所有的消息组件都会分割对于这两种消息传输模式的支持方案,即形成两套方案,分别对应队列和主题(Topic)。郭斯杰也举例了确实存在一些消息组件尝试去合并这两种不同的通信模型,例如 Twitter 的 Kestrel,但是最终这样做的性价比不高。就我个人的使用经验来看,当前比较主流的 Kafka 采用的是发布订阅模式,当然,采用哪种方式本质上没有对错之分,重要的是和你的业务需求契合。

郭斯杰将 Pulsar 的消息模型方式概括为“生产者 - 主题 - 订阅 - 消费者”,或者简称 PTSC。通过 PTSC 模式,每一条消息都可以与主题相绑定,然后通过 BookKeeper 复制到不同的机器上,接着可以被消费者任意消费。消费者存在组的概念,每一组内的消费者可以决定自己的消费方式(独占式、共享式,或故障转移方式)。下面这张图解释了 Pulsar 支持的订阅模式。

郭斯杰提出了一种观点,他认为一些基于分区的订阅发布消息组件误导了人们对于队列和流的概念,这些组件强迫消费者按照同一种模式进行消息处理。举个例子,对于队列用例,用户不得不增加分区以用于匹配消费者并行需求,这种方式降低处理的效率,特别是对于任务派发这样的功能。我主持研发的系统的一个功能就包含任务派发,对于任务派发功能,我们可以有很多种不同的实现方式,多数情况是有多个接收端,这个时候如果需要通过增加队列方式,确实很不方便,所以郭斯杰提出的用例问题确实存在。

Pulsar 是怎么满足队列需求的?郭斯杰解释它是通过基于轮询算法的共享订阅模式,允许应用程序在一个订阅过程中进行分割处理,这样我们就可以实现在一个主题内部通过增加消费者数量的方式扩展并行处理能力。Pulsar 通过将发布并行性和消费并行性扩展分离方式,允许活动的发布和消费两者独立扩展,互不约束对方。

正是由于 Pulsar 是构建于 BookKeeper 之上的,所以可以提供高性能的队列和流式处理。BookKeeper 不仅仅有助于队列、流式处理的高性能,也帮助 Pulsar 有效地实现消息删除功能,而这一项恰恰是其他主流消息组件的弱项。

Pulsar 利用游标系统实现高效的消息移除。我插一句话,对于游标,熟悉 C 语言的同学应该有点映像,它对于数据的移动非常高效。据郭斯杰介绍游标是 Pulsar 用来为每个订阅者记录消息消费的重要状态信息。对于一个独占式 / 失败切换订阅模式,游标是一个偏移量,标记哪一个消费者已经消费了;而对于共享订阅模式,游标则不仅仅是一个偏移量了,它被用于跟踪每个消息的消费情况。Pulsar 利用 BookKeeper 特有的 ledgers(ledgers 支持应用程序创建很多独立的日志,一个 ledger 是一个递增的数据结构,通过单一写入模式保持数据写入,并且通过复制机制被复制到其他节点上)设计用于记录游标的更新情况,这样就相当于通过 BookKeeper 实现了游标信息的高可用性保障,减少了消费者出现异常之后的消息重新传递几率。正因为有了游标,Pulsar 可以针对消息的消费进行跟踪,也可以进行消息的删除。据郭斯杰介绍,他们在 Streamlio 使用游标系统解决 Pulsar 发布过程中的性能问题。

总结

从郭斯杰的文章里我们可以知道 Pulsar 是一种支持灵活的消息模式(队列和订阅发布)的分布式订阅系统,背后是通过 BookKeeper 支持高度扩展、持久化流式存储。Pulsar 聚焦于消息传输和消费,允许快速删除不需要的消息。我认为正是由于背后有可扩展的流式存储支持,也相应获得了强一致性保障,并且允许流式计算中的回调数据和重新处理。

BookKeeper 与 DistributedLog 的结合形成了高度可扩展的日志流存储系统,对外提供实时的日志流存储服务,可以被用来为其他消息组件提供存储消息服务,例如 Pulsar。但是我们也应该看到 Pulsar 和 DistributedLog 之间确实存在一些技术上的重叠,更多应该从它们所分别对应的应用场景出发进行考虑。如果你正在寻找针对流式数据的实时存储组件,BookKeeper+DistributedLog 的组合适合你的需求(高吞吐、低延时、持久化复制功能等等)。我认为,如果你需要一个快速、灵活的消息系统,Pulsar 提供了灵活的消息模式,同时支持队列和发布订阅两种模式。

从全文分析来看,我认为 Pulsar 和 DistributedLog/BookKeeper 的组合,即消息 + 存储的组合可以提供快速、持久、灵活的消息传输模式以及高可扩展的流式存储。

郭斯杰认为现实世界我们不能单纯谈论消息或者存储,两者应该是并存的。对于这个观点,我也是持支持态度,因为现实的需求往往是需要多种组件协同支持的,即便是实时分析引擎,其实它内部也存在保存任务状态的持久化组件,只不过看你如何看待保存的数据了。如果没有持久化、低延时的存储,消息系统就有可能丢失数据,也就不能满足计算引擎重复处理数据的要求;如果没有消息系统,那么也就无法为实时流、微服务、事件驱动架构等提供灵活的消息传输、日志流存储等功能,消息和存储是针对两个不同技术领域的概念,消息聚焦于传输和消费,存储则聚焦于一致性、持久性和低延时等条件下的存储和复制数据,同时也支持快速传播数据的功能。就郭斯杰的这篇文章来看,他告诉了我们BookKeeper、Pulsar、DistributedLog 三者的关系。我认为,这三个框架的组合已经发展到方案层面上的组合,不再仅仅能够单独工作。开源社区应该就三者搭建更好的生态链路。相信未来这三者的组合可以为实时消息处理提供更强大的能力支撑。

对本文感兴趣的读者可以访问以下三个网址了解更多信息:

http://bookkeeper.apache.org/

http://bookkeeper.apache.org/distributedlog/ https://pulsar.incubator.apache.org/

作者介绍

周明耀,2004 年毕业于浙江大学,工学硕士。13 年软件研发经验,近 10 年技术团队管理经验,4 年分布式计算、大数据技术经验。出版书籍包括《大话 Java 性能优化》、《深入理解 JVM&G1 GC》、《技术领导力 - 码农如何才能带团队》,个人公众号“麦克叔叔每晚 10 点说”出品人。个人微信号 michael_tec。

感谢杜小芳对本文的策划和审校。

2017 年 12 月 07 日 16:572968
用户头像

发布了 50 篇内容, 共 22.7 次阅读, 收获喜欢 26 次。

关注

评论

发布
暂无评论
发现更多内容

架构师训练营 1 期 - 第四周作业(vaik)

行之

极客大学架构师训练营

spring-boot-route(十一)数据库配置信息加密

Java旅途

Java Spring Boot

第三周课后练习

大大猫

极客大学架构师训练营

架构师训练营第 1 期 -week4

习习

Spring学习笔记(一)手写一个简单的Spring

无语

Spring Framework

第9周总结

Vincent

极客时间 极客大学

Redis-技术专题-哨兵配置和原理

李浩宇/Alex

甲方日常 27

句子

生活 工作 随笔杂谈 日常

区块链支付系统开发源码,usdt承兑系统搭建

WX13823153201

区块链支付系统开发

架構師訓練營 week4 作業

ilake

LeetCode题解:144. 二叉树的前序遍历,使用栈,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

架构师训练营 - 第四周作业

一个节点

极客大学架构师训练营

第二周课后练习

大大猫

极客大学架构师训练营

详解「区块链」溯源

netkiller

区块链 防伪 超级账本 标签 区块链产品溯源

第4周 作业一

bearlu

CPU 执行程序的秘密,藏在了这 15 张图里

小林coding

操作系统 计算机基础 计算机 编译器、程序语言、CPU 指令

一个典型的大型互联网应用系统使用了哪些技术方案和手段,主要解决什么问题?

高兵

第 4 周 系统架构总结

bearlu

系统架构

典型互联网公司使用的技术

happy

浅析 Java 内存模型 一

朱华

Java JMM

go-zero流数据处理利器

Kevin Wan

go stream functional

第四周作业

Geek_4c1353

极客大学架构师训练营

浅析 Java 内存模型 三

朱华

Java volatile JMM

第9周作业

Vincent

极客时间 极客大学

架構師訓練營 week4 總結

ilake

架构师训练营 - 第四周总结

一个节点

极客大学架构师训练营

第三周架构师训练营作业

Geek_4c1353

极客大学架构师训练营

架构师训练营第4周作业

悠哉

spring-boot-route(十二)整合redis做为缓存

Java旅途

Java redis Spring Boot

架构师训练营 1 期 - 第四周总结(vaik)

行之

极客大学架构师训练营

数字人民币真的来了 六年历程全回顾

CECBC区块链专委会

数字货币 DCEP

消息传输的设计方式-InfoQ