消息中间件之rabbitMQ实战-死信队列

该篇文章内容较多,包括有rabbitMq相关的一些简单理论介绍,集成spring Boot,provider消息推送实例,consumer消息消费实例,Direct(直连类型交换机)、Fanout(广播类型交换机)的使用、死信队列等。

MQ使用场景

1、异步:

当一个服务调用链路过长时,而用户只关心前面的流程,例如下订单、减库存、增积分、发短信等等,而用户只关心我的订单是否创建成功,后面的流程可以放到MQ去通知下游系统。

2、解耦:

你一个订单流程,你扣积分,扣优惠券,发短信,扣库存。。。等等这么多业务要调用这么多的接口,每次加一个你要调用一个接口然后还要重新发布系统,写一次两次还好,

3、削峰:

简单,把请求放到队列里面,然后至于每秒消费多少请求,就看自己的服务器处理能力,你能处理5000QPS你就消费这么多,可能会比正常的慢一点,但是不至于打挂服务器,等流量高峰下去了,你的服务也就没压力了。

rabbitMQ工作模型

首先先介绍一个简单的一个消息推送到接收的流程,提供一个简单的图:

在这里插入图片描述

图中的Producer就是消息发送者服务,Broker是rabbitMQ服务器包括VHost虚拟主机(可以根据不同业务创建不同的用户分配不同的VHost),Consumer为消息消费之,在rabbitMQ中支持push和pull两种消费方式,在spring集成中之封装了push方式,当消息发送者发送一条消息(直连方式)并指定了Exchange和BindingKey,消息首先会路由到指定交换机,交换机会根据指定的BindingKey发送到相应的队列,有该队列的消费者消费。

交换机类型

常用的交换机有以下三种,因为消费者是从队列获取信息的,队列是绑定交换机的(一般),所以对应的消息推送/接收模式也会有以下几种:

Direct Exchange

直连型交换机,根据消息携带的路由键将消息投递给对应队列。

大致流程,有一个队列绑定到一个直连交换机上,同时赋予一个路由键 routing key 。
然后当一个消息携带着路由值为X,这个消息通过生产者发送给交换机时,交换机就会根据这个路由值X去寻找绑定值也是X的队列。

Fanout Exchange

扇型交换机,这个交换机没有路由键概念,就算你绑了路由键也是无视的。 这个交换机在接收到消息后,会直接转发到绑定到它上面的所有队列。

Topic Exchange

主题交换机,这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的。
简单地介绍下规则:*和#

​ *:匹配一个单词,例如ab.bd.lo为三个单词以“.”为分隔符。

​ #:匹配一个或多个单词。

安装rabbitMQ

以docker-compose为例,下面是docker-compose.yml

version: '2'
services:
  rabbitmq:
    hostname: myrabbitmq
    image: rabbitmq:management
    ports:
      - 15673:15672
      - 5673:5672
    restart: always
    volumes:
      - ./data/rabbitMQ:/var/lib/rabbitmq
    environment:
      - RABBITMQ_DEFAULT_USER=root
      - RABBITMQ_DEFAULT_PASS=root

启动rabbitMQ:docker-compose up -d,浏览器输入http://127.0.0.1:15673

在这里插入图片描述

用户:root,密码:root登录即可。

在这里插入图片描述

代码部分

集成Spring Boot

rabbitMQ依赖包,amqp(即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。)

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

实现直连交换机模型

在这里插入图片描述

配置文件
spring:
  application:
    name: rabbitmq-provider
  rabbitmq:
    host: 127.0.0.1
    port: 5673
    username: root
    password: root
    virtual-host: /
创建消息生产者配置类
@Configuration
public class RabbitMQConfiguration {

    /**
     * 声明springQueue队列
     * @return
     */
    @Bean
    public Queue springQueue() {
        return new Queue("spring_queue",true);
    }

    /**
     * 声明dubboQueue队列
     * @return
     */
    @Bean
    public Queue dubboQueue() {
        return new Queue("dubbo_queue",true);
    }

    /**
     * 声明dubboQueue队列
     * @return
     */
    @Bean
    public Queue vueQueue() {
        return new Queue("vue_queue",true);
    }
    /**
     * 声明frame_exchange直连类型交换机
     * @return
     */
    @Bean
    DirectExchange frameDirectExchange() {
        return new DirectExchange("frame_exchange",true,false);
    }

