springboot整合activemq queue topic消息类型 及消息的手动签收、持久化 — 1

前言

项目由于业务需求需要使用消息中间件,需要支持queuetopic消息类型,持久化消息并在消费后手动签收

安装

本案例activemq安装在docker

#查找镜像
docker search activemq

#拉取镜像
docker pull webcenter/activemq

#运行容器
docker run -d --name activemq -p 61616:61616 -p 8161:8161 webcenter/activemq

访问宿主机ip:8161/admin可以查看到管理页面
用户名\密码:admin\admin
管理界面

ActiveMQ

activemq是一个JMSProvider,即实现了JMS规范的消息中间件,因为本身用java开发,所以与spring天然契合。
业务需要处理的消息需要持久化,activemq支持自带的kahadb持久化,也支持自定义持久层比如mysql,我们不追求对消息的高度可视,因此追求性能决定使用内置kahadb
activemq的消息传输分为两种模式

  1. p2p,点对点,即一个消息生产者对应一个消费者
  2. 发布/订阅模式,即允许多个消费者订阅同一个生产者的消息,当发布消息时订阅者不在线,还可以支持消息的持久化

JMS规范

引用https://www.jianshu.com/p/ecdc6eab554c
该文对JMS术语的解释可以让我们更清晰地了解整合时相关的一些配置

Provider/MessageProvider:生产者

Consumer/MessageConsumer:消费者

PTP:Point To Point,点对点通信消息模型

Pub/Sub:Publish/Subscribe,发布订阅消息模型

Queue:队列,目标类型之一,和PTP结合

Topic:主题,目标类型之一,和Pub/Sub结合

ConnectionFactory:连接工厂,JMS用它创建连接

Connnection:JMS Client到JMS Provider的连接

Destination:消息目的地,由Session创建

Session:会话,由Connection创建,实质上就是发送、接受消息的一个线程,因此生产者、消费者都是Session创建的

工程结构

工程结构
activemq-provider:消息生产者
activemq-consumer:消息消费者

mq相关依赖

<!--ActiveMq-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

 <!--消息队列连接池-->
<dependency>
	<groupId>org.apache.activemq</groupId>
	<artifactId>activemq-pool</artifactId>
	<version>5.15.0</version>
</dependency>

配置

前言中提到不同的多种需求,比如对消息的点对点收发,对主题消息持久化订阅,对消息读取后的手动签收等等,因此默认的配置无法满足我们的需求,需要对配置进行进一步了解与配置

Destination

activemq-provider中我们针对点对点主题订阅注册两个默认的Destination Bean,如下

@Configuration
@EnableJms
public class ActiveMQConfig {

    /**
     * 默认queue
     * @return
     */
    @Bean
    public Queue defaultQueue() {

        return new ActiveMQQueue("default") ;
    }

    /**
     * 默认topic
     * @return
     */
    @Bean
    public Topic defaultTopic() {

        return new ActiveMQTopic("default") ;
    }
}

发送消息controller

@RestController
@RequestMapping(value = "/activemq")
@Api(tags = "activemq整合")
public class SendController {

    @Autowired
    private SendService sendService;

    @RequestMapping(value = "/sendQueueText", method = RequestMethod.GET)
    @ApiOperation(value = "消息发送")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "message", value = "消息", required = true)
    })
    public ResultDTO sendQueueText(@RequestParam(name = "message") String message)
    {
        return sendService.sendQueueText(message);
    }
}

发送消息service实现

@Service
@Slf4j
public class SendServiceImpl implements SendService {

@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;

@Autowired
@Qualifier(value = "defaultQueue")
private Queue defaultQueue;

@Autowired
@Qualifier(value = "defaultTopic")
private Topic defaultTopic;

@Override
public ResultDTO sendQueueText(String message) {
    try {
        jmsMessagingTemplate.convertAndSend(defaultQueue, message);
    } catch (Exception e) {
        log.error(e.getMessage(), e);
        return new ResultDTO(CodeMap.STATE_CODE_THROWABLE);
    }

    return new ResultDTO(CodeMap.STATE_CODE_SUCCESS);
}

@Override
public ResultDTO sendTopicText(String message) {
    try {
        jmsMessagingTemplate.convertAndSend(defaultTopic, message);
    } catch (Exception e) {
        log.error(e.getMessage(), e);
        return new ResultDTO(CodeMap.STATE_CODE_THROWABLE);
    }

    return new ResultDTO(CodeMap.STATE_CODE_SUCCESS);
}

}
代码中的相关dto不再列出,后续给出源码地址

