基础环境
RabbitMQ的安装
Springboot:2.2.5.RELEASE
示例代码:springboot-mq
RabbitMQ:3.8.3
application.yml文件配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
cache:
size: 1
# listener:
# direct:
# # 手动确认
# acknowledge-mode: auto
# simple:
# # 手动确认
# acknowledge-mode: auto
server:
servlet:
context-path: /mq
port: 8888
消息发送
消息发送前,新建了exchange[echange.key] 和 Queue[queue.name],并绑定
写个Controller测试一下,一般来说发送消息走定时或者实际业务里发起比较常见。
package com.mq.test.controller;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/test")
public class TestController {
@Autowired
RabbitTemplate rabbitTemplate;
@ResponseBody
@RequestMapping("/mq")
public String mq(){
//发送消息
rabbitTemplate.convertAndSend("exchange.key",null,"data");
return "SUCCESS";
}
}
请求:http://localhost:8888/mq/test/mq
产生了消息
消费消息
注册个消费者监听
package com.mq.test.config.mq.listener;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MQConsumeListeners {
private static Logger logger = LoggerFactory.getLogger(MQConsumeListeners.class);
@RabbitListener(queues={"queue.name"})
public void testConsume(Message message, Channel channel, String message1){
logger.info("mq consume :"+ message);
}
//testConsume 还可以写成如下形式,使用基本数据类型直接接受message的body
//@RabbitListener(queues={"queue.name"})
public void testConsume2(String data, Channel channel, String message1){
logger.info(("接受到的数据:"+data));
}
}
执行结果:
打印了接收结果
消息已被消费
队列、交换器声明及绑定
在Spring里,会根据Bean的类型,自动注册Queue、Exchange及绑定关系
package com.mq.test.config.mq;
//import com.platform.test.config.mq.listener.MQConsumeListener;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MQConfig {
@Bean
public Queue testQueue(){
return new Queue(QueueName.TEST,false);
}
@Bean
public DirectExchange testDirectExchange(){
return new DirectExchange(ExchangeName.TEST,true,false);
}
@Bean
public Binding testBinding(){
return BindingBuilder.bind(testQueue()).to(testDirectExchange()).with(RoutingKey.TEST);
}
static class QueueName{
static String TEST = "queue.test";
}
static class ExchangeName{
static String TEST = "exchange.test";
}
enum RoutingKey{
TEST("exchange.test");
private String key;
RoutingKey(String key){
this.key = key;
}
@Override
public String toString(){
return this.key;
}
}
}
发送消息试试TestController.java中新增mq2方法:
@ResponseBody
@RequestMapping("/mq2")
public String mq2(){
rabbitTemplate.convertAndSend("exchange.test","exchange.test","test data");
return "SUCCESS";
}
访问:localhost:8888/mq/test/mq2
## 关于消息丢失问题
消息持久化,解决发送成功但不落盘问题
上面注册队列时durable参数(new Queue()) 参数为false,消息在重启rabbitmq后丢失;修改为true后重新启动项目(记得提前删除已经有的队列)。
重启rabbitmq后消息不丢失
开始ack确认,解决消费失败消息丢失问题
修改yml文件,修改确认方式为手动
spring:
rabbitmq:
listener:
#如果手动声明listener类型,可在config声明时设置
direct:
# 手动确认
acknowledge-mode: manual
simple:
# 手动确认
acknowledge-mode: manual
此时访问:http://localhost:8888/mq/test/mq2,消息会被消费,但是并不会从队列中消失,且全为unacked的状态
此时需要手动ACK
修改MQConsumeListeners.java增加对queue.test队列的ack确认
@RabbitListener(queues={"queue.test"})
public void testConsume3(Message message, Channel channel, String message1){
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
logger.info("ack:"+message.getMessageProperties().getDeliveryTag());
} catch (IOException e) {
e.printStackTrace();
}
logger.info("mq consume3 :"+ message);
}
重启结果:
消息被消费且确认
无积压消息
通过实现ChannelAwareMessageListener接口实现消息消费
实现ChannelAwareMessageListener接口
package com.mq.test.config.mq.listener;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class MQConsumeListener implements ChannelAwareMessageListener {
private static Logger logger = LoggerFactory.getLogger(MQConsumeListener.class);
@Override
public void onMessage(Message message, Channel channel) throws Exception {
logger.info(message.getMessageProperties().getConsumerQueue());
logger.info(message.toString());
channel.basicQos(1);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);// deliveryTag the tag from the received,可以理解为channel的标识
//channel.basicNack(100L,false,false);
}
}
声明SimpleMessageListenerContainer,把listener绑定到队列上(MQConfig.java)
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(CachingConnectionFactory cachingConnectionFactory){
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
Runnable a = simpleMessageListenerContainer::getActiveConsumerCount;
simpleMessageListenerContainer.setQueueNames("queue.name");
simpleMessageListenerContainer.setConnectionFactory(cachingConnectionFactory);
simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
simpleMessageListenerContainer.setMessageListener(mqConsumeListener);
return simpleMessageListenerContainer;
}
总结
springboot在使用org.springframework.boot:spring-boot-starter-amqp时利用注解的便利性,使用起来很方便,当然前提是需要对MQ的工作原理有所掌握。