Springboot整合RabbitMq

390 阅读5分钟

maven依赖

	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
            <version>1.5.2.RELEASE</version>
        </dependency>

application.yml

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual #      采用手动应答
        retry:
          enabled: true #        是否支持重试  

RabbitMq配置类

import com.yaoch.common.utils.DateUtil;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitMqConfig {

    private final static String HOST = "47.104.129.104";
    private final static Integer PORT = 5672;
    private final static String USER_NAME = "hong";
    private final static String PASSWORD = "hong";

    public final static String DIRECT_EXCHANGE = "directExchange";
    public final static String FANOUT_EXCHANGE = "fanoutExchange";
    public final static String FIRST_QUEUE = "queue1";
    public final static String SECOND_QUEUE = "queue2";
    public final static String DEAD_QUEUE = "deadQueue";
    public final static String FIRST_ROUTE = "first-route";
    public final static String DEAD_ROUTE = "dead-route";


    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
        cachingConnectionFactory.setHost(HOST);
        cachingConnectionFactory.setPort(PORT);
        cachingConnectionFactory.setUsername(USER_NAME);
        cachingConnectionFactory.setPassword(PASSWORD);
        cachingConnectionFactory.setVirtualHost("hong");
        cachingConnectionFactory.setConnectionTimeout(10000);
        cachingConnectionFactory.setCloseTimeout(10000);
        //设置消息发送成功应答
        cachingConnectionFactory.setPublisherConfirms(true);
        cachingConnectionFactory.setPublisherReturns(true);
        return cachingConnectionFactory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
        return rabbitTemplate;
    }

    @Bean
    public DirectExchange directExchange() {
        /**
         * 参数1:交换机名称
         * 参数2:是否持久化
         * 参数3:是否自动删除
         */
        return new DirectExchange(DIRECT_EXCHANGE,true,false);
    }

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(FANOUT_EXCHANGE);
    }

    /**
     * 普通队列
     * 声明一个队列,如果队列在RabbitMq已经存在,则不会起作用,除非队列参数变了会报异常
     * @return
     */
    @Bean
    public Queue queue() {
        /**
         *  参数1:队列名
         *  参数2:是否持久化至硬盘(若要使队列中消息不丢失,同时也需要将消息声明为持久化)
         *  参数3:是否声明该队列是否为连接独占,若为独占,队列仅对首次声明它的连接可见,连接关闭后队列即被删除
         *  参数4:是否自动删除,若没有消费者订阅该队列,队列将被删除
         */
        return new Queue(FIRST_QUEUE,true,false,false);
    }


    @Bean
    public Queue queue2() {
        return new Queue(SECOND_QUEUE);
    }

    /**
     * 死信队列:没有被消费的消息,换个地方重新被消费
     * 产生条件:
     * ①消息被拒绝(basic.reject或basic.nack)并且requeue=false.
     * ②消息TTL过期(测试时可注释掉消费者的注解)
     * ③队列达到最大长度(测试时可注释掉消费者的注解)
     * @return
     */
    @Bean
    public Queue deadQueue(){
        Map<String, Object> args = new HashMap<>(8);
        //声明死信队列的交换机
        args.put("x-dead-letter-exchange", DIRECT_EXCHANGE);
        //声明死信队列抛出异常重定向队列的routingKey
        args.put("x-dead-letter-routing-key", FIRST_ROUTE);
        //设置消息有效期10秒
        args.put("x-message-ttl", 10000);
        //设置队列长度最大为5
        args.put("x-max-length", 5);
        return new Queue(DEAD_QUEUE,true,false,false,args);
    }

    /**
     * 绑定queue到directExchange,设置route
     * @return
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(directExchange()).with(FIRST_ROUTE);
    }

    @Bean
    public Binding binding2() {
        return BindingBuilder.bind(queue2()).to(directExchange()).with(FIRST_ROUTE);
    }

    @Bean
    public Binding bindingDead(){
        return BindingBuilder.bind(deadQueue()).to(directExchange()).with(DEAD_ROUTE);
    }


    /**
     * 消费者监听(消费者一种写法)
     * @param connectionFactory
     * @return
     */
    @Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
        simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
        //将消息的byte[]转化
        simpleMessageListenerContainer.setMessageConverter(new Jackson2JsonMessageConverter());
        //初始化消费者
        simpleMessageListenerContainer.setConcurrentConsumers(1);
        //最大消费者
        simpleMessageListenerContainer.setMaxConcurrentConsumers(5);
        //监听队列
        simpleMessageListenerContainer.setQueueNames(RabbitMqConfig.FIRST_QUEUE);
        //设置消费者应答
        simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        //设置消息监听
        simpleMessageListenerContainer.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
            System.out.println(DateUtil.dateFormat(new Date(),"yyyy-MM-dd HH:mm:ss")+">>>>使用SimpleMessageListenerContainer进行Ack信息【"+message.getMessageProperties().getDeliveryTag()
                    +"】" + new String(message.getBody()));
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        });
        return simpleMessageListenerContainer;
    }
}

