RabbitMQ实用篇

RabbitMQ实用篇

RabbitMQ引入依赖

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

快速入门

1.publisher实现(出版者)

思路:

  • 建立连接
  • 创建Channel
  • 声明队列
  • 发送消息
  • 关闭连接和channel

代码实现:

package cn.alidada.mq.helloword;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;

@Slf4j
public class PublisherTest {

    /**
     * - 建立连接
     * - 创建Channel
     * - 声明队列
     * - 发送消息
     * - 关闭连接和channel
     */
    @Test
    public void testSendingMessages() throws Exception {

//        1.建立连接
        ConnectionFactory factory = new ConnectionFactory();

//        1.1. 设置连接参数,分别是,主机名、端口、虚拟主机、用户名、密码
        factory.setHost("192.168.150.101"); //主机名称
        factory.setPort(5672); //主机端口
        factory.setVirtualHost("/"); //虚拟主机
        factory.setUsername("alidada"); //用户名
        factory.setPassword("123321"); //密码
//        1.2 建立连接
        Connection connection = factory.newConnection();

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

//        3.声明队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

//        4.发送消息
        for (int i = 0; i < 50; i++) {
            Thread.sleep(2000);
            String message = "hello reabbitmq";
            channel.basicPublish("", queueName, null, message.getBytes(StandardCharsets.UTF_8));
            log.info("发送的消息成功:{}", message);
        }

//        5.关闭通道
        channel.close();
        connection.close();

    }
}

2.consumer实现(消费者)

代码思路:

  • 建立连接
  • 创建Channel
  • 声明队列
  • 订阅消息

代码实现:

package cn.alidada.mq.helloworld;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerTest {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、虚拟主机、用户名、密码
        factory.setHost("192.168.150.101");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("alidada");
        factory.setPassword("123321");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

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

        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.订阅消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 5.处理消息
                String message = new String(body);
                System.out.println("接收到消息:【" + message + "】");
            }
        });
        System.out.println("等待接收消息。。。。");
    }
}

SpringAMQP

SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。

SpringAmqp的官方地址:https://spring.io/projects/spring-amqp

AMQP: 是 Advanced Message Queuing Protocol 的缩写。其中字母代表的含义如下:

  • AMQP 表示高级消息队列协议,它是一种用于消息传递的协议标准。

  • A: Advanced(高级)

  • M: Message(消息)

  • Q: Queuing(队列)

  • P: Protocol(协议)

Spring AMQP : 是一个基于 Spring 框架的开源项目,它提供了在应用程序中使用 AMQP(高级消息队列协议)进行消息传递的支持。Spring AMQP 为开发人员提供了一个简单且强大的方式来与消息队列系统(如 RabbitMQ)进行交互。

  • 消息生产者和消费者:Spring AMQP 提供了用于创建消息生产者和消费者的 API,使开发人员能够方便地发送和接收消息。

  • 消息监听器容器:Spring AMQP 提供了消息监听器容器,可以自动监听和处理队列中的消息。开发人员只需编写一个消息监听器,然后将其注册到容器中,容器会负责从队列中获取消息并调用监听器进行处理。

  • 消息转换器:Spring AMQP 提供了消息转换器,用于将消息从一种格式转换为另一种格式。这样,开发人员可以使用不同的消息格式(如 JSON、XML 等)进行消息传递,而不需要手动进行转换。

  • 事务支持:Spring AMQP 支持事务,开发人员可以在发送和接收消息时使用事务,确保消息的原子性和一致性。

  • 异常处理:Spring AMQP 提供了异常处理机制,可以处理发送和接收消息时可能出现的异常情况,例如连接失败、消息发送失败等。

SpringAMQP提供了三个功能:

  • 自动声明队列、交换机及其绑定关系
  • 基于注解的监听器模式,异步接收消息
  • 封装了RabbitTemplate工具,用于发送消息

1.Basic Queue 基本队列模型

在父工程mq-demo中引入依赖

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

1.消息发送

首先配置MQ地址,在publisher服务的application.yml中添加配置:

spring:
  rabbitmq:
    host: 192.168.150.101 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: alidada # 用户名
    password: 123321 # 密码

1.1 测试类 RabbitTemplate实现消息发送:

