MOM-CheatSheet

返回目录

很多内容整理/借鉴自

MOM CheatSheet | 消息中间件/消息队列知识梳理与项目盘点

Message-oriented middleware (MOM)is software or hardware infrastructure supporting sending and receiving messages between distributed systems。In computer science, message queues and mailboxes are software-engineering components used for inter-process communication (IPC), or for inter-thread communication within the same process.

消息(Message)是指在应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。

特性与应用场景

消息队列作为成熟的异步通信模式,对比常用的同步通信模式,它能够提供异步通信模式,并且起到譬如解耦、流控、复用等作用。

  • 异步通信:很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。

  • 解耦:防止引入过多的 API 给系统的稳定性带来风险;调用方使用不当会给被调用方系统造成压力,被调用方处理不当会降低调用方系统的响应能力。消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。另一方面,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。

  • 削峰和流控:在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。消息生产者不会堵塞,突发消息缓存在队列中,消费者按照实际能力读取消息。

  • 复用可扩展:一次发布多方订阅,消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。

生态圈

Kafka

Kafka 是 Apache 下的一个子项目,是一个高性能跨语言分布式发布/订阅消息队列系统,而 Jafka 是在 Kafka 之上孵化而来的,即 Kafka 的一个升级版。具有以下特性:快速持久化,可以在 O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到 10W/s 的吞吐速率;完全的分布式系统,Broker、Producer、Consumer 都原生自动支持分布式,自动实现负载均衡;支持 Hadoop 数据并行加载,对于像 Hadoop 的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka 通过 Hadoop 的并行加载机制统一了在线和离线的消息处理。Apache Kafka 相对于 ActiveMQ 是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。

RocketMQ

Pulsar

Pulsar 旨在取代 Apache Kafka 多年的主宰地位。Pulsar 在很多情况下提供了比 Kafka 更快的吞吐量和更低的延迟,并为开发人员提供了一组兼容的 API,让他们可以很轻松地从 Kafka 切换到 Pulsar。Pulsar 的最大优点在于它提供了比 Apache Kafka 更简单明了、更健壮的一系列操作功能,特别在解决可观察性、地域复制和多租户方面的问题。在运行大型 Kafka 集群方面感觉有困难的企业可以考虑转向使用 Pulsar。

RabbitMQ

RabbitMQ 是使用 Erlang 编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正因如此,它非常重量级,更适合于企业级的开发。同时实现了 Broker 构架,这意味着消息在发送给客户端时先在中心队列排队。对路由,负载均衡或者数据持久化都有很好的支持。

Redis

Redis 是一个基于 Key-Value 对的 NoSQL 数据库,开发维护很活跃。虽然它是一个 Key-Value 数据库存储系统,但它本身支持 MQ 功能,所以完全可以当做一个轻量级的队列服务来使用。对于 RabbitMQ 和 Redis 的入队和出队操作,各执行 100 万次,每 10 万次记录一次执行时间。测试数据分为 128Bytes、512Bytes、1K 和 10K 四个不同大小的数据。实验表明:入队时,当数据比较小时 Redis 的性能要高于 RabbitMQ,而如果数据大小超过了 10K,Redis 则慢的无法忍受;出队时,无论数据大小,Redis 都表现出非常好的性能,而 RabbitMQ 的出队性能则远低于 Redis。

ZeroMQ & ActiveMQ

ZeroMQ 号称最快的消息队列系统,尤其针对大吞吐量的需求场景。ZeroMQ 能够实现 RabbitMQ 不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上的复杂度是对这 MQ 能够应用成功的挑战。ZeroMQ 具有一个独特的非中间件的模式,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演这个服务器角色。你只需要简单的引用 ZeroMQ 程序库,可以使用 NuGet 安装,然后你就可以愉快的在应用程序之间发送消息了。但是 ZeroMQ 仅提供非持久性的队列,也就是说如果宕机,数据将会丢失。其中,Twitter 的 Storm 0.9.0 以前的版本中默认使用 ZeroMQ 作为数据流的传输(Storm 从 0.9 版本开始同时支持 ZeroMQ 和 Netty 作为传输模块)。

ActiveMQ 是 Apache 下的一个子项目。 类似于 ZeroMQ,它能够以代理人和点对点的技术实现队列。同时类似于 RabbitMQ,它少量代码就可以高效地实现高级应用场景。ActiveMQ 太过于复杂,在使用过程中经常出现消息丢失或者整个进程 hang 住的情况,并且难以定位。

消息消费模型

消息传递与路由

消息确认

消息保留

存储模型

配置与实践

消息传递与消费模型

