Rabbitmq入门

MQ

1. MQ引言

1.1 什么是MQ

MQ(Message Queue):翻译为消息队列,通过典型的生产者消费者模型,生产者不断向消息队列中生产消息,消费者不断从队列获取消息,因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,轻松的实现系统之间解耦,别名为消息中间件,通过利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。

1.2 MQ有哪些

当今市面上有很多主流的消息中间件,如老牌的ActiveMQ、RabbitMQ、炽手可热的kafka、阿里巴巴自主研发的RocketMQ等

1.3 不同MQ特点

# 1.ActiveMQ
		ActiveMQ是Apache出品,最流行的、能力强劲的开源消息总线,它是一个完全支持JMS规范的消息中间件,丰富的API多种集群架构模式让ActiveMQ在业界成为老牌的消息中间件,在中小型企业颇受欢迎。
		
# 2.Kafka
		kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pull的模式来处理消息消费,准求高吞吐量,一开始的目的就是用于日志收集和传输。0.8版本开始支持复制,不支持事物,对消息的重复、丢失、错误没有严格要求。

# 3.RocketMQ
		RocketMQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式应用的特点,RockerMQ思路起源于Kafka,但并不是Kafka的一个Copy, 它对消息的可靠传输及事物特性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。

# 4.RabbitMQ
		RabbitMQ是使用Erlang语音开发的开源消息队列系统,基于AMQP协议来实现,AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。

RabbitMQ比kafka可靠,kafka更适合IO高吞吐的处理,一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用,比如ELK日志收集

2. MQ实战

2.1 第一种模型(直连)

image-20220117172553709

说明:

  • P:生产者,也就是要发送消息的程序

  • C:消费者,消息的接受者,会一直等待消息的到来

  • queue: 消息队列,图中红色部分,类似一个邮箱,可以缓存消息;生产者可以向其中投递消息,消费者从其中取出消息

引入依赖

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

开发生产者

public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException {
        batchProducer();
    }

    static void batchProducer() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");

        // 连接对象
        Connection connection = connectionFactory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();
        // 通道对应的队列,不存在自动创建 | String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare("direct", false, false ,false, null);

        // 发布消息 参数1:交换机名称 参数2:队列名称 | String exchange, String routingKey, BasicProperties props, byte[] body
        channel.basicPublish("", "direct", null, ("你好,我是消息").getBytes());


        channel.close();
        connection.close();
    }
}

开发消费者

// 消费者
public class Consumer1 {
    // 消费者
    public static void main(String[] args) throws IOException, TimeoutException {
        amqpConsumer();
    }

    static void amqpConsumer() throws IOException, TimeoutException {
      ConnectionFactory connectionFactory = new ConnectionFactory();
      connectionFactory.setHost("localhost");
      connectionFactory.setPort(5672);
      connectionFactory.setUsername("guest");
      connectionFactory.setPassword("guest");
      connectionFactory.setVirtualHost("/");

      // 连接对象
      Connection connection = connectionFactory.newConnection();

      // 创建通道
      Channel channel = connection.createChannel();
      // 通道对应的队列,不存在自动创建 | String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
      channel.queueDeclare("direct", false, false ,false, null);

      // 消费消息 参数2:true自动确认消息 false手动确认消息 | String queue, boolean autoAck, Consumer callback
      channel.basicConsume("direct", false, new DefaultConsumer(channel) {
        @Override // 参数body是从消息队列取出的消息
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
          System.out.println("body = " + new String(body));

          //参数1:要确认队列中哪个消息(消息标志)  参数2:是否同时开启多个参数确认
          channel.basicAck(envelope.getDeliveryTag(), false);
        }
      });

      // channel.close();
      // connection.close();
    }
}

2.2 第二种模型(work queue)

Work queues,也被称为(Toask queues),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息消费的速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。

image-20220117173459392

说明:

  • P:生产者:任务的发布者
  • C1:消费者,领取任务并且完成任务,假设完成速度较慢
  • C2:消费者2,领取任务并且完成任务,假设完成速度快

开发生产者(批量生产消息)