package cn.alidada.mq.spring;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue() {
        // 队列名称
        String queueName = "simple.queue";
        // 消息
        String message = "hello, spring amqp!";
        // 发送消息
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

2.消息接收

首先配置MQ地址,在consumer服务的application.yml中添加配置:

spring:
  rabbitmq:
    host: 192.168.150.101 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: alidada # 用户名
    password: 123321 # 密码

2.1消息接受

package cn.alidada.mq.listener;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class SpringRabbitListener {

    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueueMessage(String msg) throws InterruptedException {
        System.out.println("spring 消费者接收到消息:【" + msg + "】");
    }
}

2.WorkQueue 任务队列模型

Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息

**WorkQueue(任务队列)**模型是 RabbitMQ 中的一种消息模型,用于在多个消费者之间共享和处理任务。它的工作原理如下:

  • 产者将任务消息发送到一个队列中。
  • 多个消费者同时监听该队列,竞争获取任务消息。
  • 每个任务消息只能被一个消费者接收和处理,确保每个任务只被处理一次。
  • 当有多个消费者时,任务消息会被平均分配给不同的消费者,实现负载均衡。
  • 消费者根据自身的处理能力和负载情况,自动调整并控制任务的处理速度。

任务队列模型常用于分布式系统中的任务处理,可以实现任务的并行处理、负载均衡和任务的持久化等功能。它可以提高系统的可靠性和扩展性,确保任务的可靠执行。

1. 能者多劳模式(负载均衡)

在spring中有一个简单的配置,可以解决这个问题。我们修改consumer服务的application.yml文件,添加配置:

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

3.Fanout 交换机(广播模式)

Fanout 是 RabbitMQ 中的一种消息交换机类型。消息交换机是 RabbitMQ 中用于接收消息并将其路由到一个或多个队列的组件。Fanout 交换机的工作方式如下:

  • Fanout 交换机接收到消息后,会将消息广播到与之绑定的所有队列,不考虑路由键或者绑定规则。

  • 无论队列是否有消费者,Fanout 交换机都会将消息发送到所有与之绑定的队列。

  • Fanout 交换机适用于广播模式,即一个消息需要被多个消费者同时接收和处理的场景。

  • 每个消费者都会独立地接收到相同的消息副本,消费者之间没有竞争关系。

  • Fanout 交换机通常用于发布/订阅模式,其中一个生产者将消息发送到 Fanout 交换机,然后多个消费者分别从不同的队列中接收消息进行处理。

1.创建交换器配置类

package cn.alidada.mq.fanout;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Fanoutconfig {

    /**
     * 声明交换机
     * @return Fanout 类型交换机
     * 接受出版者 生产的消息   传递给交换机
     */
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("alidada.fanout");
    }

    /**
     * 第一个队列
     * @return
     */
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }


    
     * 绑定队列和交换机
     * @param fanoutQueue1
     * @param fanoutExchange
     * @return
     */
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }


    /**
     * 第二队列
     * @return
     */
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }


    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }

}

2.消息发送

@Test
public void testFanoutExchange() {
    // 队列名称
    String exchangeName = "alidada.fanout";
    // 消息
    String message = "hello, everyone!";
    rabbitTemplate.convertAndSend(exchangeName, "", message);
}

3.消息接受

@RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1(String msg) {
        log.info("消费者1接收到Fanout消息:{}",msg);
    }

    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2(String msg) {
        log.info("消费者2接收到Fanout消息:{}",msg);
    }

4.Direct 直连交换机(direct exchange 路由模式)

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

在Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
  • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey
  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息

1.添加消费者

/**
     * value 添加消费者
     * exchange 添加交换机
     * key 条件
     */ 
@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "alidada.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue1(String msg) {
        log.info("消费者接收到direct.queue1的消息:{}", msg);
    }


    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"),
            exchange = @Exchange(name = "alidada.direct", type = ExchangeTypes.DIRECT),
            key = {"yellow", "red"}
    ))
    public void listenDirectQueue2(String msg) {
        log.info("消费者接收到direct.queue2的消息:{}", msg);
    }

2.添加发送

@Test
public void testSendDirectExchange() {
    // 交换机名称
    String exchangeName = "alidada.direct";
    // 消息
    String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
    // 发送消息
    rabbitTemplate.convertAndSend(exchangeName, "red", message);
}

5.Topic 路由模式加强版

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!

Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

通配符规则:

#:匹配一个或多个词

*:匹配不多不少恰好1个词

  • 举例:

  • item.#:能够匹配item.spu.insert 或者 item.spu

  • item.*:只能匹配item.spu

提问:

描述下Direct交换机与Topic交换机的差异?

  • Topic交换机接收的消息RoutingKey必须是多个单词,以 **.** 分割
  • Topic交换机与队列绑定时的bindingKey可以指定通配符
  • #:代表0个或多个词
  • *:代表1个词

1.消息发送

/**
     * topicExchange
     */
@Test
public void testSendTopicExchange() {
    // 交换机名称
    String exchangeName = "alidada.topic";
    // 消息
    String message = "喜报!孙悟空大战哥斯拉,胜!";
    // 发送消息
    rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}

2.消息接受

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue1"),
    exchange = @Exchange(name = "alidada.topic", type = ExchangeTypes.TOPIC),
    key = "china.#"
))
public void listenTopicQueue1(String msg){
    System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue2"),
    exchange = @Exchange(name = "alidada.topic", type = ExchangeTypes.TOPIC),
    key = "#.news"
))
public void listenTopicQueue2(String msg){
    System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
}

6.配置JSON转换器

显然,JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。

在publisher和consumer两个服务中都引入依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>

配置消息转换器。

在启动类中添加一个Bean即可:

@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值