当今市面上有很多主流的消息队列中间件,比如ActiveMQ、RabbitMQ,Kafka,Pulsar、RocketMQ、NSQ、NATS、RedisStream、MQTTBroker等,生产中如何选择相应产品值得探究。

MQ(Message Queue)消息队列,是基础数据结构中“先进先出”的一种数据结构。指把要传输的数据(消息)放在队列中,用队列机制来实现消息传递——生产者产生消息并把消息放入队列,然后由消费者去处理。消费者可以到指定队列拉取消息,或者订阅相应的队列,由MQ服务端给其推送消息。

消息队列在电商系统、消息通讯、日志收集等应用中扮演着关键作用,以阿里为例,其研发的消息队列(RocketMQ)在历次天猫 “双十一” 活动中支撑了万亿级的数据洪峰,为大规模交易提供了有力保障。

消息队列的作用

  1. 解耦,将一个流程加入一层数据接口拆分成两个部分,上游专注通知,下游专注处理
  2. 缓冲,应对流量的突然上涨变更,消息队列有很好的缓冲削峰作用
  3. 异步,上游发送消息以后可以马上返回,处理工作交给下游进行
  4. 广播,让一个消息被多个下游进行处理
  5. 冗余,保存处理的消息,防止消息处理失败导致的数据丢失

Kafka

在分布式消息队列的江湖里,Kafka 凭借其优秀的性能占据重要一席。它最初由 LinkedIn 公司开发,Linkedin 于 2010 年贡献给了 Apache基金会,之后成为顶级开源项目。

关于 Kafka,官网给出的定义:Apache Kafka is a distributed streaming platform.

Kafka 作为流平台具有以下三种能力:

  1. 发布和订阅记录流,类似于消息队列或企业消息系统;
  2. 具有容错能力,且可以持久化的方式存储记录流;
  3. 当记录流产生时(发生时),可及时对其进行处理。

Kafka 适用于两类应用:

  1. 建立实时流数据管道,在系统或应用之间可靠地获取数据;
  2. 建立对数据流进行转换或反应的实时流应用程序。

kafka 包含四种核心 API:

  1. Producer API:基于该 API,应用程序可以将记录流发布到一个或多个 Kafka 主题(Topics);
  2. Consumer API:基于该 API,应用程序可以订阅一个或多个主题,并处理主题对应的记录流;
  3. Streams API:基于该 API,应用程序可以充当流处理器,从一个或多个主题消费输入流,并生成输出流输出一个或多个主题,从而有效地将输入流转换为输出流;
  4. Connector API:允许构建和运行将 Kafka 主题连接到现有应用程序或数据系统的可重用生产者或消费者。例如,关系数据库的连接器可能会捕获表的每一个更改。

Kafka 特点

  1. 快速持久化,可以在 O(1) 的系统开销下进行消息持久化;
  2. 高吞吐,在一台普通的服务器上可以达到 10W/s 的吞吐速率;
  3. 完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;
  4. 支持同步和异步复制两种HA;
  5. 支持数据批量发送和拉取;
  6. Zero-Copy,减少 IO 操作步骤;
  7. 数据迁移、扩容对用户透明;无需停机即可扩展机器;
  8. 其他特性还包括严格的消息顺序、丰富的消息拉取模型、高效订阅者水平扩展、实时的消息订阅、亿级的消息堆积能力、定期删除机制。

Kafka 优缺点

优点主要包括以下几点:

  • 客户端语言丰富,支持 Java、.NET、PHP、Ruby、Python、Go 等多种语言;
  • 性能卓越,单机写入 TPS 约在百万条/秒,消息大小 10 个字节;
  • 提供完全分布式架构,并有 Replica 机制,拥有较高的可用性和可靠性,理论上支持消息无限堆积;
  • 支持批量操作;
  • 消费者采用 Pull 方式获取消息,消息有序,通过控制能够保证所有消息被消费且仅被消费一次;
  • 有优秀的第三方 Kafka Web 管理界面 Kafka-Manager;
  • 在日志领域比较成熟,被多家公司和多个开源项目使用。

