《新程序员》杂志|李鹏辉谈开源云原生消息流系统
编者荐语:
云原生的诞生是为了解决传统应用在架构、故障处理、系统迭代等方面的问题,而开源则为企业打造云原生的架构贡献了中坚力量。本文作者在全身心投入开源以及每日参与云原生的过程中,对开源行业和云原生流系统解决方案有了不一样的思考与实践。
以下文章来源于 CSDN ,作者李鹏辉
本文出自《新程序员·云原生和全面数字化实践》。 作者李鹏辉,Apache Pulsar PMC 成员,StreamNative 首席工程师。责编 CSDN 唐小引。
随着业务与环境的变化,云原生的趋势越来越明显。现在正是企业从云计算向云原生转型的时代,云原生理念经过几年落地实践的打磨已经得到了企业的广泛认可,云上应用管理更是成为企业数字化转型的必选项。可以说,现在的开发者,或正在使用基于云原生技术架构衍生的产品和工具,或正是这些产品和工具的开发者。
云原生成为基础战略
那么,什么是云原生?每个人都有不同的解释。我认为,首先,云原生就是为了在云上运行而开发的应用,是对于企业持续快速、可靠、规模化地交付业务的解决方案。云原生的几个关键词,如容器化、持续交付、DevOps、微服务等无一不是在诠释其作为解决方案的特性与能力,而 Kubernetes 更以其开创性的声明式 API 和调节器模式,奠定了云原生的基础。
其次,云原生是一种战略。云原生的诞生是为了解决传统应用在架构、故障处理、系统迭代等方面存在的问题。从传统应用到云,与其说是一次技术升级,不如说将其视为战略转型。企业上云面临应用开发、系统架构、企业组织架构,甚至商业产品的全面整合,是否加入云原生大潮一次是将从方方面面影响企业长期发展的战略性决策。
搭配开源底色的云原生
近几年诞生的与架构相关的开源项目大部分采用云原生架构设计,开源为企业打造云原生的架构贡献了中坚力量。
开源技术与生态值得信任,云可以给用户带来好的伸缩性,降低资源浪费。云原生和开源的关系也可以从以 CNCF 为主的开源基金会持续推进云原生的发展中略窥一二。许多开源项目本身就是为云原生架构而生的,这是用户上云会优先考虑的基础软件特点。
以 Apache 软件基金会为例,它是一个中立的开源软件孵化和治理平台。Apache 软件基金会在长期的开源治理中,总结出的 Apache 之道(Apache Way)被大家奉为圭臬,其中“社区大于代码”广为流传,即没有社区的项目是难以长久的。一个社区和代码保持高活跃度的开源项目,经过全世界开发者在多种场景的打磨,可以不断完善、频繁地升级迭代,并诞生丰富的生态以满足不同的用户需求。云原生大潮与当前开源大环境两种因素叠加,就会使那些伴随技术环境不断升级的优秀技术推陈出新、脱颖而出,不适应时代的技术会渐渐落后,甚至被淘汰。正如我之前所说,云原生是战略性决策,企业的战略性决策必定会首选最先进、最可靠的技术。
为云而生的消息流数据系统
前文讲述了云原生环境下开源的重要性,那么一个云原生的开源项目需要如何去设计、规划和演进?云原生时代的企业数字化转型应如何选择消息和流系统?在本文中,我将以自己全身心投入的开源云原生消息和流数据系统 Apache Pulsar 的设计和规划为例进行剖析。希望能够为大家提供参考思路,并为寻求消息和流数据系统解决方案带来启发。
回顾历史:消息与流的双轨制
消息队列通常用于构建核心业务应用程序服务,流则通常用于构建包括数据管道等在内的实时数据服务。消息队列拥有比流更长的历史,也就是开发者们所熟悉的消息中间件,它侧重在通信行业,常见的系统有 RabbitMQ 和 ActiveMQ。相对来说,流系统是一个新概念,多用于移动和处理大量数据的场景,如日志数据、点击事件等运营数据就是以流的形式展示的,常见的流系统有 Apache Kafka 和 AWS Kinesis。
由于之前的技术原因,人们把消息和流分为两种模型分别对待。企业需要搭建多种不同的系统来支持这两种业务场景(见图 1),由此造成基础架构存在大量“双轨制”现象,导致数据隔离、数据孤岛,数据无法形成顺畅流转,治理难度大大提升,架构复杂度和运维成本也都居高不下。
图 1 企业搭建不同的系统支持业务场景导致的“双轨制”
基于此,我们亟须一个集成消息队列和流语义的统一实时数据基础设施,Apache Pulsar 由此而生。消息在 Apache Pulsar 主题上存储一次,但可以通过不同订阅模型,以不同的方式进行消费(见图 2),这样就解决了传统消息和流“双轨制”造成的大量问题。
图 2 Apache Pulsar 集成消息队列与流语义
实现天然云原生的关键要素
上文提到,云原生时代带给开发者的是能够快速扩缩容、降低资源浪费,加速业务推进落地。有了类似 Apache Pulsar 这种天然云原生的消息和流数据基础设施,开发者可以更好地聚焦在应用程序和微服务开发,而不是把时间浪费在维护复杂的基础系统上。
为什么说 Apache Puslar 是“天然云原生”?这与在当初设计原型的底层架构有关。存储计算分离、分层分片的云原生架构,极大地减轻了用户在消息系统中遇到的扩展和运维困难,能在云平台以更低成本给用户提供优质服务,能够很好地满足云原生时代消息系统和流数据系统的需求。
生物学有一个结论,叫“结构与功能相适应”。从单细胞原生生物到哺乳动物,其生命结构越来越复杂,具备的功能也越来越高级。基础系统同理,“架构与功能相适用”体现在 Apache Pulsar 上有这样几点:
存储计算分离架构可保障高可扩展性,可以充分发挥云的弹性优势。
跨地域复制,可以满足跨云数据多备的需求。
分层存储,可充分利用如 AWS S3 等的云原生存储,有效降低数据存储成本。
轻量化函数计算框架 Pulsar Functions,类似于 AWS Lambda 平台,将 FaaS 引入 Pulsar。而 Function Mesh 是一种 Kubernetes Operator,助力用户在 Kubernetes 中原生使用 Pulsar Functions 和连接器,充分发挥 Kubernetes 资源分配、弹性伸缩、灵活调度等特性。
基础架构:存储计算分离、分层分片
上文说到,Pulsar 在诞生之初就采用了云原生的设计,即存储计算分离的架构,存储层基于 Apache 软件基金会开源项目 BookKeeper。BookKeeper 是一个高一致性、分布式只追加(Append-only)的日志抽象,与消息系统和流数据场景类似,新的消息不断追加,刚好应用于消息和流数据领域。
Pulsar 架构中数据服务和数据存储是单独的两层(见图 3),数据服务层由无状态的 Broker 节点组成,数据存储层则由 Bookie 节点组成,服务层和存储层的每个节点对等。Broker 仅负责消息的服务支持,不存储数据,这为服务层和存储层提供了独立的扩缩容能力和高可用能力,大幅减少了服务不可用时间。BookKeeper 中的对等存储节点,可以保证多个备份被并发访问,也保证了即使存储中只有一份数据可用,也可以对外提供服务。
图 3 Pulsar 架构
在这种分层架构中,服务层和存储层都能够独立扩展,提供灵活的弹性扩容,特别是在弹性环境(如云和容器)中能自动扩缩容,动态适应流量峰值。同时,显著降低集群扩展和升级的复杂性,提高系统的可用性和可管理性。此外,这种设计对容器也非常友好。
Pulsar 将主题分区按照更小的分片粒度来存储(见图 4)。这些分片被均匀打散,将会分布在存储层的 Bookie 节点上。这种以分片为中心的数据存储方式,将主题分区作为一个逻辑概念,分为多个较小的分片,并均匀分布和存储在存储层中。这样的设计可以带来更好的性能、更灵活的扩展性和更高的可用性。
图 4 分片存储模型
从图 5 可见,相比大多数消息队列或流系统(包括 Apache Kafka)均采用单体架构,其消息处理和消息持久化(如果提供了的话)都在集群内的同一个节点上。此类架构设计适合在小型环境部署,当大规模使用时,传统消息队列或流系统就会面临性能、可伸缩性和灵活性方面的问题。随着网络带宽的提升、存储延迟的显著降低,存储计算分离的架构优势变得更加明显。
图 5 传统单体架构 vs 存储计算分层架构
读写区别
接着上述内容,我们来看一下消息的写入、读取等方面的区别体现在哪里。
首先看写入。图 6 左侧是单体架构的应用,数据写入 leader,leader 将数据复制到其他 follower,这是典型的存储计算不分离的架构设计。在图 6 右侧则是存储计算分离的应用,数据写入 Broker,Broker 并行地往多个存储节点上写。假如要求 3 个副本,在选择强一致性、低延迟时两个副本返回才算成功。如果 Broker 有 leader 的角色,就会受限于 leader 所在机器的资源情况,因为 leader 返回,我们才能确认消息成功写入。
图 6 单体架构与分层架构写入对比
在右侧对等的分层架构中,三个中任意两个节点在写入后返回即为成功写入。我们在 AWS 上进行性能测试时发现,两种结构在刷盘时的延迟也会有几毫秒的差距:在单机系统中落在 leader 上的 topic 会有延迟,而在分层架构中受到延迟影响较小。
在实时数据处理中,实时读取占据了 90% 的场景(见图 7)。在分层架构中,实时读取可以直接通过 Broker 的 topic 尾部缓存进行,不需要接触存储节点,能够在很大程度上提升数据读取的效率和实时性。
图 7 单体架构与分层架构读取实时数据对比
架构也导致了读取历史数据时的区别。从图 8 可见,在单体架构中,回放消息时直接找到 leader,从磁盘上读取消息。在存储计算分离的架构上,需要将数据加载到 Broker 再返回客户端,以此保证数据读取的顺序性。当读取数据对顺序性没有严格要求时,Apache Pulsar 支持同时并行从多个存储节点读取数据段,即使是读取一个 topic 的数据也可以利用多台存储节点的资源提升读取的吞吐量,Pulsar SQL 也是利用这种方式来读取的。
图 8 单体架构与分层架构读取历史数据对比
IO 隔离
BookKeeper 内部做了很好的数据写入和读取的 IO 隔离。BookKeeper 可以指定两类存储设备,图 9 左侧是 Journal 盘存放 writeheadlog,右侧才是真正存储数据的地方。即使在读取历史数据时,也会尽可能地保证写入的延迟不会受到影响。
图 9 BookKeeper 的 IO 隔离
如果利用云平台的资源,Pulsar 的 IO 隔离可以让用户选择不同的资源类型。由于 Journal 盘并不需要存放大量的数据,很多云用户会根据自己的需求配置来达到低成本、高服务质量的目的,如 Journal 盘使用低存储空间、高吞吐低延迟的资源,数据盘选择对应吞吐可以存放大量数据的设备。
扩缩容
存储计算分离允许 Broker 和 BookKeeper 分别进行扩缩容,下面为大家介绍扩缩容 topic 的过程。假设 n 个 topic 分布在不同的 Broker 上,新的 Broker 加入能够在 1s 内进行 topic ownership 的转移,可视为无状态的 topic 组的转移。这样,部分 topic 可以快速地转移至新的 Broker。
对于存储节点来说,多个数据分片散布在不同的 BookKeeper 节点上,扩容时即新加入一个 BookKeeper,并且这种行为不会导致历史数据的复制。每一个 topic 在经历一段时间的数据写入后,会进行分片切换,即切换到下一个数据分片。在切换时会重新选择 Bookies 放置数据,由此达到逐渐平衡。如果有 BookKeeper 节点挂掉,BookKeeper 会自动补齐副本数,在此过程中,topic 不会受到影响。
跨云数据多备
Pulsar 支持跨云数据多备(见图 10),允许组成跨机房集群来进行数据的双向同步。很多国外用户在不同的云厂商部署跨云集群,当有一个集群出现问题时,可以快速切换到另外的集群。异步复制只会产生细微的数据同步缺口,但可以获得更高的服务质量,同时订阅的状态也可以在集群间同步。
图 10 跨云数据多备
进入无服务器架构时代
Pulsar Functions 与 Function Mesh 让 Pulsar 跨入了无服务器架构时代。Pulsar Functions 是一个轻量级的计算框架,主要是为了提供一个部署和运维都能非常简单的平台。Pulsar Functions 主打轻量、简单,可用于处理简单的 ETL 作业(提取、转化、加载)、实时聚合、事件路由等,基本可以覆盖 90%以上的流处理场景。Pulsar Functions 借鉴了无服务器架构(Serverless)和函数即服务(FaaS)理念,可以让数据得到“就近”处理,让价值得到即时挖掘(见图 11)。
图 11 单条 Pulsar Function 消息流转
Pulsar Functions 只是单个应用函数,为了让多个函数关联在一起,组合完成数据处理目标,诞生了 Function Mesh(已开源)。Function Mesh 同样采用无服务器架构,它也是一种 Kubernetes Operator,有了它,开发者就可以在 Kubernetes 上原生使用 Pulsar Functions 和各种 Pulsar 连接器,充分发挥 Kubernetes 资源分配、弹性伸缩、灵活调度等特性。例如,Function Mesh 依赖 Kubernetes 的调度能力,确保 Functions 的故障恢复能力,并且可以在任意时间适当调度 Functions。
Function Mesh 主要由 Kubernetes Operator 和 Function Runner 两个组件组成。Kubernetes Operator 监测 Function Mesh CRD、创建 Kubernetes 资源(即 StatefulSet),从而在 Kubernetes 运行 Function、连接器和 Mesh。Function Runner 负责调用 Function 和连接器逻辑,处理从输入流中接收的事件,并将处理结果发送到输出流。目前,Function Runner 基于 Pulsar Functions Runner 实现。
当用户创建 Function Mesh CRD 时(见图 12),Function Mesh 控制器从 Kubernetes API 服务器接收已提交的 CRD,然后处理 CRD 并生成相应的 Kubernetes 资源。例如,Function Mesh 控制器在处理 Function CRD 时,会创建 StatefulSet,它的每个 Pod 都会启动一个 Runner 来调用对应的 Function。
图 12 Function Mesh 处理 CRD 过程
Function Mesh API 基于现有 Kubernetes API 实现,因此 Function Mesh 资源与其他 Kubernetes 原生资源兼容,集群管理员可以使用现有 Kubernetes 工具管理 Function Mesh 资源。Function Mesh 采用 Kubernetes Custom Resource Definition(CRD),集群管理员可以通过 CRD 自定义资源,开发事件流应用程序。
用户可以使用 kubectl CLI 工具将 CRD 直接提交到 Kubernetes 集群,而无须使用 pulsar-admin CLI 工具向 Pulsar 集群发送 Function 请求。Function Mesh 控制器监测 CRD 并创建 Kubernetes 资源,运行自定义的 Function、Source、Sink 或 Mesh。这种方法的优势在于 Kubernetes 直接存储并管理 Function 元数据和运行状态,从而避免在 Pulsar 现有方案中可能存在的元数据与运行状态不一致的问题。
结语
在本文中,我分享了自己在云原生环境下,对于开源行业的思考和云原生流平台解决方案的技术实践。作为一名全身心投入的开源人,我很高兴看到近几年有越来越多的人认可开源理念并成为开源开发者与贡献者,开源行业正在蓬勃发展。我希望能和无数的开发者一样,在开源道路上一往无前,助力更多企业加速云原生和数字化进程。
作者简介
李鹏辉:Apache 软件基金会顶级项目 Apache Pulsar PMC 成员和 Committer,目前就职于 StreamNative 公司担任首席架构师。长期以来,其工作领域都与消息系统、微服务和 Apache Pulsar 息息相关,曾于 2019 年推动 Pulsar 在智联招聘的落地,构建内部统一消息服务,之后加入 Apache Pulsar 商业化公司 StreamNative,完成个人身份从一名开源项目用户到开源项目开发者的转变。他和他的团队在 StreamNative 负责支持海量规模消息场景用户落地 Apache Pulsar。
关注公众号「Apache Pulsar」,获取干货与动态
加入 Apache Pulsar 中文交流群 👇🏻
评论