个人思考:我们到底要不要引入Pulsar

2023-05-15 00:00:00 消息 节点 扩容 计算 资源

1、背景

Pulsar在2018年9月正式毕业成为Apache开源项目,消息中间件领域迎来了有一位玩家。近我们领导发现我司消息中间件相关服务器资源的利用率并不是太高,特别是CPU、内存的使用率较低。

我在思考如何有效治理的时候,也想着从市面上开源的消息中间件吸收养分,故将目光锁定在了Apache Pulsar。

我们先来看一下百度百科上关于Aapche Pulsar的描述:

Apache Pulsar 是 Apache 软件基金会项目,是下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体,采用计算与存储分离架构设计,支持多租户、持久化存储、多机房跨区域数据复制,具有强一致性、高吞吐、低延时及高可扩展性等流数据存储特性,被看作是云原生时代实时消息流传输、存储和计算佳解决方案,

这里吸引我的是“下一代云原生分布式消息流平台”,采取的是存储与计算分离架构。

我就在想存储架构分离是否真的能提升资源利用率,并且性能不会受到影响?

基于上述背景,写下了本文。

温馨提示:作为Pulsar领域的一个新手,下面的论述仅代表我个人的理解,如有表述不正确,欢迎各位朋友指正交流。

2、存储与计算分离架构

2.1 存储与计算分离概述

首先先拿我熟悉的RocketMQ消息中间件为例,我们来看一下RocketMQ的架构设计:

01

在RocketMQ中,Broker节点直接接受客户端的写入请求,然后进行计算,例如将数据从网卡中读取,计算分区,是否是定时消息、事务消息等,然后按照消息存储协议进行数据组装,后将计算结果(消息二进制格式)数据存储到本地磁盘中,也就是在RocketMQ中,计算与存储是由同一个节点来承担,也就决定了一旦计算或存储任意一个维度需要扩容时,通过增加节点的扩容手段会导致在这两个维度同时得到扩容,容易造成资源的浪费。

看到这里你或许会说RocketMQ的扩容方式,如果在磁盘容量足够的情况下,我们是不是可以选择在同一个物理机上部署多个Broker进程来增加计算能力,重复利用CPU资源呢?

我觉得在数据量不大的情况下也未尝不可,但如果并发,数据量比较多的情况下容错能力会下降。因为RocketMQ的存储架构高度依赖操作系统的PageCache机制,一台物理机上部署多个RocketMQ进程容易造成PageCache争用,导致系统性能降低,可用性降低。

我们再来看一下Pulsar的计算与存储分离架构:

02

在Pulsar架构体系中,Pulsar Broker只负责计算,例如压缩、多租户等逻辑处理后,按照消息存协议组装消息,然后通过远程TCP请求将数据存储到远程存储服务器上。这样做的一个好处计算资源+存储资源可以分别扩容。

2.2 根据扩容场景思考存储与计算分离的优势

2.2.1 存储资源不足触发扩容

像我所在的快递行业,对消息中间件重度依赖,加上数据规模巨大,日均产生的消息量众多,往往集群进行扩容的直接原因是磁盘使用空间超过了50%,担心突发流量造成磁盘空间不足,导致RocketMQ节点停止写入,从而影响大面积消息无法写入成功,对业务造成重大危害。

此种情况下,需要对存储资源进行扩容,通常的扩容方式,基本就是增加存储节点,由于存储节点基本物理机,CPU、内存这些计算类资源也会得到同步扩容,即在这种情况下,无论存储与计算是否分离,都会消耗存储与计算资源,故两者没有明显的优劣势。

2.2.2 计算资源不足触发扩容

关于消息中间件存储资源充足但计算资源不足,一个让记忆非常深刻的一个场景就是我们的日志集群。由于日志采集历史悠久,很多日志采集客户端的版本还是kafka0.10以下版本,而日志集群的服务端版本已经是Kafka2.x版本。