public static void main(String[] args) throws IOException, TimeoutException {
        batchProducer();
}

static void batchProducer() throws IOException, TimeoutException {
  ConnectionFactory connectionFactory = new ConnectionFactory();
  connectionFactory.setHost("localhost");
  connectionFactory.setPort(5672);
  connectionFactory.setUsername("guest");
  connectionFactory.setPassword("guest");
  connectionFactory.setVirtualHost("/");

  // 连接对象
  Connection connection = connectionFactory.newConnection();

  // 创建通道
  Channel channel = connection.createChannel();
  // 通道对应的队列,不存在自动创建
  channel.queueDeclare("hello", false, false ,false, null);

  // 发布消息
  for (int i=1;i<=20;i++) {
    channel.basicPublish("", "work", null, ("你好,我是消息" + i).getBytes());
  }

  channel.close();
  connection.close();
}

开发消费者1和消费者2

public class Consumer2 {
  // 消费者
  public static void main(String[] args) throws IOException, TimeoutException {
    amqpConsumer();
  }

  static void amqpConsumer() throws IOException, TimeoutException {
    ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost("localhost");
    connectionFactory.setPort(5672);
    connectionFactory.setUsername("guest");
    connectionFactory.setPassword("guest");
    connectionFactory.setVirtualHost("/");

    // 连接对象
    Connection connection = connectionFactory.newConnection();

    // 创建通道
    Channel channel = connection.createChannel();
    // 每次只能消费一个消息
    channel.basicQos(1);
    // 通道对应的队列,不存在自动创建
    channel.queueDeclare("work", false, false ,false, null);

    // 消费消息 参数2:true自动确认消息 false手动确认消息
    channel.basicConsume("work", false, new DefaultConsumer(channel) {
      @Override // 参数body是从消息队列取出的消息
      public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        try {
          Thread.sleep(2000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("body = " + new String(body));

        //参数1:要确认队列中哪个消息(消息标志)  参数2:是否同时开启多个参数确认
        channel.basicAck(envelope.getDeliveryTag(), false);
      }
    });

    // channel.close();
    // connection.close();
  }
}

2.3 第三种模型(fanout)

fanout 扇出 也称为广播

image-20220117191158403

在广播模式下,消息发送流程是这样的:

  • 可以有多个消费者
  • 每个消费者有自己的queue(队列)
  • 每个队列都要绑定到Exchange(交换机)
  • 生产者发送的消息,只能发送到交换机,交换机决定发动到哪个队列,生产者无法决定
  • 交换机把消息发送给绑定过的所有队列
  • 队列的消费者能拿到消息。实现一条消息被多个消费者消费

开发生产者

public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException {
        batchProducer();
    }

    static void batchProducer() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");

        // 连接对象
        Connection connection = connectionFactory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();

        // 声明交换机 参数1:交换机名称 参数2:交换机类型 fanout广播类型
        channel.exchangeDeclare("logs", "fanout");

        // 发布消息
        for (int i=1;i<=20;i++) {
            // String exchange, String routingKey, BasicProperties props, byte[] body
            channel.basicPublish("logs", "", null, ("你好,我是fanout消息" + i).getBytes());
        }


        channel.close();
        connection.close();
    }
}

开发消费者1和消费者2

public class Consumer1 {
    // 消费者
    public static void main(String[] args) throws IOException, TimeoutException {
        amqpConsumer();
    }

    static void amqpConsumer() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");

        // 连接对象
        Connection connection = connectionFactory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();
        // 每次只能消费一个消息
        channel.basicQos(1);
        // 通道绑定交换机 | String exchange, String type
        channel.exchangeDeclare("logs", "fanout");
        // 声明临时队列
        String queueName = channel.queueDeclare().getQueue();

        // 绑定交换机和队列 | String queue, String exchange, String routingKey
        channel.queueBind(queueName, "logs", "");


        // 消费消息 参数2:true自动确认消息 false手动确认消息 | String queue, boolean autoAck, Consumer callback
        channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
            @Override // 参数body是从消息队列取出的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body = " + new String(body));