缺点主要有:

  • Kafka 单机超过 64 个队列分区,Load 会发生明显的飙高现象,队列越多,Load 越高,发送消息响应时间越长;
  • 使用短轮询方式,实时性取决于轮询间隔时间;
  • 消费失败不支持重试;
  • 支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
  • 社区更新较慢

RabbitMQ

RabbitMQ 是流行的开源消息队列系统,最新版本为 3.7.8。RabbitMQ 是 AMQP(Advanced Message Queuing Protocol)的标准实现。支持多种客户端,如 Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP 等,支持 AJAX、持久化。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

RabbitMQ 采用 Erlang 语言开发。Erlang 是一种面向并发运行环境的通用编程语言。该语言由爱立信公司在 1986 年开始开发,目的是创造一种可以应对大规模并发活动的编程语言和运行环境。Erlang 问世于 1987 年,经过十年的发展,于 1998 年发布开源版本。

RabbitMQ 采用 Erlang 开发,需要安装 Erlang 环境;不同版本的 JDK 支持的 Erlang 和 RabbitMQ Server 的版本也有所不同,建议采用高版本 JDK,避免兼容性问题。

RabbitMQ 特点

  • 异步消息传递,支持多种消息传递协议、消息队列、传递确认机制,灵活的路由消息到队列,多种交换类型;
  • 良好的开发者体验,可在许多操作系统及云环境中运行,并为大多数流行语言提供各种开发工具;
  • 可插拔身份认证授权,支持 TLS(Transport Layer Security)和 LDAP(Lightweight Directory Access Protocol)。轻量且容易部署到内部、私有云或公有云中;
  • 分布式部署,支持集群模式、跨区域部署,以满足高可用、高吞吐量应用场景;
  • 有专门用于管理和监督的 HTTP-API、命令行工具和 UI;
  • 支持连续集成、操作度量和集成到其他企业系统的各种工具和插件阵列。可以插件方式灵活地扩展 RabbitMQ 的功能。

综上所述,RabbitMQ 是一个“体系较为完善”的消息代理系统,性能好、安全、可靠、分布式,支持多种语言的客户端,且有专门的运维管理工具。

RabbitMQ 高可用方案

RabbitMQ 集群配置方式主要包括以下几种:

  1. Cluster:不支持跨网段,用于同一个网段内的局域网;可以随意得动态增加或者减少;节点之间需要运行相同版本的 RabbitMQ 和 Erlang。
  2. Federation:应用于广域网,允许单台服务器上的交换机或队列接收发布到另一台服务器上的交换机或队列的消息,可以是单独机器或集群。Federation 队列类似于单向点对点连接,消息会在联盟队列之间转发任意次,直到被消费者接受。通常使用 Federation 来连接 Internet 上的中间服务器,用作订阅分发消息或工作队列。
  3. Shovel:连接方式与 Federation 的连接方式类似,但它工作在更低层次。可以应用于广域网。

RabbitMQ 节点类型有以下几种:

  • 内存节点:内存节点将队列、交换机、绑定、用户、权限和 Vhost 的所有元数据定义存储在内存中,好处是可以更好地加速交换机和队列声明等操作。
  • 磁盘节点:将元数据存储在磁盘中,单节点系统只允许磁盘类型的节点,防止重启 RabbitMQ 时丢失系统的配置信息。
  • 问题说明:RabbitMQ 要求集群中至少有一个磁盘节点,所有其他节点可以是内存节点,当节点加入或者离开集群时,必须要将该变更通知给至少一个磁盘节点。

如果集群中唯一的一个磁盘节点崩溃的话,集群仍然可以保持运行,但是无法进行操作(增删改查),直到节点恢复。解决方案:设置两个磁盘节点,至少有一个是可用的,可以保存元数据的更改。