生产者

package com.yaoch.rabbitmq;

import com.yaoch.common.utils.DateUtil;
import org.springframework.amqp.core.Message;
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 java.util.Date;
import java.util.UUID;

/**
 * 生产者
 */
@Component
public class MsgProducer implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    public RabbitTemplate rabbitTemplate;

    @Autowired
    public MsgProducer(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
        //确认是否正确到达 Exchange 中
        rabbitTemplate.setConfirmCallback(this);
        //当mandatory 参数设为true时,交换器无法根据自身的类型和路由键找到一个符合条件的队列,
        //那么RabbitMQ 会调用Basic.Return 命令将消息返回给生产者。
        //当mandatory参数设置为false时,出现上述情形,则消息直接被丢弃
        rabbitTemplate.setMandatory(true);
        //消息失败返回,比如路由不到队列时触发回调
        rabbitTemplate.setReturnCallback(this);
    }

    /**
     * 发送消息
     *
     * @param msg
     */
    public void sendMsg(String msg) {
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        //发送消息的两种写法,主要说第二种写法(匿名内部类,实现MessagePostProcessor接口)
        //rabbitTemplate.convertAndSend(RabbitMqConfig.DIRECT_EXCHANGE, RabbitMqConfig.FIRST_ROUTE, "【queue1】" + msg, correlationData);
        rabbitTemplate.convertAndSend(RabbitMqConfig.DIRECT_EXCHANGE, RabbitMqConfig.DEAD_ROUTE
                , "【deadQueue】" + msg, message -> {
                    // 毫秒为单位,指定此消息的延时时长,如果队列也设置了过期时间,以最小值为准
                    message.getMessageProperties().setDelay(5 * 1000);
                    return message;
                }, correlationData);
    }

    /**
     * 消息成功发到broker触发回调
     * @param correlationData 消息id
     * @param ack             是否成功
     * @param cause           失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        System.out.println(DateUtil.dateFormat(new Date(),"yyyy-MM-dd HH:mm:ss")+">>>>生产者消息确认回调:" + correlationData.getId());
        if (!ack) {
            System.out.println("生产者消息发送失败:" + cause);
        }
    }

    /**
     * 消息到达exchange但路由不到队列则会回调
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("消息主体 message : " + message);
        System.out.println("消息回复: " + replyCode);
        System.out.println("描述:" + replyText);
        System.out.println("消息使用的交换器 exchange : " + exchange);
        System.out.println("消息使用的路由键 routing : " + routingKey);
    }
}


消费者1

package com.yaoch.rabbitmq;

import com.rabbitmq.client.Channel;
import com.yaoch.common.utils.DateUtil;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Date;

/**
 * 消费者1
 * 手动Nack
 * @author 肥美的洪洪哥
 */
@Component
public class MsgConsumer {

    @RabbitListener(queues = RabbitMqConfig.FIRST_QUEUE)
    @RabbitHandler
    public void process(Message message, Channel channel) throws IOException {
        System.out.println(DateUtil.dateFormat(new Date(),"yyyy-MM-dd HH:mm:ss")+">>>>消费者1进行NAck【"+message.getMessageProperties().getDeliveryTag()+"】:"+new String(message.getBody()));
        //参数1:消息的id   参数2:是否批量将一次性拒绝所有小于deliveryTag的消息  参数3:被拒绝的消息是否重新入队列
        channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
    }

    @RabbitListener(queues = RabbitMqConfig.DEAD_QUEUE)
    @RabbitHandler
    public void dealDeadMsg(Message message,Channel channel) throws Exception{
        System.out.println(DateUtil.dateFormat(new Date(),"yyyy-MM-dd HH:mm:ss")+">>>>死信队列接收信息:【"+message.getMessageProperties().getDeliveryTag()+"】:"+new String(message.getBody()));
        //注:拒绝信息时,为了能让信息转到死信队列,不能设置消息重回队列,否则本队列会一直循环调用本方法
        //channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
        channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
    }

}

消费者2

package com.yaoch.rabbitmq;

import com.rabbitmq.client.Channel;
import com.yaoch.common.utils.DateUtil;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 消费者2
 * 手动ACK
 * @author 肥美的洪洪哥
 */
@Component
public class MsgConsumer2 {

    @RabbitListener(queues = RabbitMqConfig.FIRST_QUEUE)
    @RabbitHandler
    public void process(Message message, Channel channel) throws Exception {
        System.out.println(DateUtil.dateFormat(new Date(),"yyyy-MM-dd HH:mm:ss")+">>>>消费者2进行Ack【" + message.getMessageProperties().getDeliveryTag() + "】:" + new String(message.getBody()));
        //参数1:消息的id   参数2:是否批量将一次性ACK所有小于deliveryTag的消息。
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        //channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
    }

}