rabbitmq-demo

2.1. 搭建demo工程

创建两个demo工程:

请添加图片描述

依赖相同:

请添加图片描述

2.1.1. 生产者

请添加图片描述

application.yml:

server:
  port: 8080
spring:
  rabbitmq:
    host: 172.16.116.100
    port: 5672
    virtual-host: /fengge
    username: fengge
    password: fengge
    publisher-confirm-type: simple # SIMPLE-同步确认(阻塞) CORRELATED-异步确认
    publisher-returns: true # 确认消息是否到达队列

RabbitConfig配置类:配置发送方确认

@Configuration
@Slf4j
public class RabbitConfig {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        // 确认消息是否到达交换机
        this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (!ack){
                log.warn("消息没有到达交换机:" + cause);
            }
        });

        // 确认消息是否到达队列,到达队列该方法不执行
        this.rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            log.warn("消息没有到达队列,来自于交换机:{},路由键:{},消息内容:{}", exchange, routingKey, new String(message.getBody()));
        });
    }
}

ProducerDemoApplicationTests测试用例发送消息

@SpringBootTest
class ProducerDemoApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void contextLoads() {//Exchange队列所绑定的交换机:SPRING_RABBIT_EXCHANGE,Routing Key队列和交换机绑定的路由键:a.b
        this.rabbitTemplate.convertAndSend("SPRING_RABBIT_EXCHANGE", "a.b", "hello spring rabbit!");
    }
}

2.1.2. 消费者

请添加图片描述

application.yml:

server:
  port: 8081
spring:
  rabbitmq:
    host: 172.16.116.100
    port: 5672
    virtual-host: /fengge
    username: fengge
    password: fengge
    listener:
      type: simple # simple-listener容器使用一个额外线程处理消息  direct-listener(监听器)容器直接使用consumer线程
      simple:
        acknowledge-mode: manual # manual-手动  auto-自动(无异常直接确认,有异常无限重试) none-不重试
        prefetch: 1 # 能者多劳
        concurrency: 3 # 避免消息堆积,初始化多个消费者线程

ConsumerListener消费者代码:

@Component
public class ConsumerListener {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "SPRING_RABBIT_QUEUE", durable = "true"),
            exchange = @Exchange(value = "SPRING_RABBIT_EXCHANGE", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC),
            key = {"a.b"}
    ))
    public void listener(String msg, Channel channel, Message message) throws IOException {
        try {
            System.out.println(msg);
            // 手动确认消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            e.printStackTrace();
            // 是否已经重试过
            if (message.getMessageProperties().getRedelivered()){
                // 已重试过直接拒绝
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            } else {
                // 未重试过,重新入队
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            }
        }
    }
}

注解说明:

  • @Componet:类上的注解,注册到Spring容器
  • @RabbitListener:方法上的注解,声明这个方法是一个消费者方法,需要指定下面的属性:
    • bindings:指定绑定关系,可以有多个。值是@QueueBinding的数组。@QueueBinding包含下面属性:
      • value:这个消费者关联的队列。值是@Queue,代表一个队列
      • exchange:队列所绑定的交换机,值是@Exchange类型
      • key:队列和交换机绑定的RoutingKey

类似listen这样的方法在一个类中可以写多个,就代表多个消费者。

2.2. 延时队列及死信队列

2.2.1. 概念回顾

延时队列:

如果队列不设置TTL,表示消息永远不会过期

如果将TTL设置为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。

如果设置了队列的TTL属性,那么一旦消息过期,就会被队列丢弃

死信队列 : 如果队列里的消息出现以下情况:

  1. 消息被否定确认,使用 channel.basicNackchannel.basicReject ,并且此时requeue 属性被设置为false
  2. 消息在队列的存活时间超过设置的TTL时间。
  3. 消息队列的消息数量已经超过最大队列长度。

那么该消息将成为“死信”。

“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。

一般死信队列和延时队列一起使用,使用场景

  1. 订单在十分钟之内未支付则自动取消。
  2. 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
  3. 账单在一周内未支付,则自动结算。
  4. 用户注册成功后,如果三天内没有登陆则进行短信提醒。
  5. 用户发起退款,如果三天内没有得到处理则通知相关运营人员。
  6. 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议。

2.2.2. 结构及步骤

使用方式如下:

请添加图片描述

步骤:

  1. 声明延时交换机

  2. 声明延时队列

    x-message-ttl:指定TTL时间
    x-dead-letter-exchange:死信转发所需的死信交换机(DLX)
    x-dead-letter-routing-key:转发死信时的routingKey(DLK)
    
  3. 延时队列绑定到延时交换机

  4. 声明死信交换机(DLX)

  5. 声明死信队列(DLQ)

  6. 死信队列绑定到死信交换机,rontingKey要和第2步的DLK一致。

2.2.3. 配置延时及死信队列

修改生产者RabbitConfig,添加延时队列 死信队列及他们之间的绑定关系:

@Configuration
@Slf4j
public class RabbitConfig {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        // 确认消息是否到达交换机
        this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (!ack){
                log.warn("消息没有到达交换机:" + cause);
            }
        });

        // 确认消息是否到达队列,到达队列该方法不执行
        this.rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            log.warn("消息没有到达队列,来自于交换机:{},路由键:{},消息内容:{}", exchange, routingKey, new String(message.getBody()));
        });
    }

    @Bean
    public TopicExchange delayExchange(){
        return ExchangeBuilder.topicExchange("·	").build();
    }

    @Bean
    public Queue delayQueue(){
        return QueueBuilder.durable("SPRING_DELAY_QUEUE")
                .withArgument("x-message-ttl", 60000)
                .withArgument("x-dead-letter-exchange", "SPRING_DEAD_EXCHANGE")
                .withArgument("x-dead-letter-routing-key", "ab.dead")
                .build();
    }

    @Bean
    public Binding delayBinding(TopicExchange delayExchange, Queue delayQueue){
        return BindingBuilder.bind(delayQueue).to(delayExchange).with("ab.delay");
    }

    @Bean
    public TopicExchange deadExchange(){
        return ExchangeBuilder.topicExchange("SPRING_DEAD_EXCHANGE").build();
    }

    @Bean
    public Queue deadQueue(){
        return QueueBuilder.durable("SPRING_DEAD_QUEUE").build();
    }

    @Bean
    public Binding deadBinding(TopicExchange deadExchange, Queue deadQueue){
        return BindingBuilder.bind(deadQueue).to(deadExchange).with("ab.dead");
    }
}

2.2.4. 生产者发送消息给延时队列

请添加图片描述

2.2.5. 消费者通过监听器获取消息

修改消费者的ConsumerListener添加listener2方法:

@Component
public class ConsumerListener {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "SPRING_RABBIT_QUEUE", durable = "true"),
            exchange = @Exchange(value = "SPRING_RABBIT_EXCHANGE", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC),
            key = {"a.b"}
    ))
    public void listener(String msg, Channel channel, Message message) throws IOException {
        try {
            System.out.println(msg);
            // 手动确认消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            e.printStackTrace();
            // 是否已经重试过
            if (message.getMessageProperties().getRedelivered()){
                // 已重试过直接拒绝
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                // 未重试过,重新入队
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),false, true);
            }
        }
    }

    @RabbitListener(queues = "SPRING_DEAD_QUEUE")
    public void listener2(String msg, Channel channel, Message message) throws IOException {
        try {
            System.out.println(msg);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            e.printStackTrace();
            if (message.getMessageProperties().getRedelivered()){
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            } else {
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            }
        }
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值