优点

  1. 由于 Erlang 语言的特性,RabbitMQ 性能较好、高并发;
  2. 健壮、稳定、易用、跨平台、支持多种语言客户端、文档齐全;
  3. 有消息确认机制和持久化机制,可靠性高;
  4. 高度可定制的路由;
  5. 管理界面较丰富,在互联网公司也有较大规模的应用;
  6. 社区活跃度高,更新快。

缺点

  1. 尽管结合 Erlang 语言本身的并发优势,性能较好,但是不利于做二次开发和维护;
  2. 实现了代理架构,意味着消息在发送到客户端之前可以在中央节点上排队。此特性使得 RabbitMQ 易于使用和部署,但使得其运行速度较慢,因为中央节点增加了延迟,消息封装后也比较大;
  3. 需要学习比较复杂的接口和协议,学习和维护成本较高。

ActiveMQ

ActiveMQ 是 Apache 下的一个子项目。据官网介绍,它是最流行和最强大的开源消息总线。ActiveMQ 是一个完全支持 JMS1.1 和 J2EE 1.4 规范的 JMS Provider 实现,非常快速,支持多种语言的客户端和协议,而且可以非常容易地嵌入到企业的应用环境中,并有许多高级功能。ActiveMQ 基于 Java 语言开发。

ActiveMQ 特点

  1. 支持多种语言和协议编写客户端。语言包括 Java、C、C++、C#、Ruby、Perl、Python、PHP。应用协议包括 OpenWire、Stomp REST、WS Notification、XMPP、AMQP;
  2. 支持许多高级特性,例如消息组、虚拟目的地、通配符和复合目的地;
  3. 支持 Spring,ActiveMQ 可以很容易地嵌入 Spring 应用程序中,并使用 Spring 的 XML 配置机制进行配置;
  4. 通过了常见 J2EE 服务器(如 Geronimo、JBoss4、GlassFish、WebLogic)的测试,其中通过 JCA 1.5 Resource Adaptors 的配置,可以让 ActiveMQ 自动部署到任何兼容 J2EE 1.4 商业服务器上;
  5. 支持多种传输协议,如 VM、TCP、SSL、NIO、UDP、Multicast、JGroups 以及 JXTA;
  6. 支持通过 JDBC 和 Journal 提供高速的消息持久化;
  7. 从设计上保证了高性能的集群,客户端-服务器,点对点;
  8. REST API 为消息提供技术无关和基于语言的 Web API;
  9. AJAX 允许使用纯 DHTML 实现 Web 流对 Web 浏览器的支持,允许 Web 浏览器成为消息传递结构的一部分;
  10. 获得 CXF 和 Axes 的支持,使得 ActiveMQ 可以很容易地嵌入 Web 服务栈中的任何一个,以提供可靠的消息传递;

ActiveMQ 缺点

  • 社区活跃度较低,更新慢,增加维护成本;
  • 网络资料显示,ActiveMQ 存在一些莫名其妙的问题,会丢失消息;
  • 目前,官方将重心放到 ActiveMQ 6.0 下一代产品 Apollo 上,对 5.x 的维护较少;
  • 不适合用于上千个队列的应用场景。

RocketMQ

RocketMQ 由阿里研发团队开发的分布式队列,侧重于消息的顺序投递,具有高吞吐量、可靠性等特征。RocketMQ 于 2013 年开源,2016 年捐赠给 Apache 软件基金会,并于 2017 年 9 月成为 Apache 基金会的顶级项目。

RocketMQ 用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的改进,在消息可靠性上比 Kafka 更好,目前最新版本为 4.3.1。RocketMQ 已经被业界多个大型互联网公司采用。

在阿里内部,RocketMQ 很好地服务了集团大大小小上千个应用,在每年的双十一当天,更有不可思议的万亿级消息通过 RocketMQ 流转(在 2017 年的双 11 当天,整个阿里巴巴集团通过 RocketMQ 流转的线上消息达到了万亿级,峰值 TPS 达到 5600 万),在阿里大中台策略上发挥着举足轻重的作用。

RocketMQ 特点