                //参数1:要确认队列中哪个消息(消息标志)  参数2:是否同时开启多个参数确认 | long deliveryTag, boolean multiple
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });

        // channel.close();
        // connection.close();
    }
}

2.4 第四种模型(Routing)

2.4.1 Routing之订阅模型-Direct(直连)

在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就需要用到Direct类型的Exchange。

在Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)

  • 消息的发送方在向Exchange发送消息时,也必须指定消息的RoutingKey

  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的RoutingKey进行判断,只有队列的RoutingKey与消息的RoutingKey完全一致,才会接收到消息

流程:

image-20220118104059291

图解:

  • P:生产者,向Exchange发送消息,发送消息时,会指定一个routingKey。

  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给与routingKey完全匹配的队列

  • C1:消费者,其所在队列指定了需要routingKey为error的消息

  • C2:消费者,其所在队列指定了需要routingKey为info、error、warning

开发生产者

public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException {
        batchProducer();
    }

    static void batchProducer() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");

        // 连接对象
        Connection connection = connectionFactory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();

        // 声明交换机 参数1:交换机名称 参数2:交换机类型 路由模式
        channel.exchangeDeclare("logs_direct", "direct");

        // 发布消息
        String routingKey = "info";
        for (int i=1;i<=20;i++) {
            // String exchange, String routingKey, BasicProperties props, byte[] body
            channel.basicPublish("logs_direct", routingKey, null, ("我是direct模型发布的基于routingKey:"+ routingKey+"发送的消息" + i).getBytes());
        }


        channel.close();
        connection.close();
    }
}

开发消费者1

消费者1只能接收路由key为error的消息

public class Consumer1 {
    // 消费者
    public static void main(String[] args) throws IOException, TimeoutException {
        amqpConsumer();
    }

    static void amqpConsumer() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");

        // 连接对象
        Connection connection = connectionFactory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();
        // 每次只能消费一个消息
        channel.basicQos(1);
        // 声明交换机 | String exchange, String type
        channel.exchangeDeclare("logs_direct", "direct");
        // 声明临时队列
        String queueName = channel.queueDeclare().getQueue();

        // 绑定交换机和队列 | String queue, String exchange, String routingKey
        channel.queueBind(queueName, "logs_direct", "error");


        // 消费消息 参数2:true自动确认消息 false手动确认消息 | String queue, boolean autoAck, Consumer callback
        channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
            @Override // 参数body是从消息队列取出的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body = " + new String(body));

                //参数1:要确认队列中哪个消息(消息标志)  参数2:是否同时开启多个参数确认 | long deliveryTag, boolean multiple
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });

        // channel.close();
        // connection.close();
    }
}

开发消费者2

消费者2能接收路由key为info、warning、error的消息

public class Consumer2 {
    // 消费者
    public static void main(String[] args) throws IOException, TimeoutException {
        amqpConsumer();
    }

    static void amqpConsumer() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");

        // 连接对象
        Connection connection = connectionFactory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();
        // 每次只能消费一个消息
        channel.basicQos(1);
        // 声明交换机 | String exchange, String type
        channel.exchangeDeclare("logs_direct", "direct");
        // 声明临时队列
        String queueName = channel.queueDeclare().getQueue();

        // 绑定交换机和队列 | String queue, String exchange, String routingKey
        channel.queueBind(queueName, "logs_direct", "info");
        channel.queueBind(queueName, "logs_direct", "warning");
        channel.queueBind(queueName, "logs_direct", "error");


        // 消费消息 参数2:true自动确认消息 false手动确认消息 | String queue, boolean autoAck, Consumer callback
        channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
            @Override // 参数body是从消息队列取出的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body = " + new String(body));

                //参数1:要确认队列中哪个消息(消息标志)  参数2:是否同时开启多个参数确认 | long deliveryTag, boolean multiple
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });

        // channel.close();
        // connection.close();
    }
}
2.4.1 Routing之订阅模型-Topic

