news 2026/10/19 17:10:46

JMS与ActiveMQ实战:消息队列核心概念、代码实践与排错指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JMS与ActiveMQ实战:消息队列核心概念、代码实践与排错指南

做后端开发的朋友,多少都听过JMS和ActiveMQ这两个词。如果你还没碰过消息中间件,第一次看到它们的时候大概率是懵的——JMS到底是个什么标准,ActiveMQ和我天天写的Java代码又有什么关系?我在某公司做服务端开发时,第一次被叫去优化一个日志上报接口,业务高峰期Tomcat线程池被打满,数据库连接也濒临崩溃。后来引入ActiveMQ做异步削峰,才真正把这件事扛过去。也正是那段时间,我把JMS和ActiveMQ从概念到代码完整啃了一遍,踩了不少坑。这篇文章就是那段时间的学习记录和实战总结,给正准备入门或者入门到一半的兄弟们做个参考,不管你是学生还是刚转后端没几年的开发者,应该都能从中拿走一些直接能用的东西。

1. JMS和ActiveMQ到底在解决什么问题

1.1 先从一个真实场景说起:接口变慢了,问题出在哪

那年我们公司的日报系统接了一个新需求:用户每次操作后,需要把行为日志上报给数据分析平台。一开始实现得很直接,业务接口里同步调日志服务的HTTP接口,日志服务再写入数据库。上线后前两个月一切正常,后续接入的业务越来越多,日志量翻了几倍,问题开始冒头了:接口平均响应时间从50毫秒涨到了800毫秒,高峰期更是直接超时;日志服务一重启,业务接口也跟着报错,下游抖动直接传导到上游。

这个场景应该不少人都有共鸣,核心问题就是同步耦合太深。一次用户请求,本来只需要做自己的业务,结果被迫等一个慢吞吞的日志写入完成。你想想,就像你去餐厅点餐,服务员非要等到后厨把下一周的食材都备好才开始给你下单——明明只需要把订单递进去就够了。解决思路也很简单:把日志消息先丢到一个中间地带,让业务接口立刻返回,再由后端的消费者慢慢地、批量地去处理这些日志。这个“中间地带”,就是消息队列,而ActiveMQ就是这样一个中间件。

当然这只是最典型的异步解耦场景。消息队列还能干流量削峰的活,比如秒杀场景,瞬时请求量可能是平时的上百倍,直接打到数据库上基本就是秒挂,但通过在入口处把请求转成消息慢慢消化,系统就能平稳扛过去。还有跨系统数据同步、广播通知、日志收集,这些都是消息队列的高频使用场景。搞清楚了这些场景,你才会明白,JMS和ActiveMQ不是某个框架的花架子,而是后端架构里很实在的基础设施。

1.2 JMS和ActiveMQ是什么关系:一个标准,一个实现

先说结论:JMS是Java定义的一套消息服务API规范,ActiveMQ是实现了这套规范的具体产品。打个比方,JMS就像JDBC,ActiveMQ就像MySQL,你写代码的时候用的是JDBC的接口,底下跑的是MySQL驱动。同理,你写JMS代码的时候用的是javax.jms包里的接口,底下真正干活的是ActiveMQ的客户端库。

这个区分特别重要,因为很多人一开始会把它们混为一谈。JMS全称Java Message Service,它是Java社区定义的一组接口,规定了消息生产者、消费者、消息目的地这些核心模型应该长什么样,但不管你底层用的是ActiveMQ还是其他实现规范的消息中间件,写出来的代码结构是类似的。ActiveMQ是Apache基金会下的开源项目,算是最老牌的消息中间件之一,它完整实现了JMS 1.1规范,还提供了一些自己的扩展能力。

学习的时候,我的建议是先以ActiveMQ作为载体去理解JMS规范。因为ActiveMQ部署简单,纯Java开发,文档多,社区里踩坑记录也丰富,对新手非常友好。等把JMS这套模型吃透了,以后换别的JMS实现,或者去学别的消息中间件,你会发现很多概念都是相通的,学习成本会直线下降。我见过不少人一上来就研究什么分布式消息队列的高可用架构,结果连Queue和Topic都分不清,这种学习路径其实很亏。

1.3 学习JMS的正确路线:概念别贪多,代码必须上手