RcoketMQ 是一款低延迟、高可靠、可伸缩、易于使用的消息中间件。特性如下:

  • 支持发布/订阅(Pub/Sub)和点对点(P2P)消息模型;
  • 队列中有着可靠的先进先出(FIFO)和严格的顺序传递;
  • 支持拉(Pull)和推(Push)两种消息模式;
  • 单一队列百万消息的堆积能力;
  • 支持多种消息协议,如 JMS、MQTT 等;
  • 分布式高可用的部署架构,满足至少一次消息传递语义;
  • 提供 Docker 镜像用于隔离测试和云集群部署;
  • 提供配置、指标和监控等功能丰富的 Dashboard。

优点

  • 单机支持 1 万以上持久化队列;
  • RocketMQ 的所有消息都是持久化的,先写入系统 Page Cache,然后刷盘,可以保证内存与磁盘都有一份数据,访问时,直接从内存读取;
  • 模型简单,接口易用(JMS 的接口很多场合并不太实用);
  • 性能非常好,可以大量堆积消息在 Broker 中;
  • 支持多种消费模式,包括集群消费、广播消费等;
  • 各个环节分布式扩展设计,主从 HA;
  • 社区较活跃,版本更新较快。

缺点

  • 支持的客户端语言不多,目前是 Java、C++ 和 Go,后两种尚不成熟;
  • 没有 Web 管理界面,提供了 CLI(命令行界面)管理工具来进行查询、管理和诊断各种问题;
  • 没有在 MQ 核心中实现 JMS 等接口。

NATS

NATS是一个开源的、轻量级、高性能的消息传递系统,它基于发布/订阅模式,由Apcera公司开发和维护。NATS原来是使用Ruby编写,后来使用Go语言重写,性能大幅提升。

NATS的功能

  • 发布/订阅:NATS的核心是一个发布/订阅消息传递系统,允许消息生产者发布消息到特定的主题(Subject),而消息消费者可以订阅感兴趣的主题来接收消息
  • 消息队列:NATS支持消息队列的功能,确保消息能够可靠地传递到消费者。在消费者暂时不可用的情况下,消息可以在队列中等待
  • 请求/响应:NATS支持请求/响应模式,允许客户端发送请求消息并等待服务器的响应
  • 集群:NATS支持集群模式,允许跨多个服务器进行扩展,以提高性能和容错能力
  • 持久化:NATS Streaming提供了消息持久化的功能,可以确保即使在服务器重启后,消息也不会丢失
  • 安全性:NATS支持多种安全机制,包括TLS加密、用户认证和授权等
  • 跨平台:NATS可以在多种操作系统和平台(包括Linux、Windows、MacOS等)上运行
  • 多种客户端支持:NATS提供了多种编程语言的客户端库,包括Go、Java、Node.js、Python、Ruby等。

NATS的应用

  • 微服务架构:在微服务架构中,NATS可以用作服务之间的解耦通信,允许服务独立部署和扩展
  • 实时消息系统:NATS的高性能特性使其成为实时消息系统的理想选择,如实时交易系统、实时通知服务等
  • 物联网:在物联网应用中,NATS可以用于连接各种设备和后端服务,支持设备之间的实时数据交换
  • 异步任务处理:NATS可以用作异步任务队列,允许将长时间运行的任务分发到多个工作进程
  • 分布式系统:在分布式系统中,NATS可以用于跨多个节点和数据中心的事件通知和协调
  • 游戏后端:NATS的高性能和低延迟特性使其适用于游戏后端,支持玩家之间的实时交互和游戏状态同步
  • 移动应用:在移动应用中,NATS可以用于实现推送通知和实时消息传递功能

NATS的核心组件

  • NATS服务器:NATS服务器负责处理客户端连接、消息路由和分发。开发者可以部署单个NATS服务器或通过集群模式提高可用性和容错性
  • NATS客户端:NATS客户端与NATS服务器通信,发送和接收消息。客户端可以扮演发布者、订阅者、请求者或响应者的角色
  • 协议:NATS使用自定义的文本协议进行通信,简单易解,同时保持较低的性能开销。NATS支持基于TCP的通信,也支持TLS/SSL加密通信