Topic类型的 ExchangeDirect相比,都是可以根据 RoutingKey把消息路由到不同的队列。只不过 Topic类型的 Exchange可以让队列在绑定 RoutingKey的时候使用通配符!这种模型 Routingkey一般都是由一个或多个单词组成,多个单词以"."分隔,例如: item.insert

image-20220118144046584
# 通配符
	*(star) 匹配不多不少恰好一个词
	# 匹配一个或多个词
# 如:
	audit.# 匹配audit.irs.corporate或者 audit.irs等
	audit.* 只能匹配audit.irs

开发生产者

public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException {
        batchProducer();
    }

    static void batchProducer() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");

        // 连接对象
        Connection connection = connectionFactory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();

        // 声明交换机 参数1:交换机名称 参数2:交换机类型 路由模式
        channel.exchangeDeclare("topics", "topic");

        // 发布消息
        String routingKey = "user.save.delete";
        for (int i=1;i<=20;i++) {
            // String exchange, String routingKey, BasicProperties props, byte[] body
            channel.basicPublish("topics", routingKey, null, ("我是topic动态路由模型发布的基于routingKey:"+ routingKey+"发送的消息" + i).getBytes());
        }


        channel.close();
        connection.close();
    }
}

开发消费者1

public class Consumer1 {
    // 消费者
    public static void main(String[] args) throws IOException, TimeoutException {
        amqpConsumer();
    }

    static void amqpConsumer() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");

        // 连接对象
        Connection connection = connectionFactory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();
        // 每次只能消费一个消息
        channel.basicQos(1);
        // 声明交换机 | String exchange, String type
        channel.exchangeDeclare("topics", "topic");
        // 声明临时队列
        String queueName = channel.queueDeclare().getQueue();

        // 绑定交换机和队列 | String queue, String exchange, String routingKey
        channel.queueBind(queueName, "topics", "user.*");


        // 消费消息 参数2:true自动确认消息 false手动确认消息 | String queue, boolean autoAck, Consumer callback
        channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
            @Override // 参数body是从消息队列取出的消息
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body = " + new String(body));

                //参数1:要确认队列中哪个消息(消息标志)  参数2:是否同时开启多个参数确认 | long deliveryTag, boolean multiple
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });

        // channel.close();
        // connection.close();
    }
}

开发消费者2

public class Consumer2 {
    // 消费者
    public static void main(String[] args) throws IOException, TimeoutException {
        amqpConsumer();
    }

    static void amqpConsumer() throws IOException, TimeoutException {
        {
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("localhost");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("guest");
            connectionFactory.setPassword("guest");
            connectionFactory.setVirtualHost("/");

            // 连接对象
            Connection connection = connectionFactory.newConnection();

            // 创建通道
            Channel channel = connection.createChannel();
            // 每次只能消费一个消息
            channel.basicQos(1);
            // 声明交换机 | String exchange, String type
            channel.exchangeDeclare("topics", "topic");
            // 声明临时队列
            String queueName = channel.queueDeclare().getQueue();

            // 绑定交换机和队列 | String queue, String exchange, String routingKey
            channel.queueBind(queueName, "topics", "user.#");


            // 消费消息 参数2:true自动确认消息 false手动确认消息 | String queue, boolean autoAck, Consumer callback
            channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
                @Override // 参数body是从消息队列取出的消息
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    System.out.println("body = " + new String(body));

                    //参数1:要确认队列中哪个消息(消息标志)  参数2:是否同时开启多个参数确认 | long deliveryTag, boolean multiple
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            });

            // channel.close();
            // connection.close();
        }
    }
}

3.Springboot整合RabbitMq

3.1 pom.xml

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

3.2 hello例子

@SpringBootTest(classes = AmqpDemoApplication.class)
public class MqBoot {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // hello
    @Test
    public void hello() {
        rabbitTemplate.convertAndSend("hello", "你好rabbitmq");
    }
}

3.3 work模型

测试类

@SpringBootTest(classes = AmqpDemoApplication.class)
public class MqBoot {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // worker模型,默认均衡消费
    @Test
    public void worker() {
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("worker", "worker模型"+i );
        }
    }
}

WorkListener消费者

@Component
public class WorkListener {