众所周知,Kafka的消息存储协议目前存在V0、V1、V2三种消息协议版本。采取消息协议V2版本的服务端可以兼容V0版本的客户端发送消息和拉取消息,但这个兼容是要付出性能代价的,也就是在服务端需要对消息格式进行转换,这个过程不可避免的要进行二进制数据的解码与编码,需要消耗大量的CPU资源。

当时我们查看Kafka服务端的线程中存在大量线程都在处理Lzip的解压缩,大量线程处于阻塞等待,无法及时处理其他的消息发送,导致消息发送耗时过长,从而影响了日志的采集,一个显著的问题就是在日志查看平台会存在很大的延迟。

Kafka与RocketMQ类似,都是存储与计算不分离架构,要扩容,就需要进行节点扩容,在扩容了计算资源的情况也同时增加了磁盘容量,但这些存储资源又不是刚需,必然会造成资源利用率下降,资源浪费严重。

如果是这种情况,采取存储与计算分离的Pulsar在这方面将非常具有优势。我们可以将Pulsar Broker部署在容器中,可以进行无限扩容,从而增加计算资源。

2.2.3 因存储读写瓶颈导致计算资源无法发挥

RocketMQ在存储设计方面追求的IO性能,所有的消息都顺序写入到Commitlog文件,并且写消息时串行执行(加锁),导致无法充分利用物理机的CPU资源。但生产环境物理机器一般都不会只有一块磁盘,都会挂载多磁盘,但RocketMQ这个特性的限制,并不能充分发挥并行IO的性能,从而导致CPU等计算资源的浪费,利用率无法获得较大提升的情况下就会因为磁盘性能不足而进行节点扩容。

这种情况,在存储与计算分离的架构中可以只去扩容存储,这样可以降低计算资源的浪费。

3、存储与计算分离架构的制约条件

采取存储与计算分离,看似非常完美,特别是当存储资源充足,而计算资源不够的情况,可以单独去扩容计算资源。但我觉得这里有一个前提条件就是随着计算节点的扩容,存储节点的性能不会受到计算节点的增加而显著降低,除非了是存储节点的CPU、IO都达到了较高水位线,并且超大的存储集群,必须具备资源隔离机制,才能在故障发生时控制故障故障范围

故为此我专门去调研了Apache Pulsar底层使用的存储组件Apache Bookkeeper,在存储设计上,大概采取了如下措施来提升性能(高并发,高可用性,高吞吐):

  • IO隔离(并行IO) 在Apache Bookkeeper中事务日志Journal、Ledger(数据目录)可以进行单独设置,并且无论是journal、Ledger都可以支持多目录,每一个目录会对应一个单独的实例,每一个实例拥有自己独立的写缓存、读缓存、索引文件,多个实例在上层使用ledgerId进行负载均衡。生产实践中一个目录表示一块独立磁盘。

  • 线程隔离

    Apache Bookkeeper中主要拥有四类线程,write(写)、read(读)、long poll(长轮询)与hign priority(高优先级线程池)。

  • Apache Bookkeeper中存储节点之间无需相互通信,可以水平扩容,而且扩容之间无需进行大量数据重新平衡。

  • 消息写入与消息读取隔离,消费积压并不会影响写入性能

  • 提供了副本之间读一致性,这样多个消费者可以分别从不同的存储节点进行读取,消费带宽比RocketMQ、Kafka要高

  • Apache Bookkeeper的leader文件由客户端创建

关于消息读取与写入相互不影响,我这边稍微再展开一下。

Apache Bookkeeper的读取流程:从写缓存、读缓存,如果还未查询到,则根据文件位置去读取磁盘文件(FileChannel块读取),如果读取到,会填入读缓存,并返回。

Apache Bookkeeper的写流程:首先写入journal内存队列,然后再异步刷盘,然后异步线程将其转发到数据文件(Ledger),数据文件可以根据journal文件进行数据恢复,并且journal数据写入时是顺序写。但有一个特征是journal可以使用多目录,这样重复利用物理机多物理磁盘的优势。

好了,本文就先介绍到这里了,再次声明我作为Pulsar领域的新手,文章中可能存在许多错误,共同探讨与交流,期待和大家一起不断深入研究Apache Pulsar消息中间件。

相关文章