NATS vs Kafka

  • 消耗更少的系统资源:比 Kafka 更少的基础设施支出
  • 用 Go 编写:易于嵌入,不需要像 Kafka 那样单独的服务器实例
  • 更简单的数据保留:使用 JetStream,比 Kafka 更易于管理和配置保留
  • 降低管理开销:比 Kafka 更方便开发人员管理和维护
  • 降低代码库的复杂性:由于消除了复杂的持久存储需求,代码堆栈更加简单

ActiveMQ(Java编写)、KafKa(Scala编写)、RabbitMq(Erlang编写)、Nats(之前是Ruby编写现已修改为Go)、Redis(C语言编写)、Kestrel(Scala编写不常用)、NSQ(Go语言编写),这些消息通信系统在Broker吞吐量方面的比较:(注:来自作者Derek Collison 对不同版本的消息系统进行的比较)

image-20240929115940650

开源项目地址:https://github.com/nats-io/nats-server

Go-SDK:https://github.com/nats-io/nats.go

NSQ

NSQ是一种分布式的、实时的消息平台,支持多种语言,包括Golang。NSQ是完全自主构建的,不依赖于任何第三方库,它使用Golang编写,性能非常高。NSQ使用了多种语言和协议,包括Go, Python, Ruby, Java、HTTP、TCP和HTTP长轮询等,其中最常使用的是Go语言与HTTP协议的方式接收和传输消息。

NSQ的基本概念同样是Topic和Channel,消息由生产者发送到Topic中,消费者从Channel中消费。NSQ具有良好的性能和可扩展性,同时也提供了高可靠性与消息处理质量的保证。

NSQ vs Kafka

  • NSQ 默认是把消息放到内存中,只有当队列里消息的数量超过–mem-queue-size配置的限制时,才会对消息进行持久化
  • Kafka 会把写到磁盘中进行持久化,并通过顺序读写磁盘来保障性能。持久化能够让Kafka做更多的事情:消息的重新消费(重置offset);让数据更加安全,不那么容易丢失。同时Kafka还通过partition的机制,对消息做了备份,进一步增强了消息的安全性
  • NSQ 使用的是推模型,推模型能够使得时延非常小,消息到了马上就能够推送给下游消费,但是下游消费能够无法控制,推送过快可能导致下游过载
  • Kafka 使用的拉模型,拉模型能够让消费者自己掌握节奏,但是这样轮询会让整个消费的时延增加,不过消息队列本身对时延的要求不是很大,这一点影响不是很大
  • NSQ 因为不能够把特性消息和消费者对应起来,所以无法实现消息的有序性
  • Kafka 因为消息在Partition中写入是有序的,同时一个Partition只能够被一个Consumer消费,这样就可能实现消息在Partition中的有序。自定义写入哪个Partition的规则能够让需要有序消费的相关消息都进入同一个Partition中被消费,这样达到”全局有序“

开源项目地址:https://github.com/nsqio/nsq

Go-SDK:https://github.com/nsqio/go-nsq

消息队列大比武

下面是网上介绍的一组对比数据,供参考:

image-20240924172304737

总结

1.消息队列不是万能的,对于需要强事务保证而且延迟敏感的,RPC是优于消息队列的

2.对于一些无关痛痒,或者对于别人非常重要但是对于自己不是那么关心的事情,可以利用消息队列去做。

3.支持最终一致性的消息队列,能够用来处理延迟不那么敏感的“分布式事务”场景,而且相对于笨重的分布式事务,可能是更优的处理方式

4.当上下游系统处理能力存在差距的时候,利用消息队列做一个通用的“漏斗”,在下游有能力处理的时候,再进行分发。

5.如果下游有很多系统关心你的系统发出的通知的时候,果断地使用消息队列吧。

参考阅读:

https://blog.51cto.com/zhongmayisheng/7842178

https://blog.csdn.net/citywu123/article/details/141950838

https://blog.csdn.net/weixin_44496870/article/details/130215078

(完)