RabbitMq

183 阅读14分钟

JMS与AMQP的区别

  • JMS就是JMS API,主要为Java应用提供统一的消息操作,支持的消息类型有:TextMessage、MapMessage、ByteMessage、StreamMessage、ObjectMessage、Message,ActiveMQ就是基于JMS实现的。
  • AMQP是一种协议,可以跨平台使用,并不受客户端/中间件不同产品、不同开发语言的限制,只支持byte[],本质来讲、AMQP的消息模型,在路由机制上做了更详细的划分。

获取MQ的连接

public class ConnectionUtils {
    public static Connection getConnection() throws IOException, TimeoutException {
        //定义一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("118.31.224.147");
        //AMQP 5672
        factory.setPort(5672);
        //设置虚拟主机  vhost 相当于数据库中的库 一般以 / 开头
        factory.setVirtualHost("/");
        //用户名
        factory.setUsername("guest");
        //密码
        factory.setPassword("guest");
        return factory.newConnection();
    }
}

1.1 模型

blog.csdn.net/hry2015/art…

简单队列

如果发送消息时不绑定任何交换机,只写" ",使用的默认交换机-----AMQP default

P:消息生产者  

红色的队列

C:消费者

不足:耦合度高-----不能多个消费者消费队列中消息

工作队列

为什么会出现工作队列?

simple队列是一一对应的,而且我们实际开发,生产者发送消息很快,而消费者一般是要跟业务相结合的,消费者接收到消息后就需要处理,可能会花费时间,这时候队列就会积压很多消息。

默认分配方式是轮询方式,即不管消费者处理信息的效率,队列都会给所有消费者轮流发送一条信息,直到消息发送完毕。

订阅模式

借助fanout 交换机。

交换机并没有存储数据的能力,只有队列有。

路由模式

借助直连交换机,绑定队列时有一个路由key,根据路由key保证发送到哪些消费者上。

主题模式

可以使用通配符来选择发送到哪个队列。

消息接收应答方式

RabbitMQ发送消息给队列,会收到一个应答,然后从RabbitMQ中删除该消息。

默认是两种

  • 一发送到消费者端,就会自动确认就会直接删除队列中的消息
  • 手动应答:在合适的时候进行确认消息,删除队列中的消息,如果不回应,这个消息则会一直留在mq里

但是用 spring 整合 mq 时,有三种设置:

  • manual:手动 ack,需要在业务代码结束后,调用 api 发送 ack
  • auto:自动 ack,由 spring 监测 listener 代码是否出现异常,没有异常则返回 ack,反之返回 nack,如果出现异常会一直重试
  • none:关闭 ack,MQ 在消息投递后会立即删除消息
//自动应答:表示消费者一收到消息就会提示队列中可以删除该消息,不管你对消息的操作是否完成
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
//当消费者连上队列之后,消费者没有指定一次获取消息的条数,所以队列把消息一下子推送到消费者端,然后自动确认就会直接删除队列中的消息
//手动应答:在合适的时候进行确认消息,删除队列中的消息,如果不回应,这个消息则会一直留在mq里
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
//通过显示调用   channel.basicAck(envelope.getDeliveryTag(), false);  来告诉MQ删除消息

//信息执行失败   最后一个参数为true,会把该消息重新放到队列的末端,为false则不会重回队列
//第二个参数为是否是批量的
channel.basicNack(envelope.getDeliveryTag(), false, true);

如果设置消费者每次从队列中获取指定的条数

channel.basicQos(1);  

如果开启手动应答,消费者将不再接收消息   这时候就是公平分发,能者多劳。

持久化

//声明队列时设置队列持久化    durable默认是false  未持久化
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
//如果MQ中已经有一个同名的队列,再更改durable未true是不可以的,rabbitMQ不允许重新定义一个已存在的队列
//交换器持久化   durable=true
channel.exchangeDeclare(EXCHANGE_NAME,"direct",true);

rabbitMQ的消息发送确认机制(事务 + confirm)

在rabbitMQ中,我们可以通过持久化数据,解决rabbitMQ服务器异常的数据丢失问题。

生产者将消息发送出去之后,消息到底到没到rabbitMQ服务器,怎么知道到没到呢?

《RabbitMQ》 | 消息丢失也就这么回事

两种方式

AMQP实现了事务机制

 try {
         //开启事务
         channel.txSelect();
         //发布消息  basic:基础的  publish:发布
         channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
         //提交
         channel.txCommit();
   } catch (Exception e) {
         //回滚数据
         channel.txRollback();
}