    /**
     * 队列springQueue与交换机frame_exchange绑定
     * @return
     */
    @Bean
    Binding bindingDirectSpring() {
        return BindingBuilder.bind(springQueue()).to(frameDirectExchange()).with("spring");
    }
    /**
     * 队列dubboQueue与交换机frame_exchange绑定
     * @return
     */
    @Bean
    Binding bindingDirectDubbo() {
        return BindingBuilder.bind(dubboQueue()).to(frameDirectExchange()).with("dubbo");
    }
    /**
     * 队列vueQueue与交换机frame_exchange绑定
     * @return
     */
    @Bean
    Binding bindingDirectVue() {
        return BindingBuilder.bind(vueQueue()).to(frameDirectExchange()).with("vue");
    }
}
消息生产者测试
@SpringBootTest(classes = RabbitMQApplication.class)
public class ProductTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void springMessage(){
        rabbitTemplate.convertAndSend("frame_exchange", "spring", "1111");
    }

    @Test
    public void dubboMessage(){
        rabbitTemplate.convertAndSend("frame_exchange", "dubbo", "1111");
    }

    @Test
    public void vueMessage(){
        rabbitTemplate.convertAndSend("frame_exchange", "vue", "1111");
    }
}
消费者配置类

与生产者一样即可

监听队列

分监听对应队列

@Component
@RabbitListener(queues = "spring_queue")
public class SpringQueueConsumer {

    @RabbitHandler
    public void process(String msg) {
        System.out.println("SpringQueueConsumer 消费消息:" + msg);
    }
}
@Component
@RabbitListener(queues = "dubbo_queue")
public class DubboQueueConsumer {

    @RabbitHandler
    public void process(String msg) {
        System.out.println("DubboQueueConsumer 消费消息:" + msg);
    }
}
@Component
@RabbitListener(queues = "vue_queue")
public class VueQueueConsumer {
    @RabbitHandler
    public void process(String msg) {
        System.out.println("VueQueueConsumer 消费消息:" + msg);
    }
}
启动消费者
运行单元测试
@Test
public void springMessage(){
  // BindingKey = "spring"
  rabbitTemplate.convertAndSend("frame_exchange", "spring", "1111");
}

消费者输出:SpringQueueConsumer 消费消息:1111

@Test
public void dubboMessage(){
  // BindingKey = "dubbo"
  rabbitTemplate.convertAndSend("frame_exchange", "dubbo", "1111");
}

消费者输出:DubboQueueConsumer 消费消息:1111

@Test
public void vueMessage(){
  // BindingKey = "vue"
  rabbitTemplate.convertAndSend("frame_exchange", "vue", "1111");
}

消费者输出:VueQueueConsumer 消费消息:1111

实现广播类型交换机模型

在这里插入图片描述

配置类
@Configuration
public class FanoutExchangeConfiguration {

    /**
     * 声明springQueue队列
     * @return
     */
    @Bean
    public Queue springQueue() {
        return new Queue("spring_queue",true);
    }

    /**
     * 声明dubboQueue队列
     * @return
     */
    @Bean
    public Queue dubboQueue() {
        return new Queue("dubbo_queue",true);
    }

    /**
     * 声明fanout_exchange广播类型交换机
     * @return
     */
    @Bean
    FanoutExchange frameFanoutExchange() {
        return new FanoutExchange("fanout_exchange",true,false);
    }

    /**
     * 队列dubboQueue与交换机fanout_exchange绑定
     * @return
     */
    @Bean
    Binding bindingDirectDubbo() {
        return BindingBuilder.bind(dubboQueue()).to(frameFanoutExchange());
    }
    /**
     * 队列vueQueue与交换机fanout_exchange绑定
     * @return
     */
    @Bean
    Binding bindingDirectVue() {
        return BindingBuilder.bind(springQueue()).to(frameFanoutExchange());
    }
}
测试
@Test
public void fanoutMessage(){
  // 指定广播类型交换机 广播不需要BindingKey,只要与交换机绑定的所有消费者都会消费
  rabbitTemplate.convertAndSend("fanout_exchange", null,"1111");
}
结果