JmsListenerContainerFactory

同样的,activemq-consumer中我们为不同的消息模式各定义一个默认的JmsListenerContainerFactory

/**
     * topic模式的ListenerContainer
     * @param activeMQConnectionFactory
     * @return
     */
    @Bean
    public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
        DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
        bean.setPubSubDomain(true);
        bean.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
        bean.setConnectionFactory(activeMQConnectionFactory);
        return bean;
    }

    /**
     * Queue模式的ListenerContainer
     * @param activeMQConnectionFactory
     * @return
     */
    @Bean
    public JmsListenerContainerFactory<?> jmsListenerContainerQueue(ConnectionFactory activeMQConnectionFactory) {
        DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
        bean.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
        bean.setConnectionFactory(activeMQConnectionFactory);
        return bean;
    }

示例中我们定义的都是DefaultJmsListenerContainerFactory,可以看到JmsListenerContainerFactory还有一个实现类SimpleJmsListenerContainerFactory
在这里插入图片描述
它提供了线程池处理消息的方法,需要的可以具体去了解
其次,注册这两个bean时我们指定消息需要消费者手动签收,后续测试是否生效

bean.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE)

消费者service实现类

@Service
@Slf4j
public class ReciveServiceImpl implements ReciveService {

    @Override
    @JmsListener(destination = "default", containerFactory = "jmsListenerContainerQueue")
    public void reciveQueueText(ActiveMQMessage message, Session session) {
        try {
            System.out.println("收到消息" + message);
            message.acknowledge();

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

测试queue点对点模式

测试消息能否点对点收发

  • 启动activemq-provider

  • 启动activemq-consumer

  • 访问provider swagger页面发送一条消息
    测试
    发送成功

  • consumer成功接收消息
    接收消息成功

测试点对点是否持久化

  • 断开consumer

  • provider发送一条消息

  • 观察管理界面,发现消费者下线时消息并没有被消费
    消息没有被消费

  • 此时让消费者上线,启动consumer,发现消费者上线后就收到了消息
    收到消息

  • 管理页面也显示消息被消费
    消息被消费

测试手动签收是否生效

当前可以明确消息确实被签收了,但不确定是自动还是手动,我们注释掉message.acknowledge();手动签收的代码,测试消息是否确实是手动签收的
注释后启动consumer,再由生产者发送一条消息
消息还是被签收
很不幸,消息还是被签收了,说明目前并没有实现消息的手动签收,先记录该问题。接下来测试一下topic订阅消息模式

测试订阅模式

测试消息能否被多消费者订阅

  • 启动provider
  • 启动consumer1
  • 启动consumer2
  • 由生产者发送一条消息
  • 观察管理页面发现我们的一条消息确实被两个消费者接收
    消息被接受

测试topic消息是否持久化

  • 我们让其中一个消费者下线

  • 发送一条消息,发现消息只被在线的消费者消费
    在这里插入图片描述

  • 如果此时让下线的消费者再上线,是否还能收到刚才这条消息呢

  • 很遗憾,上线后的消费者并没有收到消息,说明目前的配置也不满足topic类型消息的持久化

总结

至此,我们算是完成了第一步,无论点对点还是订阅模式的消费都可以在正常情况(指双方都在线)的情况下被消费。
但是我们提到,需求远不止这么简单,首先我们要支持消息手动签收,而不是由客户端自动签收。其次,订阅的消息必须持久化,不能因为消费者的掉线(网络等原因)而永久错过这条消息。下一篇我们将继续解决这些问题!

下一篇:springboot整合activemq queue topic消息类型 及消息的手动签收、持久化 — 1

猜你喜欢

转载自blog.csdn.net/weixin_42189048/article/details/106249230
今日推荐