    @RabbitListener(queuesToDeclare = @Queue(value = "worker"))
    public void worker1(String msg) {
        System.out.println("worker1 = " + msg);
    }

    @RabbitListener(queuesToDeclare = @Queue(value = "worker"))
    public void worker2(String msg) {
        System.out.println("worker2 = " + msg);
    }
}

3.4 fanout广播模型

测试类

@SpringBootTest(classes = AmqpDemoApplication.class)
public class MqBoot {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 广播fanout模式
    @Test
    public void fanout() {
        rabbitTemplate.convertAndSend("logs", "", "fanout模型");
    }
}

FanoutListener消费者

@Component
public class FanoutListener {

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue, // 创建临时队列
                    exchange = @Exchange(value = "logs", type = "fanout") // 绑定交换机
            )
    })
    public void fanout1(String msg) {
        System.out.println("fanout1 = " + msg);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue, // 创建临时队列
                    exchange = @Exchange(value = "logs", type = "fanout") // 绑定交换机
            )
    })
    public void fanout2(String msg) {
        System.out.println("fanout2 = " + msg);
    }

}

3.5 topic订阅模型

测试类

@SpringBootTest(classes = AmqpDemoApplication.class)
public class MqBoot {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 订阅topic模式 动态路由
    @Test
    public void topic() {
        rabbitTemplate.convertAndSend("topics", "product.aa.bb.cc", "topic模型");
    }
}

TopicListener消费者

@Component
public class TopicListener {

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue, // 创建临时队列
                    exchange = @Exchange(value = "topics", type = "topic"), // 绑定交换机
                    key = {"user.save", "user.*"}
            )
    })
    public void topic1(String msg) {
        System.out.println("topic1 = " + msg);
    }

    // *匹配一个,#匹配一个或多个
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue, // 创建临时队列
                    exchange = @Exchange(value = "topics", type = "topic"), // 绑定交换机
                    key = {"order.#", "product.#", "user.*"}
            )
    })
    public void topic2(String msg) {
        System.out.println("topic2 = " + msg);
    }

}

4.Mq应用场景

4.1 异步处理

场景 :用户注册后,一般会发短信验证码和注册邮件,传统做法有两种:1.串行方式 2.并行方式

  1. 串行方式

    将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。这有一个问题是,邮件,短信并不是必须的,它只是一个通知,其实客户端没有必要等待。

    image-20220208185726541

  2. 并行方式

    将注册信息写入数据库后,发送邮件的同时,发送短信,三个任务完成后,返回给客户端,并行的方式能提高处理时间。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wcy9Q5d3-1644851073611)(MQ.assets/image-20220208185913837.png)]

  3. 使用消息队列

    假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行方式使用时间100ms。虽然并行已经提高了处理时间,但是,前面说过,邮件和短信对我正常使用网站没有任何影响,客户端没有必要 等着其发送完成才显示注册成功,应该是写入数据库后就返回。

    使用消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理。

    image-20220208190504544

    由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),响应时间是串行的3倍,是并行的2倍。

4.2 应用解耦

场景 :双11购物节,用户下单后,订单系统需要通知库存系统,传统的做法是订单系统调用库存系统的接口。

image-20220209103021543

这种做法有一个缺点:

当库存系统出现故障时,订单就会失败。订单系统和库存系统高耦合。

引入消息队列:

image-20220209103309618

  • 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回给用户下单成功。
  • 库存系统:订阅下单的消息,获取下单消息,进行库操作。就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失。

4.3 流量消峰

场景 :秒杀活动,一般会因为流量过大,导致应用挂掉,一般在 应用前端加入消息队列

作用

  1. 可以控制活动人数,超过阈值的订单直接抛弃 (这也可能就是我秒杀一次没成功过的原因,哈哈哈)
  2. 可以缓解短时间的高流量压垮应用(应用程序按照自己的最大处理能力获取订单 )

image-20220208184929226

场景描述

  1. 用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过阈值,则直接抛弃用户请求或者跳转到错误页面
  2. 秒杀业务根据消息队列中的请求信息,再做后续处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值