Rabbitmq消息中间件消息发送与消费

235 阅读3分钟

消息中间件作为我们日常开发中必不可少一个组件,所以我们要理解消息从生产者发送到消息对列,以及消费者监听消息对列从对列中拿到消息进行消费的一系列流程,我针对这些流程写了些关于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

1D17D89C-3A8C-43D5-86AE-98FBA1270100.png

启动项目自动与Rabbitmq建立连接

2A07E3AC-FDEC-435B-938E-B5D6A7DD9C30.png 分别为我们创建的exchange、queue

662CA2CA-B6DD-4A4F-8438-9C975EC89824.png

9F61BCB2-EEFC-4181-94BF-9FF545D37BD9.png

这里能看到我们一个交换机和对列的绑定关系,以及队列中的消息

image.png

image.png

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通知会将对列里面的消息删除。

EE5C91A2-980E-47E3-BBCE-C42AEB8F3C80.png

队列里面消息为0 3A9595C2-5567-48DC-998B-69FDFC1AA046.png

总结

简单的从Rabbitmq生产者发送消息,然后消费者监听消息,以及生产者的confirm机制,消费者手动ack的这几个方面对rabbitmq进行简单的学习。