Confirm模式   实现原理

confirm只保证发送到交换机,路由到队列是否成功需要 returnListener 即 ReturnCallback。

生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者得知消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息重新写入磁盘后发出,broker回传给生产者的确认消息中,deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm是异步的,

普通方式确认:

public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //从连接中获取一个信道
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        //将channel设置为confirm模式  如果该队列已经开启事务,则不能再开启confirm模式
        channel.confirmSelect();
        String msg = "heool confirm message";
        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());

   	    //普通发送方式确认
        if (!channel.waitForConfirms()) {
            System.out.println("发送失败  message send failed");
        } else {
            System.out.println("发送成功 message send ok");
        }
        channel.close();
        connection.close();
    }

批量发送确认:

channel.waitForConfirmsOrDie()批量确认模式;

异步模式:

Channel 对象提供的 ConfirmListener() 回调方法只包含 deliveryTag (当前 Chanel 发出的消息序列号),我们需要为每一个 Channel 维护一个 unconfirm 的消息序列集合,每 publish 一条数据,集合中元素加 1,每回调一次 handleAck 方法,unconfitrm 集合删除响应的一条 (multiple = false)或多条(multiple = true)记录,从程序上看,这个 unconfirm 最好采用有序集合 SortedSet 存储数据。

public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //从连接中获取一个信道
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        //将channel设置为confirm模式  如果该队列已经开启事务,则不能再开启confirm模式
        channel.confirmSelect();

        SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            //b为true  一下删除多条记录   没问题调用的方法
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println("已确认消息:" + l + ",是否是多个消息:" + b);
                if (b) {
                    //删除l + L之前的所有数据
                    confirmSet.headSet(1 + l).clear();
                } else {
                    confirmSet.remove(l);
                }
            }
            //有问题调用的方法
            @Override
            public void handleNack(long l, boolean b) throws IOException {
                System.out.println("已确认消息失败:" + l + ",是否是多个消息:" + b);
                if (b) {
                    confirmSet.headSet(1 + l).clear();
                } else {
                    confirmSet.remove(l);
                }
            }
        });
        String msg = "hello confirm message";
        for (int i = 0; i < 50; i++) {
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            long nextPublishSeqNo = channel.getNextPublishSeqNo();
            System.out.println(nextPublishSeqNo + " 条消息");
            confirmSet.add(nextPublishSeqNo);
        }

//        channel.close();  不能关闭  因为需要处理异步的消息确认
//        connection.close();
    }

spring 集成 RabbitMQ

依赖

<dependencies>
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.7.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>
    </dependencies>

xml配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/rabbit
    http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd" >

<!--    定义rabbitMQ的连接工厂-->
    <rabbit:connection-factory id="connectionFactory"
           host="118.31.224.147" port="5672" username="test_vhost" password="654321"
           virtual-host="/test_vhost" />

<!--    定义Rabbit模板,指定连接工程以及定义exchange,也可定义队列-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" 			exchange="fanoutExchange"/> <!--queue=""-->

<!--    NQ的管理,包括队列、交换器声明等-->
    <rabbit:admin connection-factory="connectionFactory"/>
<!--    定义队列  durable=true:开启持久化默认是true-->
    <rabbit:queue name="myQueue" durable="true"/>

<!--    定义订阅模式交换器,自动声明-->
    <rabbit:fanout-exchange name="fanoutExchange" durable="true">
        <rabbit:bindings>
            <rabbit:binding queue="myQueue"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>

<!--    路由模式  <rabbit:direct-exchange name="directExchange"/>-->
<!--    主题模式   <rabbit:topic-exchange name="topicExchange"/>-->

<!--    队列监听-->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="foo" method="listen" queue-names="myQueue"/>
    </rabbit:listener-container>
<!--    消费者-->
    <bean id="foo" class="com.wzl.rabbitmq.spring.MyConsumer"/>
</beans>