作为一个消息系统,其基本结构中至少要有产生消息的组件(消息生产者,Producer)以及消费消息的组件(消费者,Consumer)。 消息模型应涵盖以下 3 个方面:

消息消费——如何发送和消费消息; 消息确认(ack)——如何确认消息; 消息保存——消息保留多长时间,触发消息删除的原因以及怎样删除;

在面向微服务或事件驱动的体系结构中,队列模型和流模型都是必需的。

在实时流式架构中,消息传递可以分为两类:队列(Queue)和流(Stream)。

队列(Queue)模型 队列模型主要是采用无序或者共享的方式来消费消息。通过队列模型,用户可以创建多个消费者从单个管道中接收消息;当一条消息从队列发送出来后,多个消费者中的只有一个(任何一个都有可能)接收和消费这条消息。消息系统的具体实现决定了最终哪个消费者实际接收到消息。 队列模型通常与无状态应用程序一起结合使用。无状态应用程序不关心排序,但它们确实需要能够确认(ack)或删除单条消息,以及尽可能地扩展消费并行性的能力。典型的基于队列模型的消息系统包括 RabbitMQ 和 RocketMQ。

流式(Stream)模型 相比之下,流模型要求消息的消费严格排序或独占消息消费。对于一个管道,使用流式模型,始终只会有一个消费者使用和消费消息。消费者按照消息写入管道的确切顺序接收从管道发送的消息。 流模型通常与有状态应用程序相关联。有状态的应用程序更加关注消息的顺序及其状态。消息的消费顺序决定了有状态应用程序的状态。消息的顺序将影响应用程序处理逻辑的正确性。

流式分区模型

Kafka 采用了基于分区(Partation)的模型

image

Kafka 中关于消息的存储只有一种文件,叫做 Partition,它是以文件的形式存储在文件系统中,比如,创建了一个名 为 page_visits 的 topic,其有 5 个 partition,那么在 Kafka 的数据目录中(由配置文件中的 log.dirs 指定的)中就有这样 5 个目录: page_visits-0, page_visits-1,page_visits-2,page_visits-3,page_visits-4,其命名规则 为-,里面存储的分别就是这 5 个 partition 的数据。

image

不管对于 Producer 还是 Consumer,单个 Partition 文件在正常的发送和消费逻辑中都是顺序 IO,充分利用 Page Cache 带来的巨大性能提升,但是,万一 Topic 很多,每个 Topic 又分了 N 个 Partition,这时对于 OS 来说,这么多文件的顺序读写在并发时变成了随机读写。

RocketMQ 则是使用了 Commit Log,一个文件集合,每个文件 1G 大小,存储满后存下一个,为了讨论方便可以把它当成一个文件,所有消息内容全部持久化到这个文件中;Consume Queue:一个 Topic 可以有多个,每一个文件代表一个逻辑队列,这里存放消息在 Commit Log 的偏移值以及大小和 Tag 属性。

image

RocketMQ 的消息整体是有序的,所以这 5 条消息按顺序将内容持久化在 Commit Log 中。Consume Queue 则用于将消息均衡地排列在不同的逻辑队列,集群模式下多个消费者就可以并行消费 Consume Queue 的消息。在队列非常多的情况下 Consume Queue 不也是和 Kafka 类似,虽然每一个文件是顺序 IO,但整体是随机 IO。不要忘记了,RMQ 的 Consume Queue 是不会存储消息的内容,任何一个消息也就占用 20 Byte,所以文件可以控制得非常小,绝大部分的访问还是 Page Cache 的访问,而不是磁盘访问。正式部署也可以将 Commit Log 和 Consume Queue 放在不同的物理 SSD,避免多类文件进行 IO 竞争。

队列模型

延时消费模型

消息确认与保留

由于分布式系统的特性,当使用分布式消息系统时,可能会发生故障。比如在消费者从消息系统中的主题消费消息的过程中,消费消息的消费者和服务于主题分区的消息代理(Broker)都可能发生错误。消息确认(ACK)的目的就是保证当发生这样的故障后,消费者能够从上一次停止的地方恢复消费,保证既不会丢失消息,也不会重复处理已经确认(ACK)的消息。

在 Apache Kafka 中,恢复点通常称为 Offset,更新恢复点的过程称为消息确认或提交 Offset。在 Apache Pulsar 中,每个订阅中都使用一个专门的数据结构——游标(Cursor)来跟踪订阅中的每条消息的确认(ACK)状态。每当消费者在主题分区上确认消息时,游标都会更新。更新游标可确保消费者不会再次收到消息。

延伸阅读

Todos