JMS涉及的概念其实不少,但很多是边角料,入门阶段根本用不上。我把自己的学习路线整理了一下,大致分四个阶段:

  • 第一阶段:理解消息模型,搞明白Queue、Topic、消息生产者、消息消费者分别是什么,能画出消息流转的草图。
  • 第二阶段:动手搭建环境,把ActiveMQ跑起来,用管理控制台看一眼队列里的消息长什么样,建立直观感受。
  • 第三阶段:写代码,先写一个点对点模式的生产者和消费者,让消息能发能收;再写一个发布订阅模式,理解两者的区别。
  • 第四阶段:进阶机制,研究事务、消息确认、持久化订阅这些可靠性相关的机制,最后带着问题去查官方文档。

这么一套走下来,基本就能在实际项目里独立使用ActiveMQ了。下面几个章节,就按这个路线把每个阶段的关键内容拆开讲清楚,代码部分我直接贴出来,你照着跑一遍就行。

2. JMS核心概念梳理:模型、消息结构和可靠性机制

2.1 两种消息模型:点对点与发布订阅

JMS规范里定义了两种消息传递模型,这是整个JMS世界的地基,就是Queue和Topic。

Queue是点对点模型,也叫P2P模型。生产者把消息发给一个指定的队列,消息队列会保存这些消息,直到某个消费者把它取走。一条消息只能被一个消费者消费,消费完就没有了,不存在多个消费者同时收到同一条消息的情况。这就像去银行柜台办业务,你取一个号,排队到某个窗口,只有这个窗口的员工会处理你的业务,别的窗口不会重复办理。如果暂时没有消费者在线,消息也不会丢,它会安静地待在队列里,等消费者上线后取走。这个特性在做任务分发、异步处理时特别实用。

Topic是发布订阅模型,也叫Pub/Sub模型。生产者把消息发到一个主题上,所有订阅了这个主题的消费者都能收到消息。这就像公众号发文,你关注了某个公众号,作者一发文章,所有粉丝的订阅列表里都会有这条消息。不过有个关键区别:Topic消息默认不持久化,如果消费者当时不在线,等它上线后是收不到之前错过的消息的。要让消费者能收到离线期间的消息,就得用持久化订阅,这个后文专门讲。

初学者最容易混淆的地方就是这两个模型的区别。记住一句话:Queue是一条消息只给一个消费者,大家抢着消费,消息被抢走就没了;Topic是一条消息广播给所有订阅者,每个订阅者都能拿到一份拷贝。把这个逻辑刻在脑子里,很多后面的事情就顺了。

2.2 一条消息的完整结构:消息头、属性和消息体

JMS里一条消息不是光秃秃的一串文字,它有固定的三部分结构:消息头、属性和消息体。

消息头是一堆系统自动设置的字段,JMS规范里定义了很多标准字段,其中最重要的几个是:JMSDestination表示消息发往的目的地,是Queue还是Topic;JMSDeliveryMode表示消息是持久化还是非持久化;JMSExpiration表示消息过期时间;JMSPriority表示消息优先级;还有JMSCorrelationID,可以用来关联多条消息,比如把请求消息和响应消息关联起来。实际操作中,这些字段大部分是消息中间件自动填充的,但有些你也可以手动设置,比如过期时间。

属性部分可以理解为消息的自定义标签,分为三类:应用属性、JMS定义的属性和消息中间件厂商的特定属性。用得最多的是应用属性,你可以设置一些键值对,比如消息来源、消息类型、业务ID等。属性的一个重要作用是支持消息筛选,消费者可以设置消息选择器,只消费满足特定条件的消息。

消息体是消息的实际内容,JMS定义了五种类型的消息:TextMessage承载纯文本,最常用;MapMessage承载一组键值对;ObjectMessage承载序列化后的Java对象;BytesMessage承载二进制数据;StreamMessage承载原始数据流。第一次用的时候直接选TextMessage就行,简单直接,后面有需求再研究其他类型。我见过有人非要用ObjectMessage,结果对象没实现序列化接口,消费者反序列化直接报错,绕了一大圈——能用TextMessage解决的事情,不要附加多余复杂度。

2.3 可靠性三板斧:持久化、确认机制和事务