注解配置类

 @Configuration
 @ComponentScan({"com.wzl.rabbitmq.spring"})
 @Bean
 public ConnectionFactory connectionFactory() {
     CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
     connectionFactory.setAddresses("118.31.224.147");
     connectionFactory.setUsername("test_vhost");
     connectionFactory.setPassword("654321");
     connectionFactory.setVirtualHost("/test_vhost");
  return connectionFactory;
 }

 /**MQ的管理,也可对队列,交换机等的操作
  * <rabbit:admin connection-factory="connectionFactory"/>*/
 @Bean
 public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
     RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
     //这里要设置成自动启动
     rabbitAdmin.setAutoStartup(true);
     return rabbitAdmin;
 }

 /**rabbitMQ模板
 	<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" 			exchange="fanoutExchange"/>*/
 @Bean
 public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
     RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
     //可以通过rabbitTemplate开启confirm模式等操作
     return rabbitTemplate;
 }
 /**
   * 监听容器,容器启动之后就可以持续监听
   */
  @Bean
  public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
      //监听队列
      container.setQueueNames("test_direct_queue");
      //当前的消费者数量
      container.setConcurrentConsumers(1);
      //最大消费者数量
      container.setMaxConcurrentConsumers(5);
      //是否使用重回队列:否
      container.setDefaultRequeueRejected(false);
      //应答方式
      container.setAcknowledgeMode(AcknowledgeMode.AUTO);	
      //监听到之后的操作
      container.setMessageListener(new ChannelAwareMessageListener() {
          @Override
          public void onMessage(Message message, Channel channel) throws Exception {
              String msg = new String(message.getBody());
              System.out.println("------------------消费者监听到了消息:" + msg);
          }
      });
      
      //也可用适配器方式进行监听操作
      MessageListenerAdapter adapter = new MessageListenerAdapter(new ListenerAdapter());
      //默认监听方法名是handleMessage,修改为listenerMethod
      adapter.setDefaultListenerMethod("listenerMethod");

      //适配器方法:让队列名称和方法名进行一一对应
      Map<String,String> queueOrTagToMethodName = new HashMap<>();
      queueOrTagToMethodName.put("queueName","methodName");
      adapter.setQueueOrTagToMethodName(queueOrTagToMethodName);

      container.setMessageListener(adapter);
      return container;
  }

监听器类

/**
 * 如果没有接收对应参数类型的方法,会报异常
 * 或者监听容器设置一个实现MessageConverter接口的转换器
 */
public class ListenerAdapter {
    /**监听字节数组为参数的消息的操作*/
    public void handleMessage(byte[] message) {
        System.out.println("适配器的默认方法,消息内容" + new String(message));
    }
    /**监听字符串为参数的消息的操作*/
    public void handleMessage(String message) {
        System.out.println("适配器的默认方法,消息内容" + message);
    }
}

声明、绑定与发送消息

//声明与绑定方式:
public class RabbitTests {
    private static RabbitAdmin rabbitAdmin;
    private static RabbitTemplate rabbitTemplate;

    static {
        ApplicationContext context = new 																	AnnotationConfigApplicationContext(RabbitMqConfig.class);
        rabbitAdmin = (RabbitAdmin) context.getBean("rabbitAdmin");
        rabbitTemplate = (RabbitTemplate) context.getBean("rabbitTemplate");
    }
    @Test
    public void testAdmin() {
        //声明创建交换机,不持久化
        rabbitAdmin.declareExchange(new DirectExchange("test_direct", false, false));
        //声明创建队列,不持久化
        rabbitAdmin.declareQueue(new Queue("test_direct_queue", false));

        //绑定交换机
        rabbitAdmin.declareBinding(new Binding("test_direct_queue",		 //队列名
                                         Binding.DestinationType.QUEUE,//队列绑定
                                               "test_direct",		 //交换机名
                                               "routingKey",		 //路由名
                                               null));				 //map集合
        //清空队列数据  false是否需要等待
        rabbitAdmin.purgeQueue("queueName",false);
    }
    
    //发送消息
    @Test
    public void testSendMessage() {
        //RabbitMq的消息主要是两部分组成  一部分是properties,和要发送的消息
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.getHeaders().put("desc", "信息描述...");
        messageProperties.getHeaders().put("headers","自定义的数据");
        Message message = new Message("Hello RabbitMQ".getBytes(), messageProperties);
        rabbitTemplate.convertAndSend("test_direct", "routingKey", message, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                System.out.println("消息后置处理操作,先对消息进行处理操作,再发送");
                message.getMessageProperties().getHeaders().put("desc","修改的信息描述...");
                return message;
            }
        });
        
        //使用的是test_direct交换机
        rabbitTemplate.send("test_direct","routingKey",message);
        //直接向指定队列发送消息  使用的是默认的交换机------AMQP default
        rabbitTemplate.convertAndSend("test_direct_queue", "hello Java");
    }
}

