消息中间件中的数据存储
顺序读写
消息系统数据持久化一般采用为每个消费者队列提供一个B 树或其他通用的随机访问数据结构来维护消息的元数据,B 树操作的时间复杂度为O(log n) ,O(log n) 的时间复杂度可以看成是一个常量时间,而且B 树可以支持各种各样的事务性和非事务性语义消息的传递。尽管B 树具有这些优点,但这并不适合磁盘操作。目前的磁盘寻道时间一般在10ms 以内,对一块磁盘来说,在同一时刻只能有一个磁头来读写磁盘,这样在并发IO 能力上就有问题。同时,对树结构性能的观察结果表明:其性能会随着数据的增长而线性下降。鉴于消息系统本身的作用考虑,数据的持久化队列可以建立在简单地对文件进行追加的实现方案上。
操作系统每次从磁盘读写数据的时候,需要先寻址,也就是先要找到数据在磁盘上的物理位置,然后再进行数据读写。如果是机械硬盘,这个寻址需要比较长的时间,因为它要移动磁头,这是个机械运动,机械硬盘工作的时候会发出咔咔的声音,就是移动磁头发出的声音。顺序读写相比随机读写省去了大部分的寻址时间,它只要寻址一次,就可以连续地读写下去,所以说,性能要比随机读写要好很多。
以Kafka 为例,因为是顺序追加,所以Kafka 在设计上是采用时间复杂度O(1) 的磁盘结构,它提供了常量时间的性能,即使是存储海量的信息(TB 级) 也如此,性能和数据的大小关系也不大,同时Kafka 将数据持久化到磁盘上,这样只要磁盘空间足够大数据就可以一直追加,而不会像一般的消息系统在消息被消费后就删除掉,Kafka 提供了相关配置让用户自己决定消息要保存多久,这样为消费者提供了更灵活的处理方式,因此Kafka 能够在没有性能损失的情况下提供一般消息系统不具备的特性。
WAL
包括数据库内很多的具有持久化能力的中间件都会采用WAL ,Writing Ahead Log 策略来保证数据的安全性与一致性。从客户端获取到的数据往往是会被首先写入到类似于Commit Log 这样的文件中,该文件是实时顺序追加写入的。当系统发生了某些异常的崩溃后,即可以从这样的Commit Log 中进行数据恢复。在写入Commit Log 之后,数据或者对于数据的描述信息才会被写入到实际的表或分区文件中。数据文件往往采用异步刷盘的策略,而刷盘的时候也是依据时间或者数据的策略:
Flush driven by timer: There is a backend timer which flushes data in cache periodically to disks. The period is configurable via parameter commitTime in system configuration file taos.cfg.
Flush driven by data: Data in the cache is also flushed to disks when the left buffer size is below a threshold. Flush driven by data can reset the timer of flush driven by the timer.
Flush
Kafka 与Pulsar 存储对比
Apache Kafka 和Apache Pulsar 都有类似的消息概念。客户端通过主题与消息系统进行交互。每个主题都可以分为多个分区。然而,Apache Pulsar 和Apache Kafka 之间的根本区别在于Apache Kafka 是以分区为存储中心,而Apache Pulsar 是以Segment 为存储中心。
在Apache Kafka 中,分区只能存储在单个节点上并复制到其他节点,其容量受最小节点容量的限制。这意味着容量扩展需要对分区重新平衡,这反过来又需要重新复制整个分区,以平衡新添加的代理的数据和流量。重新传输数据非常昂贵且容易出错,并且会消耗网络带宽和IO 。维护人员在执行此操作时必须非常小心,以避免破坏生产系统。
Kafka 中分区数据的重新拷贝不仅发生在以分区为中心的系统中的群集扩展上。许多其他事情也会触发数据重新拷贝,例如副本故障,磁盘故障或计算机的故障。在数据重新复制期间,分区通常不可用,直到数据重新复制完成。例如,如果您将分区配置为存储为3 个副本,这时,如果丢失了一个副本,则必须重新复制完整个分区后,分区才可以再次可用。
在用户遇到故障之前,通常会忽略这种缺陷,因为许多情况下,在短时间内仅是对内存中缓存数据的读取。当数据被保存到磁盘后,用户将越来越多地不可避免地遇到数据丢失,故障恢复的问题,特别是在需要将数据长时间保存的场合。
相反,在Apache Pulsar 中,同样是以分区为逻辑单元,但是以Segment 为物理存储单元。分区随着时间的推移会进行分段,并在整个集群中均衡分布,旨在有效地迅速地扩展。Pulsar 是以Segment 为中心的,因此在扩展容量时不需要数据重新平衡和拷贝,旧数据不会被重新复制,这要归功于在Apache BookKeeper 中使用可扩展的以Segment 为中心的分布式日志存储系统。
通过利用分布式日志存储,Pulsar 可以最大化Segment 放置选项,实现高写入和高读取可用性。例如,使用BookKeeper ,副本设置等于2 ,只要任何2 个Bookie 启动,就可以对主题分区进行写入。对于读取可用性,只要主题分区的副本集中有1 个处于活动状态,用户就可以读取它,而不会出现任何不一致。
总之,Apache Pulsar 这种独特的基于分布式日志存储的以Segment 为中心的发布/ 订阅消息系统可以提供许多优势,例如可靠的流式系统,包括无限制的日志存储,无需分区重新平衡的即时扩展,快速复制修复以及通过最大化数据放置实现高写入和读取可用性选项。