两个消费者都有消费

SpringQueueConsumer 消费消息:1111
DubboQueueConsumer 消费消息:1111

死信队列

死信队列介绍
  • 死信队列:DLX,dead-letter-exchange
  • 利用DLX,当消息在一个队列中变成死信 (dead message) 之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX
消息变成死信有以下几种情况
  • 消息被拒绝(basic.reject / basic.nack),并且requeue = false
  • 消息TTL过期
  • 队列达到最大长度
死信处理过程
  • DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
  • 当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。
  • 可以监听这个队列中的消息做相应的处理。

引用:作者:若汐缘,链接:https://www.jianshu.com/p/986ee5eb78bc

设置死信队列的Exchange和Queue
@Configuration
public class DLXConfiguration {

    /**
     * 普通队列
     *
     * @return
     */
    @Bean
    public Queue simpleQueue() {
        return QueueBuilder
                .durable("simpleQueue")
                //声明该队列的死信消息发送到的 交换机 (队列添加了这个参数之后会自动与该交换机绑定,并设置路由键,不需要开发者手动设置)
                .withArgument("x-dead-letter-exchange", "DXLExchange")
                //声明该队列死信消息在交换机的 路由键
                .withArgument("x-dead-letter-routing-key", "dead-letter-routing-key")
                .build();
    }

    /**
     * 普通交换机
     * @return
     */
    @Bean
    public DirectExchange simpleExchange() {
        return new DirectExchange("simpleExchange",true, false);
    }

    /**
     * 普通队列与交换机绑定
     *
     * @param simpleQueue    普通队列名
     * @param simpleExchange 普通交换机名
     * @return
     */
    @Bean
    public Binding binding(Queue simpleQueue, DirectExchange simpleExchange) {
        return BindingBuilder.bind(simpleQueue).to(simpleExchange).with("simple");
    }

    /**
     * 死信交换机
     *
     * @return
     */
    @Bean
    public Exchange deadLetterExchange() {
        return new DirectExchange("DXLExchange", true, false);
    }


    /**
     * 普通队列的死信消息 路由的队列
     * 普通队列simpleQueue的死信投递到死信交换机`dead-letter-exchange`后再投递到该队列
     * 用这个队列来接收simple-queue的死信消息
     *
     * @return
     */
    @Bean
    public Queue deadLetterQueue() {
        return QueueBuilder.durable("deadLetterQueue").build();
    }

    /**
     * 死信队列绑定死信交换机
     *
     * @param deadLetterQueue      simple-queue对应的死信队列
     * @param deadLetterExchange 通用死信交换机
     * @return
     */
    @Bean
    public Binding deadLetterBinding(Queue deadLetterQueue, Exchange deadLetterExchange) {
        return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with("dead-letter-routing-key").noargs();
    }
}
定义消费者
@Component
public class Consumer {

    @RabbitHandler
    @RabbitListener(queues = "simpleQueue")
    public void simpleQueue(String msg, Message message, Channel channel) throws IOException {
        //拒收消息:消息的DeliveryTag,是否批量拒绝,是否重新入队
        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
        System.out.println("Consumer.simpleQueue 普通队列拒收消息 内容:" + msg);
    }

    @RabbitHandler
    @RabbitListener(queues = "deadLetterQueue")
    public void deadLetterQueue(String msg, Message message, Channel channel) throws IOException {
        System.out.println("Consumer.deadLetterQueue 接收到死信消息: " +  msg);
        System.out.println("Consumer.deadLetterQueue 死信队列收到消息....消息路由键为:" + message.getMessageProperties().getReceivedRoutingKey());
    }
}
测试
@Test
public void delMessage(){
  	// 正常发送消息
    rabbitTemplate.convertAndSend("simpleExchange", "simple","死信队列测试!");
}
结果

在这里插入图片描述

我们发送正常消息的路由键为“simple”,拒收后发送到死信交换机的路由键为“dead-letter-routing-key”。

死信队列可用场景很多,例如设置队列超时时间,一旦超过该时间,就丢到死信队列处理,可用于下单多少时间内不付款,自动取消订单。

求个关注~~~

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值