生产者

public class SpringProducer {
    public static void main(String[] args) {
        AbstractApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath:rabbitMq.xml");
        //RabbitMQ模板
        RabbitTemplate template = classPathXmlApplicationContext.getBean(RabbitTemplate.class);
        //发送消息
        template.convertAndSend("Hello World!");
        try {
            Thread.sleep(1000);
            classPathXmlApplicationContext.destroy();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

配置文件的消费者

public class MyConsumer {
    public void listen(String msg) {
        System.out.println("消费者:" + msg);
    }
}

springBoot 集成 RabbitMQ

依赖

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

配置文件

spring.rabbitmq.host=118.31.224.147
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#消息确认改为手动  开启ACK simple 和 direct 是rabbitmq 的容器类型
spring.rabbitmq.listener.type=simple
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual

# 消费者每次只取用一个消息
spring.rabbitmq.listener.simple.prefetch=1
spring.rabbitmq.listener.direct.prefetch=1

# 开启confirm发送确认机制
# 通过实现ConfirmCallBack接口,消息发送到交换器Exchange后触发回调
spring.rabbitmq.publisher-confirm-type=correlated

#通过实现ReturnCallback接口,如果消息从交换器发送到对应队列失败时触发
# 比如根据发送消息时指定的routingKey找不到队列时会触发
spring.rabbitmq.publisher-returns=true
#设置为 true 后 消费者在消息没有被路由到合适队列情况下会被return监听,
# 而不会自动删除
spring.rabbitmq.template.mandatory=true

配置类

比spring整合RabbitMQ更简单,不需要创建Admin、RabbitTemplate和监听容器,大部分配置可以在配置文件中配置

@Configuration
public class FanoutConfig {
    /**两个队列*/
    /**this(name, true, false, false); 
    durable:默认开启持久化 exclusive:与mq的连接断开时该队列是否自动删除 autoDelete:最后一个消费者断	开连接之后队列是否自动删除*/
    @Bean
    public Queue fanout1() {
        return new Queue("fanout1");
    }
    @Bean
    public Queue fanout2() {
        return new Queue("fanout2");
    }

    /**一个交换机*/
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanoutExchange");
    }
  
    /**把队列绑定到交换机,后还可以.with(routingKey路由键)*/
    @Bean
    public Binding fanoutQueue1() {
        return BindingBuilder.bind(fanout1()).to(fanoutExchange());
    }
    @Bean
    public Binding fanoutQueue2() {
        return BindingBuilder.bind(fanout2()).to(fanoutExchange());
    }

}
设置某些队列手动 ack

设置某些队列手动 ack 需要新建一个监听容器

@Configuration
public class WordMqConfiguration {

    public static final String INFORM_USER_RECITE_WORD_EXCHANGE="user.recite.word.exchange";
    public static final String USER_RECITE_WORD_QUEUE_RK="user.recite.word.queue.rk.*";

    @Bean
    public Exchange informUserReciteWordExchange(){
        return ExchangeBuilder.topicExchange(INFORM_USER_RECITE_WORD_EXCHANGE).durable(true).build();
    }

    @Bean
    public Queue informUserReciteWordQueue() {
        return new Queue(MqConstants.SAVE_USER_RECITE_WORD_QUEUE);
    }

    @Bean
    public Binding informUserReciteWordBinding(@Qualifier("informUserReciteWordQueue") Queue queue,
                                        @Qualifier("informUserReciteWordExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(USER_RECITE_WORD_QUEUE_RK).noargs();
    }
    @Bean
    public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        // 设置确认模式手工确认
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        container.setQueues(informUserReciteWordQueue());
        return container;
    }
}

发送与消费消息

生产者

@Component
public class RabbitSend {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**确保消息正确发送到交换机*/
    private RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
        @Override
        public void confirm(CorrelationData correlationData, boolean b, String s) {
            System.out.println("消息唯一标识:" + correlationData);
            System.out.println("消息是否发送成功:" + b);
            if (b) {
                System.out.println("发送成功");
            } else {
                System.err.println("发送失败,失败原因:" + s);
            }
        }
    };
    /**确保消息正确发送到队列,发送到队列时失败之后的操作*/
    private RabbitTemplate.ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {
        @Override
        public void returnedMessage(Message message, int i, String s, String s1, String s2) {
            System.out.println("消息主题:" + message);
            System.out.println("响应码:" + i);
            System.out.println("错误概述:" + s);
            System.out.println("使用的交换机:" + s1);
            System.out.println("消息使用的路由键:" + s2);
        }
    };

    public void send() {
        //开启confirm模式,防止消息发送到交换机出现问题
        rabbitTemplate.setConfirmCallback(confirmCallback);
        //开启return机制,防止消息发送到队列出现问题
        rabbitTemplate.setReturnCallback(returnCallback);
        rabbitTemplate.convertAndSend("test_direct_queue", "hello confirm".getBytes());
    }
}

消费者

@Component
@RabbitListener(queues = "test_direct_queue")
public class RabbitMessageListener {
    @RabbitHandler
    //发送消息时发送什么类型的数据,这里就需要一个什么类型的数据接收,一般用字符串类型,发送对象需要序列化
    public void message(Channel channel, Message message,String msg) throws IOException {
        System.out.println("----------------------------------------------");
        System.out.println(message);
        System.out.println("接收到消息:" + msg);
        //设置了手动应答
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//信息执行失败 ,最后一个参数为true,会把该消息重新放到队列的末端,为false则不会重回队列第二个为消息是否是批量的
//channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);      
    }
}

补偿机制

SpringBoot整合-RabbitMQ,消费者消费过程中出现异常,会自动进行重试,可以修改重试次数与重试间隔时间。实现原理是AOP捕获到异常就缓存到RabbitMq服务端进行重试。

死信/死信队列

什么是死信?