消息中间件存在的价值,就是帮你把消息可靠地送到消费者手里。JMS提供了三个核心的可靠性保证,分别是持久化、消息确认和事务。

持久化对应的是JMSDeliveryMode字段。非持久化消息的性能更好,因为消息存在内存里,但Broker一重启消息就没影了;持久化消息会先落盘再确认,Broker重启后消息还能恢复。如果消息很重要,比如订单、支付回调,一定要设置成持久化。ActiveMQ默认的消息发送模式就是持久化,这点对新手倒是挺友好。不过持久化也有成本,ActiveMQ默认通过KahaDB存储,磁盘IO上会有一些开销,如果每条消息都是持久化且量又特别大,磁盘性能就成了瓶颈,这个我在后面性能调优部分还会再讲。

消息确认机制是消费者端的可靠性保证。消费者从Broker取走消息后,需要告诉Broker“这条消息我处理好了”,这样Broker才敢把消息删除。如果消费者取走消息后还没确认就挂了,Broker会在消费者重新连接后再次投递这条消息,这样就保证了消息至少被消费一次。JMS定义了四种确认模式:AUTO_ACKNOWLEDGE自动确认、CLIENT_ACKNOWLEDGE手动确认、DUPS_OK_ACKNOWLEDGE允许重复确认、SESSION_TRANSACTED配合事务使用。默认的自动确认看似省事,但如果你在消费者里处理消息需要调外部接口,万一消息取回来后,还没处理完就自动确认了,处理逻辑却失败了,这条消息就丢了。这种情况就必须手动确认,确保处理成功之后再调用acknowledge方法。

事务是更进一步的保证,它可以让一批消息的发送或接收要么全部成功要么全部失败。你在Session上开启事务后,发送消息不会立即生效,要调用session.commit()才会真正提交;处理过程中发现异常就调用session.rollback(),这批消息会被回滚。把事务和确认机制配合好,基本就能在业务上做到不丢消息。

3. 环境搭建与两种模式的完整实操

3.1 五分钟跑起ActiveMQ:安装、启动和控制台验证

ActiveMQ的部署算是消息中间件里最简单的了。去Apache官网下载对应操作系统的压缩包,我这边以某次用的ActiveMQ 5.15版本为例。解压之后,目录里有个bin文件夹,Windows环境下直接双击activemq.bat,Linux或者macOS环境就在终端执行activemq start。这里提醒一句:如果用的是macOS,解压到/Download这种路径可能会报权限问题,把它挪到用户目录下再启动就行。

启动成功之后,有两个地址要记一下。一个是Broker的服务地址,默认是:61616。后面代码里创建连接工厂要填这个地址,在这里设置用户名密码的时候注意别搞混端口。另一个是管理控制台,默认是:8161,打开浏览器访问http://127.0.0.1:8161,默认账号密码是admin/admin。控制台里能看到当前有几个Queue、几个Topic,消息有没有堆积,消费者是不是在线,对初学者来说是最直观的验证工具。我第一次启动的时候,一直盯着终端等输出,以为要看到什么特殊日志才叫启动成功,其实只要没报错,控制台能打开,Broker就是在正常工作了。

3.2 点对点模式实践:一个Hello World级别的生产者消费者

接下来是动手环节。先建一个普通的Java项目,把ActiveMQ的客户端jar包引入进来。如果你用的是Maven,在pom.xml里加依赖就行,activeMQ-client这个jar包就用5.15版本和你装的Broker保持一致就好,版本不一致有时候会出现兼容问题。

先看生产者代码:

import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class QueueProducer { public static void main(String[] args) throws Exception { // 1. 创建连接工厂,指定Broker地址 ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory( "tcp://127.0.0.1:61616"); // 2. 创建连接并启动 Connection connection = factory.createConnection(); connection.start(); // 3. 创建会话,第一个参数false表示不开启事务 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); // 4. 创建目标队列 Queue queue = session.createQueue("demo.queue"); // 5. 创建生产者并发送消息,这里把持久化模式设为可配置 MessageProducer producer = session.createProducer(queue); producer.setDeliveryMode(DeliveryMode.PERSISTENT); for (int i = 0; i < 5; i++) { TextMessage message = session.createTextMessage("Hello JMS " + i); producer.send(message); } System.out.println("消息发送完成"); // 6. 关闭资源 producer.close(); session.close(); connection.close(); } }

再写消费者:

import org.apache.activemq.ActiveMQConnectionFactory; import javax.jms.*; public class QueueConsumer { public static void main(String[] args) throws Exception { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory( "tcp://127.0.0.1:61616"); Connection connection = factory.createConnection(); connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Queue queue = session.createQueue("demo.queue"); MessageConsumer consumer = session.createConsumer(queue); // 同步接收消息,设置3秒超时,超过时间返回null while (true) { Message message = consumer.receive(3000); if (message == null) { break; } if (message instanceof TextMessage) { TextMessage textMessage = (TextMessage) message; System.out.println("收到消息:" + textMessage.getText()); } } consumer.close(); session.close(); connection.close(); } }

先运行消费者再运行生产者,控制台就能看到五条消息被依次消费。这里有个新手容易忽略的细节:connection.start()这一步不能省,没有启动连接,消费者虽然创建成功了,但根本不会接收到任何消息。而且实际项目中,连接和会话的创建是有开销的,不要每条消息都新建连接,应该复用长连接。同步receive的方式虽然在Demo里看着直观,但真实项目里更推荐用MessageListener做异步监听,代码后面会给。

3.3 发布订阅模式实践:从群发消息到持久化订阅

发布订阅模式和点对点的代码结构几乎一样,差别只在两处:一是目的地用Topic而不是Queue,二是消费者的行为不同。先看一段发布者的代码:

Topic topic = session.createTopic("demo.topic"); MessageProducer producer = session.createProducer(topic); for (int i = 0; i < 5; i++) { TextMessage message = session.createTextMessage("Topic消息 " + i); producer.send(message); }

消费者的创建,普通订阅这样写:

Topic topic = session.createTopic("demo.topic"); MessageConsumer consumer = session.createConsumer(topic);

这时候你如果直接运行,会发现一个现象:先启动消费者、再运行发布者,消费者能收到五条消息。但如果你先发消息,再启动消费者,消费者什么都收不到。这就是我前面说的Topic消息默认不持久化,消息发出去没有订阅者接收,就直接失效了。很多新手在这里卡很久,以为自己代码写错了,其实这是发布订阅的默认行为。

要想让离线消费者能收到错过的消息,就要用持久化订阅。JMS里创建持久化订阅要调用Session的createDurableSubscriber方法,传两个参数:Topic和订阅ID。同时,订阅者的ClientID必须在连接层面先指定,否则ActiveMQ会报错。示例代码如下:

// 必须指定ClientID,同一个订阅ID的ClientID要保持一致 connection.setClientID("consumer-001"); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Topic topic = session.createTopic("demo.topic"); // 第二个参数是订阅名,Broker用它在持久化存储里标记这条订阅 MessageConsumer consumer = session.createDurableSubscriber(topic, "subscriber-001");

用了持久化订阅后,即使消费者下线,Broker也会把主题消息保存下来,等消费者重新上线,消息会按照订阅关系重新投递。生产环境里,广播通知、配置变更推送这类场景,千万别忘了用持久化订阅,否则消费者一重启,关键消息就全错过了。我自己就吃过这个亏:有一次做配置推送,消费者半夜发布重启,正好错过了重启期间的配置变更消息,第二天线上配置不对,排查了好久才回过味来。

4. 进阶实践:事务、连接复用和代码细节

4.1 Session事务模式:一批消息要么全成功要么全失败

在实际业务里,我们经常需要一批消息一起被消费并完成数据库操作,如果中间某条消息处理失败,希望前面处理过的也回滚。JMS事务正是为这种场景设计的。

开启事务的Session是这样创建的:

Session session = connection.createSession(true, Session.SESSION_TRANSACTED);

注意,第一个参数传true的时候,第二个参数就被忽略,规范要求固定传Session.SESSION_TRANSACTED。在这个Session下发送消息,消息不会立即到达Broker,而是缓存在本地,必须调用session.commit()之后才会真正发送。消费者也是一样,从Broker取消息后,消息会暂存在本地,处理成功就commit,处理过程中发现异常就rollback,消息会被重新投递。

我举个实际例子。有一个订单状态同步服务,消费者从队列里拿到订单消息,需要更新本地数据库,然后给上游系统回写一个确认状态。如果只做数据库更新,不开启事务,消息处理到一半挂了,数据库更新成功但回写失败,这条消息就被确认掉了,后续无法重试。遇到这种要么全做要么全不做的场景,就应该开启事务。但要注意,事务和数据库操作要放在同一个事务边界里比较难协调,因为JMS事务和数据库事务是两套体系。经验做法是:消息的commit和数据库事务要么同时成功要么协调回滚,可以用一个统一的事务管理器去管理,比如Spring的JmsTransactionManager配合DataSourceTransactionManager做分布式事务协调。这块有点深,入门阶段理解“事务可以保证消息批量可靠处理”这个点就够了。

4.2 消息确认时机:自动确认和手动确认该怎么选

前面提到AUTO_ACKNOWLEDGE很方便,但某些场景下它是陷阱。我详细说一下它的工作方式:消费者调用receive方法拿到消息,只要消息一交给应用程序,Session就立刻向Broker确认,压根不管后面的业务处理代码有没有执行完。如果你拿到消息之后调了一个远程接口,结果远程接口超时,本地处理抛出异常——来不及了,消息已经被确认了,Broker不会重新投递。换到CLIENT_ACKNOWLEDGE模式,则是你拿到消息后,只有自己主动调用message.acknowledge(),Broker才会收到确认。

所以我的建议是:如果消费者只是把消息简单打印或者写入本地日志,自动确认就够了;如果消息处理涉及业务逻辑,比如调接口、写数据库、做计算,一律优先考虑手动确认,而且要在所有业务处理成功之后再确认。手动确认的写法是:

Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); MessageConsumer consumer = session.createConsumer(queue); consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { try { // 处理业务逻辑:写库、调接口、写文件 process(message); // 所有逻辑处理成功后再确认 message.acknowledge(); } catch (Exception e) { // 处理失败,不确认,Broker会重新投递 log.error("消息处理失败,等待重试", e); } } });

这里有个细节:在CLIENT_ACKNOWLEDGE模式下,调用任一消息的acknowledge方法,会把当前Session里所有未确认的消息都一起确认掉。也就是说,如果一个Session里同时消费了10条消息,你只想确认其中1条,但实际上10条都会被确认。这是JMS规范里定义的行为,很多人刚开始不知道,就会产生消息丢失的误解。如果确实需要对每条消息单独确认,就必须一个消息一个Session,或者改用ActiveMQ扩展的个别确认机制。

4.3 资源关闭顺序和连接工厂的细节

JMS资源有明确的层级关系:ConnectionFactory创建Connection,Connection创建Session,Session创建Producer和Consumer。关闭资源的时候顺序要反过来,先关Producer和Consumer,再关Session,最后关Connection。之前遇到有人只关Connection不关其他资源,这种情况下大多数连接池还算能容忍,但在一些版本里会出现Broker端连接未释放的告警。

连接工厂还有一个很实用的坑要提醒:ActiveMQ的ConnectionFactory默认创建的是普通连接,不支持自动重连。如果Broker在运行过程中重启了,连接就会断开,应用程序需要手动重新创建连接。对生产环境来说这很难接受,所以一定要用ActiveMQ提供的failover协议,连接地址写成这样:

ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory( "failover:(tcp://127.0.0.1:61616)?initialReconnectDelay=100&maxReconnectAttempts=10");

failover协议会自动在连接断开后重新连接,还能配置重连间隔和重试次数。我的一个项目里用普通连接,凌晨Broker做一次滚动重启,结果一堆消费者全部失联,第二天排查才发现是连接没有自动恢复机制。从那以后,我写的所有ActiveMQ连接都统一用failover协议,这个习惯算是踩坑踩出来的。

5. 常见问题与排查经验实录

5.1 消费者收不到消息:优先排查这四个方面

消费者收不到消息是最常见的故障,我总结了一套排查顺序,照着做基本能定位。

第一,先确认Broker和消费者之间网络是不是通的。用telnet命令测一下61616端口通不通,如果端口不通,防火墙、安全组都是嫌疑对象,特别要注意云服务器上还要看安全组规则,有时候本地端口放行了,远程IP没放行,一样连不上。

第二,去管理控制台看消息的状态。打开8161端口的管理页面,找到对应的Queue,看Enqueue count和Dequeue count。如果Enqueue是0,说明生产者根本没把消息发进来,问题在生产端;如果Enqueue有值但Dequeue一直不涨,说明消息进了队列但消费者没取走,问题在消费端;如果两者都在涨,消息就没丢,只是处理速度慢。

第三,看消费者连接是否建立。控制台里能看到有哪些消费者连接了Broker,如果消费者列表是空的,说明消费端的代码根本没跑到创建消费者的那一步,或者连接被阻塞了。特别要检查connection.start()有没有被遗忘,这个错误太常见了。

第四,确认消息目的地是否一致。生产者创建的Queue名称和消费者创建的Queue名称必须完全一致,多一个空格、大小写不同都收不到。Topic也是一样,还要确认订阅ID是否是持久化订阅。我还遇到过一种情况:消息设置了过期时间,消费者处理太慢,消息还没被消费就过期了,控制台里Expired count会显示过期数量,这时候就该调大过期时间或者看看消费者为什么这么慢。

5.2 消息重复消费和消息丢失,如何尽量规避

消息重复消费几乎是所有消息系统都绕不开的问题。原因在于:消费者处理消息的过程中挂了,Broker没收到确认,等消费者恢复后会重新投递这条消息,而之前那条消息可能已经被处理了一部分。所以消息中间件保证的往往是“至少一次投递”,而不是“恰好一次”。要想让业务不重复,通常需要在消费端做幂等处理:通过唯一业务ID查一下数据库,已经处理过就直接忽略;或者在消息处理前加一个去重表,用唯一索引防重复插入。

比重复更可怕的是消息丢失。消息丢失的高发点有三个:一是生产者端发送非持久化消息,Broker重启消息就没了,重要消息必须设置PERSISTENT;二是消费者在处理业务逻辑之前就自动确认了消息,后面处理挂了消息也回不来,这时就要改成手动确认;三是用了非持久化的Topic普通订阅,消费者不在线消息就丢了,要改成持久化订阅。把这三个点守住,消息丢失的概率能大幅下降。

JMS还有一个重投递机制要知道:消费者调用session.rollback()或者不确认消息,Broker会重新投递,但会有一个Redelivery策略,默认会限制最大重投次数。重投次数耗尽之后,消息会被放进死信队列。死信队列这个概念在生产环境里特别重要,它相当于一个消息的“收容所”,把多次处理失败的消息收集起来,方便人工介入排查。ActiveMQ默认的死信队列名称是ActiveMQ.DLQ,所有超限消息都会汇集到那里。

5.3 生产调优的几个实用参数

ActiveMQ的调优是很大的话题,新手阶段掌握几个关键参数就够用了。首先是生产者端,可以开启异步发送。ActiveMQ里,可以在连接地址上加上useAsyncSend=true参数,让生产者不等待Broker的确认回执,消息发送吞吐量会明显提升,代价是一旦Broker挂了,可能有少量消息未真正落盘就丢了。所以异步发送适合允许容忍极小概率消息丢失、但对吞吐量要求高的场景。我对这个参数的态度是:非核心链路可以开,核心链路必须考虑清楚。

消费者端要注意prefetch的语义。prefetch表示一次拉取多少条消息到本地缓存,默认值是1000。如果消费者处理每条消息比较慢,prefetch太大会导致Broker把一堆消息发给消费者后,消费者还没处理完就断开,这些消息需要重新投递,造成重复。可以把prefetch设为1,让消费者一条条拉取,这样能保证每条消息都处理完再取下一条,损失一些吞吐量但换来回稳定。设置prefetch的方式也是在连接地址上加参数:tcp://127.0.0.1:61616?jms.prefetchPolicy.queuePrefetch=1。

最后提醒一个容易忽略的点:消息堆积不要只盯着消费者提升单机性能,要看消费者的并发能力。如果Queue里的消息越堆越多,单纯调大prefetch是治标不治本,要检查是不是消费者数量不够。JMS规范里支持多个消费者竞争同一个Queue,所以Java应用里可以开启多线程创建多个消费者,或者部署多个实例,让消息被并行消费。我在一个数据同步任务里,刚开始单消费者每秒只能处理几百条,后来开了4个消费者并行消费,吞吐量直接翻了好几倍。记住,消息队列本来就是用来并行的,别把单个消费者当成唯一处理线程。

5.4 给后来者的一份学习避坑清单

最后按习惯总结几条干过的蠢事,给准备学ActiveMQ和JMS的朋友抄作业:

  • 第一,概念没理清之前不要碰代码。先花半天时间把Queue、Topic、持久化、ACK这四件事想明白,后面写代码会顺很多。
  • 第二,环境变量和端口别凭感觉配置。ActiveMQ的61616和8161端口要背下来,Broker地址写错是新手报错的第一大来源。
  • 第三,写Demo的时候一定要打开管理控制台配合验证。发了消息看不到队列里有,光盯着代码看是看不出所以然的。
  • 第四,所有代码里的连接地址一律用failover协议,不要给自己挖自动断线后不重连的坑。
  • 第五,官方文档值得读。ActiveMQ官网有完整的JMS教程和API说明,遇到问题优先查官方资料,比搜索到的一堆互相矛盾的博客可靠。

我在实际项目中还有一个习惯:写JMS相关代码时,把消息的生产、发送、消费日志都打上MessageID和业务ID串联起来,排查问题的时候能顺着日志链路快速定位消息到底卡在哪一环。生产环境消息量大的时候,没有日志链路排查消息问题,就像在黑夜里找针,这个习惯帮我省了无数排查时间。

最后再分享一个小技巧:ActiveMQ其实有个开箱即用的Java客户端命令行工具,在bin目录下可以执行activemq browse来查看队列的消息内容,配合activemq query能查询Broker状态。有时候代码还没写,用命令行就能验证队列里消息来没来、长什么样,对初学者来说是很直观的辅助工具。多花一点时间熟悉这些内置命令,比反复重启应用去猜状态高效太多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 17:06:13

RAG知识库文档处理实操:解析、切分与检索效果全面调优

“收藏备用&#xff01;RAG知识库文档处理实操”&#xff0c;这个标题我第一眼看到就想点头。做RAG知识库的人&#xff0c;谁没被文档处理折磨过&#xff1f;模型选型、Prompt优化这些话题都是热热闹闹的技术高地&#xff0c;结果真正把项目拖死的&#xff0c;往往是最朴素的一…

作者头像 李华
网站建设 2026/10/19 17:01:43

开放式Code Review流程搭建实践:从规则设计到自动化落地

从零搭建一套开放式的Code Review流程&#xff0c;我踩过的坑和沉淀下来的方案这几年带过不少团队&#xff0c;也见过很多种代码审查的做法。有的团队用GitHub的Pull Request天然流程&#xff0c;有的用Gerrit那套严格的merge前审查&#xff0c;还有的直接靠leader一个人在群里…

作者头像 李华
网站建设 2026/10/19 16:55:22

数据清洗实战:Pandas处理30万行脏订单数据的完整记录

大概从第9天开始&#xff0c;我每天都会给自己安排一个"接近真实"的小任务&#xff1a;第9天是抓取静态页面&#xff0c;第10天是写一个能轮询的监控脚本&#xff0c;第11天处理了Excel合并&#xff0c;第12天自己画了十几张图表。到了第13天&#xff0c;我决定不再用…

作者头像 李华
网站建设 2026/10/19 16:50:48

AI文生视频实战:原理、提示词写法、翻车排查与场景边界

上个月我把一句话丢进AI视频生成工具——那句话大概是“一只银色的机械狐狸在废弃火车站的月台上抖落身上的雪”。当时我盯着生成进度条&#xff0c;心态其实很平淡&#xff1a;能出个能当动态壁纸的东西就行。结果第一版渲染出来&#xff0c;狐狸尾巴上每一绺毛都带着风雪飘散…

作者头像 李华
网站建设 2026/10/19 16:50:44

BTP Event Mesh

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/19 16:49:37

端侧多模态实时对话系统开发实战:9B模型的流式架构与性能调优

上个月我在做“某端侧多模态实时对话 Demo”的性能优化&#xff0c;项目代号模拟项目X&#xff0c;核心是把一个 9B 参数的视觉语言模型塞进手机&#xff0c;实现“摄像头看得到、麦克风听得见、离线也能聊”的交互体验。第一版跑通时大家都很兴奋&#xff0c;但真到演示环节就…

作者头像 李华