消息中间件作为我们日常开发中必不可少一个组件,所以我们要理解消息从生产者发送到消息对列,以及消费者监听消息对列从对列中拿到消息进行消费的一系列流程,我针对这些流程写了些关于Rabbitmq的配置,以及生产者发送消息,消费者消费消息的一些代码来说明中间的流程。
RabbitMQConfig
下面是我们对RabbitMQ一些配置的代码,包括与mq建立连接,定义交换机,绑定交换机与对列的关系,消息监听工厂的配置,这里我将我的配置信息以yaml的文件形式都写在了nacos配置中心里面。
package com.erp.common.mq.config;
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.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.erp.common.mq.constants.RabbitMqConstants;
@Component
@Configuration
public class RabbitMQConfig {
@Value("${Spring.rabbitmq.host}")
public String host ;
@Value("${Spring.rabbitmq.port}")
private int port;
@Value("${Spring.rabbitmq.userName}")
private String userName;
@Value("${Spring.rabbitmq.password}")
private String password;
@Value("${Spring.rabbitmq.virtualHost}")
private String virtualHost;
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
try {
connectionFactory.setHost(host);
connectionFactory.setPort(port);
connectionFactory.setUsername(userName);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(virtualHost);
}catch (Exception e){
return null;
}
return connectionFactory;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
return template;
}
/**
* 针对消费者配置
* 1. 设置交换机类型
* 2. 将队列绑定到交换机
*
*
FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念
HeadersExchange :通过添加属性key-value匹配
DirectExchange:按照routingkey分发到指定队列
TopicExchange:多关键字匹配
*/
@Bean
public DirectExchange defaultExchange() {
return new DirectExchange(RabbitMqConstants.OREDER_MESSAGE_EXCHANGE);
}
@Bean
public Queue queue() {
return new Queue(RabbitMqConstants.OREDER_QUEUE, true); //队列持久
}
@Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(defaultExchange()).with(RabbitMqConstants.OREDER_ROUTINGKEY);
}
}
RabbitMQSender
下面是基于RabbitTemplate封装了一层生产者类,来用于消息的发送,同时我在数据库建了一张存放Mq数据的表,方便我们用来查看消息的发送消费情况,我这里开启了生产者Confirm机制来保证生产者消息的准确投递,但是这种机制比较耗性能,在发送消息量比较大的情况下还是慎用。
package com.erp.common.mq.send;
import java.util.UUID;
import org.apache.log4j.Logger;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.erp.common.core.utils.bean.BeanUtils;
import com.erp.common.mq.request.SysMqLogRequestDto;
import com.erp.system.api.RemoteLogService;
import com.erp.system.api.domain.SysMqLog;
/**
* @Author lixianglong
* @create 2022/7/17 上午11:36
*/
@Component
public class RabbitMQSender implements RabbitTemplate.ConfirmCallback{
/** logger */
private static Logger logger = Logger.getLogger(RabbitMQSender.class);
private RabbitTemplate rabbitTemplate;
@Autowired(required=true)
private RemoteLogService remoteLogService;
public RabbitMQSender(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void send(String content,String exchange,String routingKey,String queue){
logger.info("MQ发送消息开始 发送内容:"+ content);
CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
logger.warn("Rabbit MQ队列 发送消息 content:" + content +" 回调ID:" + correlationId.getId());
rabbitTemplate.convertAndSend(exchange,routingKey,content,correlationId);
SysMqLog sysMqLog =new SysMqLog();
sysMqLog.setContent(content);
sysMqLog.setExchange(exchange);
sysMqLog.setCorrelationId(correlationId.getId());
sysMqLog.setQueueName(queue);
sysMqLog.setSendStatus(0);
sysMqLog.setOpStatus(0);
remoteLogService.saveMqLog(sysMqLog);
logger.info("发送消息结束");
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
logger.info("回调id::"+ correlationData);
if (ack) {
logger.info("消息成功消费");
if(correlationData != null && !"".equals(correlationData)){
SysMqLog sysMqLog =new SysMqLog();
sysMqLog.setCorrelationId(correlationData.getId());
remoteLogService.updateMqLog(sysMqLog);
}
} else {
logger.error("Rabbit MQ队列 推送消息失败 回调ID:" + correlationData + " 原因:"+ cause);
}
}
}
这里我们通过请求发送一条消息到队列上。
@GetMapping("createOrder")
public String createOrder() {
String content = "订单服务创建的第一条订单";
rabbitMQSender.send(content, RabbitMqConstants.OREDER_MESSAGE_EXCHANGE, RabbitMqConstants.OREDER_ROUTINGKEY,
RabbitMqConstants.OREDER_QUEUE);
return "消费发送成功";
}
GET http://localhost:8082/order/createOrder
启动项目自动与Rabbitmq建立连接
分别为我们创建的exchange、queue
这里能看到我们一个交换机和对列的绑定关系,以及队列中的消息
RabbitMQConsumer
下面我们创建另外一个消费者服务,并且写MessageOrderAckListenerConfig消费者监听配置,监听对列的消息进行进行消费,这里我设置手动ack。
@Configuration
public class MessageOrderAckListenerConfig {
private Logger logger = LoggerFactory.getLogger(MessageOrderAckListenerConfig.class);
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
// RabbitMQ默认是自动确认,这里改为手动确认消息
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
// 设置需要手动确认消息的队列,可以同时设置多个,前提是队列需要提前创建好
container.setQueueNames("order.message.queue");
// 设置监听消息的方法,匿名内部类方式
container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
// 开始消费消息
logger.info("body:\n{}", JSONUtil.toJsonPrettyStr(new String(message.getBody())));
logger.info("prop:\n{}", JSONUtil.toJsonPrettyStr(message.getMessageProperties()));
// 手动确认
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag, false); // 肯定确认
});
return container;
}
}
启动消费者服务,我们从日志开可以看到消息体body和header里面的信息,并且通过手动ack确认消息,rabbitmq得到ack通知会将对列里面的消息删除。
队列里面消息为0
总结
简单的从Rabbitmq生产者发送消息,然后消费者监听消息,以及生产者的confirm机制,消费者手动ack的这几个方面对rabbitmq进行简单的学习。