想使用消息队列,先考虑下这些问题!

本文探讨了消息队列(MQ)在微服务架构中的关键作用,包括服务解耦、提升并发性能、应对突发流量等优势,并深入讨论了MQ引入的挑战,如保证高可用性、防止消息重复消费与丢失、确保消费顺序等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

消息队列优势

消息队列(Message Queue,简称MQ),其主要用于在复杂的微服务系统中进行消息通信,它的优点可以大致整理成以下几点:

  1. 服务间解耦
  2. 提高服务并发、性能
  3. 突发流量削峰
  4. ...

服务间解耦

微服务系统业务之间相互依赖,各种调用错综复杂,如果不能良好对服务进行解耦那一个服务的可用性、并发都会受到其他服务的影响。
在没有引用MQ的之前服务调用大概是这些步骤:

图上的A服务是直接调用的,这是没啥问题的,但是服务上线后要迭代更新的麻,这个时候要是服务C的开发人员有点代码小洁癖说:我这个C服务接口命名不太好,我需要重新更新下,当A服务的小哥哥还戴着小耳机听着小歌曲,突然就得改代码了~~。
后来负责服务C的那小哥哥也不好意思了,提出大家一起使用MQ吧,于是A、C的调用就变成下面这个样子了:

服务A不直接调用C而是向消息队列中发送消息(生产者),另一边的C取出队列中的消息(消费者)进行处理,这样A、C就完成了解耦。

提高服务并发、性能

举个例子,在没引入MQ之前服务调用多个服务都是同步调用,比如像这样:

服务A要顺序的调用B、C服务来完成业务逻辑如果A->B需要200ms,A->C需要200ms,再加上自身业务逻辑处理可能需要花费500ms,其中有400ms是调用A和B的花费,明明自身100ms就能处理完还白白浪费400ms,不能忍啊于是可以引入MQ做一下改造:

这下有了MQ,A服务只需要发一条消息比如花费50ms,再加上自身业务逻辑的100ms,那整个调用过程只需要花费150ms了,这样对并发和性能都有一定的改善。

突发流量削峰

突发流量就是互联网很常见的情况,有时候有热点、突发事件,那平常QPS为100的接口,突然提升10-20倍这个时候没有MQ所有流量直接进入服务,这对服务和数据库都是很大的挑战:


再次引入MQ就情况就不一样了,服务A先将请求丢给MQ,然后可以慢慢消费掉:

消息队列带来的一些问题

使用MQ还有很多好处,但是他也会带一些麻烦事。首先就是会降低系统的可用性,比如MQ挂了怎么办呢?所以在引入MQ之前就需要考虑之后带来的哪些问题,不能只看它的好处也需要考虑它不好的地方。比如下面列出的这些问题要如果解决:

  1. 如何保证消息队列的高可用?
  2. 如何保证消息不被重复消费?
  3. 如何保证消息不丢失?
  4. 如何保证消息的消费顺序?

下面我们来分析下这些问题。

1、如何保证消息队列的高可用?

如果是单机消息队列,一台机器挂了消息队列都就不用了,这是不能接受的,如果是一个消息队列群集,一台机器挂了还有其他机器能正常提供服务,所以要保证消息队列的高可用,我们就需要做消息队列集群。

以RabbitMQ为例它有两种集群模式:

  1. 普通模式
  2. 镜像模式

普通模式

普通模式,RabbitMQ会同步各个节点的数据/状态,但不包括消息队列,默认情况下,消息队列驻留在一个节点上,尽管它们在所有节点上都是可见且可访问的。

在这种模式下,每个节点都有会所有节点的元数据信息,所以当发送消息到队列时,无论连接的是哪一个节点都能正确的发送,但是节点只会同步其他节点的元数据,消息队列的数据还是在一个节点上,如果这个节点挂了那就意味着发消息就会失败,无法保证消息队列的高可用。

镜像模式

默认情况下,RabbitMQ中Queue与Binding、Exchange不一样,它只会存于声明队列的节点中,但是可以选择使Queue跨多个节点进行镜像。
每一个镜像队列由一个Master和一个或多个镜像组成,任何队列的的操作,都会先应用到Master节点上然后传播到多个镜像节点。如果Master节点挂了,最老的镜像节点将会成为新的Master节点。

