一,应用场景:

1,异步:不要求实时结果或者交易耗时长只能选择异步返回结果;

2,解耦:生产者不需要强依赖消费者。比如E动访问核心系统出单,需要阻塞等待核心系统返回投保结果才能进行后续操作,核心系统异常会导致E动报错。核心要推送投保单状态给E动,如果采用同步接口,则E动服务异常会导致投保单状态无法正常发送过去,并且核心会接收到异常。所以想一些实时性不高的如投保单状态同步、费用状态同步选择通过MQ来实现解耦。

3,削峰:如果同时有高并发请求同时访问某个系统,可能导致该系统服务崩溃,使用MQ作为一个缓冲,能起到削峰作用,保护后端服务正常。

二,队列模式、主题模式:

队列模式,就是我们的消息消息生产者(Producer)向一个目的地(Desitinate)存放我们的消息,如果是多个消费者我们可以它们会分别消费这些消息,每个消息只能供一个消费者使用。

主题模式,消息发布者(生产者)向目的放发送消息后,多个订阅者(消费者)可以同时接收到该消息。可以用微信公众号来类比记忆,所有订阅某公众号的微信用户都能接收到微信公众号推送的消息。

主题模式还有一个持久订阅者的概念,因为队列模式在消息的消费者服务挂掉时,会把消息保留在MQ服务中(一般使用持久化消息,该消息即使MQ重启也不会丢失),而主题模式如果不使用持久订阅,则在订阅者服务挂掉期间,

消息发布者发布的消息无法正常推送给订阅者。此种问题的解决方法就是使用持久订阅者的方式,在使用持久订阅后,MQ本身会根据持久订阅者的clientId保留该订阅者服务挂掉期间新发布的消息,该clientId的订阅者上线后,消息重新推送给该订阅者。也可以用微信公众号来类比记忆,我们手机断网期间无法收到的公众号消息,会在我们手机联网后接收到断网期间消息发布者发布的消息。

三,代码demo(demo中是原生代码,实际开发过程中一般与Spring集成)

队列消息生产者代码demo:

QueueProducer.java

消息消息消费者代码demo:

QueueConsumer.java

四,几个常见问题:

1,消息堆积。检查消费者服务是否在线,可通过MQ控制台查看consumer数量。如果消费者在线,需要查看消费者消息处理是否正常。如果正常,需要考虑消息发送速度和消息消费速度的平衡,是否应当新增消费者节点。

2,多消费节点获取到消息数量差距很大,导致有的节点很忙有点节点很闲无法发挥并行优势。需要查看控制台界面的prefetch数量,极端情况下,消费数量很少,但是单个消息处理很慢,那么可以调整prefetch数量为1,以保证多个消费节点都能拿到消息消费。如果消息数量很大,并且单个消息处理很快,可以使用默认值1000。

3,死信消息处理。默认MQ所有死信消息会发送到同一个DLQ队列,不方便定位具体问题,可以考虑给每个容易出现死信消息的队列配置自己的死信消息队列,以区分处理。

4,丢消息问题。生产者使用事务的session,保证消息发送成功。消费者使用client_acknowledge,保证消息处理完成后再发送ack给MQ的broker。消息使用持久化机制(磁盘或者数据库,数据库也是磁盘持久化),保证消息暂存于MQ时,不会因为MQ服务故障内存易失性问题导致消息丢失。

5,为保证MQ服务的高可用、高性能。需要综合MQ集群和主备的特性,不仅有通过共享文件实现的主备,还有使用network connector实现的多节点集群。

6,根据实际场景选择P2P的队列模式或者一对多的主题模式。主题模式类似微信公众号,可以一个消费分发给多个订阅者,不过一般需要设置为持久订阅者,以保证订阅者故障恢复时仍然能接受到故障期间发布的新消息。

7,ActiveMQ消息中间件本身目前没有人维护,开发团队已经转移工作重心到新的产品上。可以考虑使用社区更为活跃的RabbitMQ、RocketMQ、Kafka。目前核心系统在引入kafka,一个更适合分布式、大数据量的消息中间件系统,kafka可以实现分布式、多分区、冗余备份、发布订阅模式,kafka本身概念较多,并且依赖Zookeeper,还有就是kafka是用scale语言(也是运行于JVM上的语言)编写,这些都对开发运维人员有更高的要求。
后面的是生产使用过程中遇到的问题

8,
某系统测试服务错误订阅了线上MQ服务上的主题topic,并且设置的是持久订阅者。该系统已把该测试服务关闭了,但是MQ本身会维护持久订阅者,默认情况下持久订阅者下线后MQ还会维护着该订阅者,等该订阅者上线后下线期间的消息还能收到。
这样就导致类似误订阅的订阅者服务真的不用之后,MQ本身还维护着该订阅者,有新消息发布到主题上后,会在内存中维护一份放在MQ的JVM内存中,消息增多可能导致MQ的内存吃紧,过多可能导致MQ服务OOM。
为了解决该问题,可以配置一个下线持久订阅者超时时间offlineDurableSubscriberTimeout=“1800”。不过这样就有个问题时,如果正确的订阅者服务突然宕机超过设置的超时时间,那重新启动服务后宕机期间的消息就丢失了,所以该配置只能临时用来解决误订阅的持久订阅者,等误订阅的持久订阅者从MQ中清理后,再去掉该配置。

9,

MQ的每个队列消费者、主题订阅者服务都是MQ的一个连接connection,需要有一个唯一标识来区分(MQ服务级别),可通过控制台查看消费者的Connection ID。如果多个相同ID的监听连接同一个MQ服务,则会报错:

javax.jms.InvalidClientIDException: Broker: south-uat - Client: sun-consumer already connected from tcp://IP:PORT