  • 消息被拒绝(basic.reject或者basic.nack)并且requeue=false
  • 消息TTL过期
  • 队列达到最大长度,队列满了

死信的处理方式

  • 丢弃,如果不是很重要,可以选择丢弃
  • 记录死信入库,然后做后续的业务分析或处理
  • 通过死信队列,由负责监听死信的应用程序进行处理
public class DLXRecv {
    public static final String QUEUE_NAME = "test_dlx_queue";
    public static final String EXCHANGE_NAME = "test_dlx_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明、创建普通的交换机  主题模式
        channel.exchangeDeclare(EXCHANGE_NAME, "topic", true);

        Map<String, Object> arguments = new HashMap<>();
        /*value是自己设定的*/
        arguments.put("x-dead-letter-exchange", "dlx.exchange");
        // 设置队列中的消息 10s 钟后过期
        arguments.put("x-message-ttl", 10000);

        /*arguments当做声明队列的参数,声明正常的队列,出现死信后消息进入指定的死信交换机*/
        channel.queueDeclare(QUEUE_NAME, true, false, false, arguments);
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "dlx.#");

        /*创建、声明死信交换机,交换机名字是前面map里存的value,然后绑定队列、该队列就成了死					  信队列*/
        channel.exchangeDeclare("dlx.exchange","topic",true);
        channel.queueDeclare("dlx.queue",true,false,false,null);
        //绑定队列到交换机  路由key是#,所有都可以接收
        channel.queueBind("dlx.queue","dlx.exchange","#");

        /*这是收到消息要做的处理*/
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println(msg);
                //手动回执  告诉rabbitMQ可以删除这条消息了
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        /*false:手动确认  在合适的时候来进行消息确认
        * 监听的是"dlx.queue",如果消费者收到消息,说明死信队列有消息了*/
        channel.basicConsume("dlx.queue", false, consumer);
    }
}

springBoot创建死信队列

@Configuration
public class RabbitMqConfig {
    @Bean
    public Queue orderQueue() {
        Map<String, Object> arguments = new HashMap<>(2);
        arguments.put("x-dead-letter-exchange","dlx_exchange");
        arguments.put("x-message-ttl",10000);
        return new Queue("no_dlx_queue",true,false,false,arguments);
    }
    @Bean
    public Queue dlxQueue() {
        return new Queue("dlx_queue");
    }
    @Bean
    public TopicExchange dlxExchange() {
        return new TopicExchange("dlx_exchange");
    }
    //队列绑定死信交换机,称为死信队列
    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("#");
    }
}
信道Channel的作用

数据流动都是通过channel进行的,而且定义Queue、Exchange、绑定Queue和Exchange、发布消息、监听队列等都是通过Channel上进行的

RabbitMq 延迟队列

SpringCloud Stream结合rabbitMQ实现消息消费失败重发机制

安装与配置

下载地址