总结

RabbitMQ有两种集群方法:普通模式镜像模式,要实现消息队列的高可用可以选一种合适的集群方式来达到,关于RabbitMQ的集群搭建方式,由于篇幅有限这里就不多说,可自行查看 Distributed RabbitMQ文章。

2、如何保证消息不被重复消费?

想象下消费者收到重复的消息会发生什么情况,比如订单支付消息,如果支付服务收到两条重复的消息让用户去支付两次,那用户肯定是不愿意的,明明已经支付过了还要支付。

如上图中第四步消费消息B的时候失败了,如果支付服务在做完业务之后,发送ACK之前服务挂了,MQ没有收到ACK,由于消息还存在队列中,服务恢复正常后会再次收到消息,如果支付不做检查那用户就会发生两次支付。

要避免这个重复消费的问题,可以在消费端引入内存、Redis、数据库来保存消息消费记录,根据消息Id来判断消息是否已经被消费过。

3、如何保证消息不丢失?

假设有订单服务和支付服务,正常流程是用户下单成功,然后向支付服务发送支付消息,这里面就涉及订单服务、支付服务、MQ的交互了,消息丢失可以分为三种情况:

  1. 生产者消息丢失
  2. MQ消息丢失
  3. 消费者消息丢失

生产者消息丢失

生产者消息丢失,可以使用本地消息表解决、消息确认/重发等方式来解决。以RabbitMQ为例,它有confirm机制,发出去的消息是否入队列,会使用回调的形式告知生产者,生产者收到消息后判断是Ack还是Nak,如果是Nak则重发消息。

此时还会有问题,如果极端情况下订单服务挂了,再次重启后消息就真丢失了,所以最好还是在生产中对消息做持久化,待订单服务恢复后使用Job重新发送消息。

MQ消息丢失

MQ消息丢失一般为未开启持久化,MQ挂了再次重启后消息丢失,所以应当将消息持久化到磁盘中。如果MQ收到消息后在同步到磁盘之前MQ挂了,那磁盘中也没有消息,这样还是会导致消息丢失消息,不过这只是小概率事件。

消费者消息丢失

消费者消息丢失,大都为开启了autoAck选项,消费者收到消息后还未完成处理,此时服务挂了,由于开启了autoAck, MQ会以为此消息已经被成功消费,将消息从队列中移除,而服务恢复过后也不会收到原来的消息了。

4、如果保证消息的消费顺序?

有些场景下要保持消息的顺序消费怎么办?比如写Log都是一条条打印出来,如果发到消息队列后出现消费顺序不一致那消息的那日志就会乱掉,给看日志的人带来不必要的麻烦。比如为了加快日志的处理速度使用三个消费都处理日志:

按图上的流程,消费者A、B、C可能分别消费日志1、2、3,这时候就无法保证消息的处理顺序。要保证消息的消费顺序,首先让消息都发送到同一个队列,然后使用一个消费者去处理消息:

这样消息的处理速度就大大降低,要保持消息的顺序,则又想让消息的处理速度不至于太慢,可以引用本地队列:

MQ服务器端和客户端通信浅谈 1. WebSphere MQ的服务端的安装和配置 (1)创建名为venus.queue.manager的默认队列管理器。 在DOS窗口命令提示符下,输入以下命令: crtmqm -q venus.queue.manager (2)启动刚才创建的队列管理器。 在DOS窗口命令提示符下,输入以下命令: strmqm venus.queue.manager (3)启动MQSC命令。 在DOS窗口命令提示符下,输入以下命令: runmqsc venus.queue.manager (4)新建名为orange.queue的本地队列。 输入以下命令: define qlocal (orange.queue) 注意:MQSC中任何小写字母将自动转换成大写,除非用单引号将它们括起来。这意味着如果使用名称Orange.Queue创建了队列,则记住在MQSC以外其他命令中都必须使用Orange.Queue。 (5)停止MQSC。 输入以下命令: end 现在,我们已经定义了以下对象: (1)名为venus.queue.manager的默认队列管理器。 (2)名为ORANGE.QUEUE的队列。 测试对象:要测试队列管理器和队列,请使用样本程序amqsput(将消息放入队列)和amqsget(从队列获取消息),步骤如下: (1)启动DOS窗口,进入到C:\Program Files\IBM\WebSphere MQ\bin目录下。 (2)将消息放入队列。 输入以下命令: amqsput ORANGE.QUEUE 显示以下消息: Sample amqsput0 start Target queue is ORANGE.QUEUE (3)输入一些字符数据,然后双击"Enter"键,则显示以下消息: Sample amqsput0 end 现在消息已经被放在队列中了。 (4)要从队列中获取消息,输入以下命令: amqsget ORANGE.QUEUE 在屏幕上将显示您刚才输入的字符数据消息。暂停后,例子程序结束。 如果以上步骤都能完成,则完成了本地安装的验证。 注意:如果在任何阶段中断整个安装过程,则应该从头开始重新运行安装。 2. WebSphere MQ客户端的安装和配置 如果我们开发的基于MQ的应用与MQ服务器不在同一台机器上,那么就必须在MQ应用端安装MQ客户端。下面我们以在Suse Linux 10下安装WebSphere MQ v6.0 Client为例介绍MQ客户端在Linux下的安装和配置步骤。 (1)解压缩MQ客户端安装包:C84CJML.WebSphere MQ V6.0 Linux x86 Client.tar.gz,命令如下: tar -xvfz C84CJML.WebSphere MQ V6.0 Linux x86 Client.tar.gz (2)创建WebSphere MQ 必需的文件系统,命令如下: mkdir /opt/mqm # for product code mkdir /var/mqm # for working data (3)创造MQ用户和用户组,命令如下: groupadd mqm useradd -d /var/mqm -g mqm -G mqm mqm passwd mqm # change password to "password" (4)为mqm用户组添加root组 cd /etc vi group 修改 mqm:!:1000:mqm ? mqm:!:1000:mqm,root (5)调整系统参数(视需要而定) 修改最大共享内存,命令如下: cat /proc/sys/kernel/shmmax echo 268435456 >/proc/sys/kernel/shmmax 修改最大文件句柄,命令如下: cat /proc/sys/fs/file-max echo 32768 >/proc/sys/fs/file-max (6)转到WebSphere MQ解压后的目录中,获得WebSphere MQ授权,命令如下: ./mqlicense.sh -text_only 阅读全文后,选择1接受授权。 (7)安装WebSphere MQ客户端,命令如下: rpm -ivh MQSeriesRuntime-6.0.0-0.i386.rpm rpm -ivh MQSeriesSDK-6.0.0-0.i386.rpm rpm -ivh MQSeriesJava-6.0.0-0.i386.rpm rpm -ivh MQSeriesClient-6.0.0-0.i386.rpm rpm -ivh MQSeriesSamples-6.0.0-0.i386.rpm (8)安装校验 rpm -qa | grep MQSeries 结果如下则表明MQ 客户端的安装已成功。 MQSeriesRuntime-6.0.0-0 MQSeriesSDK-6.0.0-0 MQSeriesJava-6.0.0-0 MQSeriesClient-6.0.0-0 MQSeriesSamples-6.0.0-0 3. WebSphere MQ的服务端和客户端的互联 本节的WebSphere MQ的服务端和客户端不在同一台机器上。 本节以上述1,2两个步骤安装好的MQ服务端和MQ客户端为例,介绍如何配置MQ的服务端和客户端使两者能够互联。 (1)服务端配置 1)点"开始"->"所有程序"->"IBM WebSphere MQ"->"WebSphere MQ 资源管理器",进入WebSphere MQ 资源管理器界面。如下图所示: 2)创建名为"QM_JACK"的队列管理器 选中"队列管理器"->"新建"->"队列管理器",如下图所示: 在队列管理中输入"QM_JACK",其他选项默认不变,点"下一步": 设置队列日志(本步骤采用系统默认设置),点"下一步": 启动队列管理器,创建服务器连接通道,允许在TCP/IP上进行队列管理器的远程管理,点击"下一步": 设置队列管理器QM_JACK的侦听端口:8927(用户可以根据需要自行更改端口号),点击"完成"。 系统进入等待界面: 队列管理器QM_JACK创建成功 3)在QM_JACK下创建名为"QUEUE_RECV"和"QUEUE_REPLY"的本地队列(客户可以根据自己的需求随意更改本地队列的名字和数量,这里创建这两个本地队列只是为之后的MQ_Tuxedo项目作准备): 设置队列名后其他属性全为系统默认值,点击"确定"。 4)在QM_JACK下创建名为"CNN_JACK"的服务器通道。 通道名称设为CNN_JACK,其他选项保留为系统默认设置,点击"确定"。 5)在MQ服务器端的计算机用户中添加MQ客户端所在计算机的系统用户。比如我的MQ客户端被Suse Linux下的root用户使用,那么,我们就需要在MQ的服务端(也就是Windows xp系统中添加名为"root"的用户)所在的计算机系统中添加名为"root"的用户。具体步骤如下: 点击"开始"->"控制面板"->"计算机管理"->"系统工具"->"本地用户和组"->"用户",点右键,选"新用户",如下图所示: 创建新用户:root(注意:这里的用户名"root"是MQ客户端所在系统的用户名,用户要根据具体情况进行修改) 随意设置一个有效密码,选中密码永不过期,点击"创建"。 将新用户加入mqm组(注意:这里的mqm组是我们安装完MQ Server后系统自动创建的) 启动监听器 Runmqlsr –m QM_JACK" 设置 export MQCHLTAB= /PTAH/AMQCLCHL.TAB 重启机器。至此,MQ服务端的配置完成。 (2)客户端配置 按照我们前面的步骤,MQ客户端是安装在Suse Linux 10 上,所以本节以Suse Linux 10下的MQ Client v6为例说明如何配置MQ客户端才能连上MQ服务器。 1)设置MQSERVER环境变量,命令如下: export MQSERVER='CNN_JACK/tcp/9.186.110.133(8927)' 参数说明: 服务器连接通道名称:CNN_JACK 服务器连接通道协议类型:tcp 服务器IP地址:9.186.110.133 服务器连接通道所在的队列管理器QM_JACK的侦听端口:8927 2)测试客户端与服务端的网络连接 在客户端使用ping命令检查客户端是否能连上服务端机器。 同样,在服务端使用ping命令检查服务端是否能连上客户端机器。 测试完成后,进入下一步。 3)进入/opt/mqm/samp/bin,使用样本程序amqsputc(用来将消息发送到服务端队列)和amqsgetc(用来从服务端队列中读取消息)来测试客户端与服务端的MQ连接是否畅通,具体步骤如下: 输入命令: ./amqsputc QUEUE_RECV QM_JACK 则会显示以下消息: Sample AMQSPUT0 start target queue is QUEUE_RECV 输入一些字符,然后双击"Enter"键,则显示如下信息: Sample AMQSPUT0 end 要从队列获取消息,则输入如下命令: ./amqsgetc QUEUE_RECV QM_JACK 则会在屏幕上打印出刚才输入的字符数据消息。暂停后,;例子程序结束。 如果以上步骤都能够完成,则完成了MQ服务端和客户端的互联配置。 注意:有时候,MQ客户端联服务端时会因为两端字符集编码CCSID不一样造成连接的失败,典型错误是:MQCONN ended with reason code 2059。在队列管理器的错误日志(位于/var/mqm/errors/AMQERROR**.log)中有如下信息: ------------------------------------------------------------------------------- 08/23/2002 11:51:48 AMQ9541: CCSID supplied for data conversion not supported. EXPLANATION: The program ended because, either the source CCSID '437' or the target CCSID '1381' is not valid, or is not currently supported. ACTION: Correct the CCSID that is not valid, or ensure that the requested CCSID can be supported. 解决方法如下:在服务端用runmqsc qmname命令,输入dis qmgr查看CCSID属性值nnnn,在客户端上配置环境变量: Windows 下:set MQCCSID=nnnn(在服务端看到的CCSID), Linux下:export MQCCSID=nnnn(在服务端看到的CCSID),重新启动MQ 客户端机器即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值