🍔RabbitMQ学习

课程地址:

  【【编程不良人】MQ消息中间件之RabbitMQ以及整合SpringBoot2.x实战教程,已完结!】

  https://www.bilibili.com/video/BV1dE411K7MG/?share_source=copy_web&vd_source=045f1e1c5f0609a50870e499d44940af

测试代码:

  https://github.com/zhangzhixi0305/RabbitMQ-Study#

一、MQ引言

1.1 什么是MQ

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

1.2 MQ有哪些

  当今市面上有很多主流的消息中间件,如老牌的ActiveMQRabbitMQ,炙手可热的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开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起
		源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消
		息推送、日志流式处理、binglog分发等场景。

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

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

二、RabbitMQ 的引言

官网: https://www.rabbitmq.com/

官方教程: https://www.rabbitmq.com/#getstarted

2.1 RabbitMQ

  基于AMQP协议,erlang语言开发,是部署最广泛的开源消息中间件,是最受欢迎的开源消息中间件之一。

 AMQP协议

AMQP(advanced message queuing protocol)`在2003年时被提出,最早用于解决金融领不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。以下是AMQP协议模型:

2.2、RabbitMQ的安装

 官网下载地址:https://www.rabbitmq.com/download.html

 

1、下载安装包

链接: https://pan.baidu.com/s/19y9aIzkqzsYzwt7jwk29dw?pwd=rgjg

2、安装步骤

# 1.将rabbitmq安装包上传到linux系统中,这里我上传到了:/usr/local/rabbitmq下
	erlang-22.0.7-1.el7.x86_64.rpm
	rabbitmq-server-3.7.18-1.el7.noarch.rpm

# 2.安装Erlang依赖包
	rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
	rpm -ivh socat-1.7.3.2-2.el7.x86_64.rpm
# 3.安装RabbitMQ安装包
	rpm -ivh rabbitmq-server-3.7.18-1.el7.noarch.rpm
		
# 4.复制配置文件
	默认安装完成后配置文件模板在:/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example目录中
	需要将配置文件复制到/etc/rabbitmq/目录中,并修改名称为rabbitmq.config
	cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config

# 5.查看配置文件位置
	ls /etc/rabbitmq/rabbitmq.config

# 6.修改配置文件(参见下图:)
	vim /etc/rabbitmq/rabbitmq.config 

 

# 7.执行如下命令,启动rabbitmq中的插件管理
	rabbitmq-plugins enable rabbitmq_management
	
	出现如下说明:
		Enabling plugins on node rabbit@localhost:
    rabbitmq_management
    The following plugins have been configured:
      rabbitmq_management
      rabbitmq_management_agent
      rabbitmq_web_dispatch
    Applying plugin configuration to rabbit@localhost...
    The following plugins have been enabled:
      rabbitmq_management
      rabbitmq_management_agent
      rabbitmq_web_dispatch

    set 3 plugins.
    Offline change; changes will take effect at broker restart.

# 8.启动RabbitMQ的服务
	systemctl start rabbitmq-server
	systemctl restart rabbitmq-server
	systemctl stop rabbitmq-server
	

# 9.查看服务状态(见下图:)
	systemctl status rabbitmq-server
  ● rabbitmq-server.service - RabbitMQ broker
     Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
     Active: active (running) since 三 2019-09-25 22:26:35 CST; 7s ago
   Main PID: 2904 (beam.smp)
     Status: "Initialized"
     CGroup: /system.slice/rabbitmq-server.service
             ├─2904 /usr/lib64/erlang/erts-10.4.4/bin/beam.smp -W w -A 64 -MBas ageffcbf -MHas ageffcbf -
             MBlmbcs...
             ├─3220 erl_child_setup 32768
             ├─3243 inet_gethost 4
             └─3244 inet_gethost 4
      .........

3、访问RabbitMQ-WEB管理界面:ip:15672

如果是阿里云腾讯云这些,要开放安全组端口:

放行端口:[25672]	RabbitMQ-集群端口
放行端口:[5672]		RabbitMQ-TCP通信端口
放行端口:[15672]	RabbitMQ-web管理页面 

如果是本地虚拟机,关闭防火墙就好了

systemctl disable firewalld

三、RabbitMQ配置 

 3.1、RabbitMQ管理命令行

# 1.服务启动相关
	systemctl start|restart|stop|status rabbitmq-server

# 2.管理命令行  用来在不使用web管理界面情况下命令操作RabbitMQ
	rabbitmqctl  help  可以查看更多命令

# 3.插件管理命令行
	rabbitmq-plugins enable|list|disable 

3.2、WEB管理界面介绍

 

  • connections:无论生产者还是消费者,都需要与RabbitMQ建立连接后才可以完成消息的生产和消费,在这里可以查看连接情况

  • channels:通道,建立连接后,会形成通道,消息的投递获取依赖通道。

  • Exchanges:交换机,用来实现消息的路由

  • Queues:队列,即消息队列,消息存放在队列中,等待消费,消费后被移除队列。

3.3、Admin用户和虚拟主机管理 

1、添加用户

2、创建虚拟主机

虚拟主机:为了让各个用户可以互不干扰的工作,RabbitMQ添加了虚拟主机(Virtual Hosts)的概念。其实就是一个独立的访问路径,不同用户使用不同路径,各自有自己的队列、交换机,互相不会影响。

相当于MYSQL中数据库的概念吧,指定用户可以使用这个数据库(虚拟主机)

3、虚拟主机绑定用户

四、RabbitMQ程序案例

代码地址:https://github.com/zhangzhixi0305/RabbitMQ-Study

4.1、AMQP协议

4.2、RabbitMQ支持的消息模型

https://www.rabbitmq.com/getstarted.html

4.3、引入依赖

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

MQ工具类:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

@SuppressWarnings("all")
public class RabbitMQUtils {

    private static final ConnectionFactory connectionFactory;

    //重量级资源,类加载执行之执行一次
    static {
        // 1、创建连接mq的连接工厂对象
        connectionFactory = new ConnectionFactory();
        // 2、设置连接rabbitmq主机
        connectionFactory.setHost("192.168.31.73");
        // 3、设置端口号
        connectionFactory.setPort(5672);
        // 4、设置用户名和密码
        connectionFactory.setUsername("zhixi");
        connectionFactory.setPassword("zhixi158");
        // 5、设置虚拟主机
        connectionFactory.setVirtualHost("/ems");

    }

    /**
     * 定义提供连接对象的方法
     *
     * @return Connection
     */
    public static Connection getConnection() {
        try {
            return connectionFactory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 关闭通道和关闭连接工具方法
     *
     * @param channel 通道
     * @param conn    连接
     */
    public static void closeConnectionAndChanel(Channel channel, Connection conn) {
        try {
            if (channel != null) channel.close();
            if (conn != null) conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.4、第一种模型(直连)

生产者

/**
 * 生产消息-HelloWorld
 */
@Test
public void testSenMessage() throws IOException, TimeoutException {
    // 1、创建连接工厂
    Connection connection = RabbitMQUtils.getConnection();
    // 2、创建通道
    assert connection != null;
    Channel channel = connection.createChannel();
    // 3、通道绑定对应消息队列
    //'参数1':用来声明通道对应的队列
    //'参数2':用来指定是否持久化队列
    //'参数3':用来指定是否独占队列,true:独占 false:不独占
    //'参数4':用来指定是否在消费者消费完成后自动删除队列,true:自动删除 false:不自动删除
    //'参数5':对队列的额外配置
    channel.queueDeclare("hello", true, false, false, null);
    // 4、发布消息
    // 参数1:交换机名称
    // 参数2:路由键
    // 参数3:消息的其他属性 - 路由标头等。MessageProperties.PERSISTENT_TEXT_PLAIN:设置消息的持久化
    // 参数3:消息体
    //channel.basicPublish("", "hello", null, "hello rabbitmq".getBytes());
    channel.basicPublish("", "hello", MessageProperties.PERSISTENT_TEXT_PLAIN, "hello rabbitmq".getBytes());
    RabbitMQUtils.closeConnectionAndChanel(channel, connection);
}  

消费者

public class ConsumerMessage {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        // 2、创建通道
        Channel channel = connection.createChannel();
        // 3、通道绑定对应消息队列
        channel.queueDeclare("hello", true, false, false, null);
        // 4、消费消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, StandardCharsets.UTF_8);
                System.out.println("消费消息:" + message);
            }
        };
        // 5、消费消息(自动提交,消息拿到了,不管这边有没有消费完毕,MQ那边的消息已经没有了)
        channel.basicConsume("hello", true, consumer);
        // channel.close();
        // connection.close();
    }
}  

测试

生产者运行,会在MQ创建一个消息队列,并放入一条消息

运行消费者,就可以看到已经提交的消息

 或者可以通过WEB界面,也是可以消费消息的:

 4.5、第二种模型(work quene)

  Work queues,也被称为(Task queues),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。

长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:

  让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。

角色:

  • P:生产者:任务的发布者

  • C1:消费者-1,领取任务并且完成任务,假设完成速度较慢

  • C2:消费者-2:领取任务并完成任务,假设完成速度快

生产者

/**
 * 生产消息-WorkQueue
 */
@Test
public void testWorkQueue() throws IOException {
    // 1、创建连接工厂
    Connection connection = RabbitMQUtils.getConnection();
    // 2、创建通道
    Channel channel = connection.createChannel();
    channel.queueDeclare("work", true, false, false, null);
    for (int i = 1; i <= 10; i++) {
        channel.basicPublish("", "work", null, (i + "-hello rabbitmq").getBytes());
    }
    RabbitMQUtils.closeConnectionAndChanel(channel, connection);
}

消费者1

import com.rabbitmq.client.*;
import com.zhixi.util.RabbitMQUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName Consumber1
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-27 18:08
 * @Version 1.0
 */
@SuppressWarnings("all")
public class Consumber1 {
    public static void main(String[] args) throws IOException {
        // 1、创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        // 2、创建通道
        final Channel channel = connection.createChannel();
        channel.queueDeclare("work", true, false, false, null);
        channel.basicQos(1);//一次只接受一条未确认的消息
        // 4、消费消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String message = new String(body, StandardCharsets.UTF_8);
                System.out.println("消费者1,消费消息:" + message);
                // 参数1:确认队列中哪个具体消息
                // 参数2:是否开启多个消息同时确认(false:手动确认消息)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 5、消费消息(关闭自动提交)
        channel.basicConsume("work", false, consumer);
    }
}

消费者2

import com.rabbitmq.client.*;
import com.zhixi.util.RabbitMQUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * @ClassName Consumber1
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-27 18:08
 * @Version 1.0
 */
@SuppressWarnings("all")
public class Consumber2 {
    public static void main(String[] args) throws IOException {
        // 1、创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        // 2、创建通道
        final Channel channel = connection.createChannel();
        channel.queueDeclare("work", true, false, false, null);
        channel.basicQos(1);//一次只接受一条未确认的消息
        // 4、消费消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, StandardCharsets.UTF_8);
                System.out.println("消费者2,消费消息:" + message);
                channel.basicAck(envelope.getDeliveryTag(), false);//手动确认消息
            }
        };
        // 5、消费消息(关闭自动提交)
        channel.basicConsume("work", false, consumer);
    }
}

 测试

总结:

  默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。

4.6、第三种模型(fanout)

扇出,也称为广播

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

  • 可以有多个消费者

  • 每个消费者有自己的queue(队列)

  • 每个队列都要绑定到Exchange(交换机)

  • 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。

  • 交换机把消息发送给绑定过的所有队列

  • 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

生产者

/**
 * 生产消息-扇出/广播
 */
@Test
public void testFanout() throws IOException {
    // 1、获取连接对象
    Connection connection = RabbitMQUtils.getConnection();
    // 2、创建通道
    assert connection != null;
    Channel channel = connection.createChannel();
    // 3、设置交换机的名称与交换机的类型。
    // 交换机不存在会自动创建,fanout表示广播。
    channel.exchangeDeclare("logs", "fanout");
    // 4、发送消息
    // 交换机名称、路由键、消息持久化、消息体
    channel.basicPublish("logs", "", null, "fanout type message".getBytes());
    // 5、释放资源
    RabbitMQUtils.closeConnectionAndChanel(channel, connection);
}

 消费者1

import com.rabbitmq.client.*;
import com.zhixi.util.RabbitMQUtils;

import java.io.IOException;

/**
 * @ClassName ConsumberFanout1
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-28 10:33
 * @Version 1.0
 */
@SuppressWarnings("all")
public class ConsumberFanout1 {
    public static void main(String[] args) throws IOException {
        // 1、创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        // 2、创建通道
        assert connection != null;
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("logs", "fanout");

        // 3、获取临时队列名称
        String queueName = channel.queueDeclare().getQueue();
        // 4、绑定交换机和队列
        // 队列名称、交换机名称、路由键
        channel.queueBind(queueName, "logs", "");
        // 5、消费消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1:" + new String(body));
            }
        });
    }
}

消费者2

import com.rabbitmq.client.*;
import com.zhixi.util.RabbitMQUtils;

import java.io.IOException;

/**
 * @ClassName ConsumberFanout1
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-28 10:33
 * @Version 1.0
 */
@SuppressWarnings("all")
public class ConsumberFanout2 {
    public static void main(String[] args) throws IOException {
        // 1、创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        // 2、创建通道
        assert connection != null;
        Channel channel = connection.createChannel();
        // 3、获取临时队列名称
        String queueName = channel.queueDeclare().getQueue();
        channel.exchangeDeclare("logs", "fanout");

        // 4、绑定交换机和队列
        // 队列名称、交换机名称、路由键
        channel.queueBind(queueName, "logs", "");
        // 5、消费消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2:" + new String(body));
            }
        });
    }
}

测试:先开启两个消费者,再开启生产者

4.7、第四种模型(Routing)

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

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

在Direct模型下:

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

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

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

图解:

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

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

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

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

生产者

/**
 * 生产消息-路由-直连
 */
@Test
public void testRoutingDirect() throws IOException {
    // 1、获取连接对象
    Connection connection = RabbitMQUtils.getConnection();
    // 2、创建通道
    assert connection != null;
    Channel channel = connection.createChannel();
    // 3、通过通道声明交换机,设置交换机的名称与交换机的类型。
    // 交换机不存在会自动创建,fanout表示广播。
    channel.exchangeDeclare("logs_direct", "direct");
    // 4、路由key
    String routingKey = "error";
    // 5、发送消息
    // 交换机名称、路由键、持久化、消息体
    channel.basicPublish("logs_direct", routingKey, null, ("基于路由方式的直连消息发送,路由键是【" + routingKey + "】发送的消息").getBytes());
    // 5、释放资源
    RabbitMQUtils.closeConnectionAndChanel(channel, connection);
}

消费者1:指定路由键为info

import com.rabbitmq.client.*;
import com.zhixi.util.RabbitMQUtils;

import java.io.IOException;

/**
 * @ClassName ConsumberDirect1
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-28 11:16
 * @Version 1.0
 */
@SuppressWarnings("all")
public class ConsumberDirect1 {
    /**
     * 交换机名称
     */
    private static final String exchange = "logs_direct";

    public static void main(String[] args) throws IOException {
        // 1、创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        // 2、创建通道
        assert connection != null;
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(exchange, "direct");
        // 3、获取临时队列名称
        String queueName = channel.queueDeclare().getQueue();
        // 4、绑定交换机和队列
        // 队列名称、交换机名称、路由键
        channel.queueBind(queueName, exchange, "info");
        // 5、消费消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1中的消息:" + new String(body));
            }
        });
    }
}

消费者2:指定路由键为error 

// 4、绑定交换机和队列
// 队列名称、交换机名称、路由键
channel.queueBind(queueName, exchange, "error");

消费者3:指定路由键为info、warning、error

// 4、绑定交换机和队列
// 队列名称、交换机名称、路由键
channel.queueBind(queueName, exchange, "info");
channel.queueBind(queueName, exchange, "error");
channel.queueBind(queueName, exchange, "warning");

测试:开启三个消费者,再开启生产者

发送error消息,只有特定的消费者能够接收到

4.7.2、Routing 之订阅模型-Topic(动态路由)

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。

只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!

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

# 通配符
  * (star)  匹配不多不少恰好1个词
  # (hash) 匹配一个或多个词
# 例如
  audit.# 匹配audit.irs.corporate或者 audit.irs 等
  audit.* 只能匹配 audit.irs

生产者

 * 生产消息-路由-动态路由
 */
@Test
public void testRoutingTopic() throws IOException {
    // 1、获取连接对象
    Connection connection = RabbitMQUtils.getConnection();
    // 2、创建通道
    assert connection != null;
    Channel channel = connection.createChannel();
    // 3、通过通道声明交换机,设置交换机的名称与交换机的类型。
    // 交换机不存在会自动创建,fanout表示广播。
    channel.exchangeDeclare("topics", "topic");
    // 4、路由key
    String routingKey = "user.save.message";
    // 5、发送消息
    // 交换机名称、路由键、持久化、消息体
    channel.basicPublish("topics", routingKey, null, ("基于路由方式的动态路由消息发送,路由键是【" + routingKey + "】发送的消息").getBytes());
    // 5、释放资源
    RabbitMQUtils.closeConnectionAndChanel(channel, connection);
}

消费者1:使用*通配符匹配一个单词

import com.rabbitmq.client.*;
import com.zhixi.util.RabbitMQUtils;

import java.io.IOException;

/**
 * @ClassName ConsumberTopic1
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-28 12:44
 * @Version 1.0
 */
public class ConsumberTopic1 {
    /**
     * 交换机名称
     */
    private static final String exchange = "topics";

    public static void main(String[] args) throws IOException {
        // 1、创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        // 2、创建通道
        assert connection != null;
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(exchange, "topic");
        // 3、获取临时队列名称
        String queueName = channel.queueDeclare().getQueue();
        // 4、绑定交换机和队列
        // 队列名称、交换机名称、路由键
        // user.后面只包含一个单词
        channel.queueBind(queueName, exchange, "user.*");
        // 5、消费消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("动态路由中,消费者1中的消息:" + new String(body));
            }
        });
    }
}

消费者2:使用#通配符匹配0个或者多个单词 

import com.rabbitmq.client.*;
import com.zhixi.util.RabbitMQUtils;

import java.io.IOException;

/**
 * @ClassName ConsumberTopic1
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-28 12:44
 * @Version 1.0
 */
public class ConsumberTopic2 {
    /**
     * 交换机名称
     */
    private static final String exchange = "topics";

    public static void main(String[] args) throws IOException {
        // 1、创建连接工厂
        Connection connection = RabbitMQUtils.getConnection();
        // 2、创建通道
        assert connection != null;
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(exchange, "topic");
        // 3、获取临时队列名称
        String queueName = channel.queueDeclare().getQueue();
        // 4、绑定交换机和队列
        // 队列名称、交换机名称、路由键
        // user.后面包含0个或者多个单词
        channel.queueBind(queueName, exchange, "user.#");
        // 5、消费消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("动态路由中,消费者2中的消息:" + new String(body));
            }
        });
    }
}

测试:开启两个生产者,再开启消费者

实际上就比上面的直连多了通配符的概念

 五、SpringBoot中使用RabbitMQ

直接看代码吧:https://gitee.com/zhang-zhixi/RabbitMQ-Study/tree/master/rabbitmq-02-springboot

启动SpringBoot项目,就会让消费者进行监听,然后再进行启动生产者进行测试

六、RabbitMQ集群

6.1、集群架构

普通集群

默认情况下:RabbitMQ代理操作所需的所有数据/状态都将跨所有节点复制。这方面的一个例外是消息队列,默认情况下,消息队列位于一个节点上,尽管它们可以从所有节点看到和访问

核心解决问题: 当集群中某一时刻master节点宕机,可以对Quene中信息,进行备份

缺点:不会自动切换节点,主节点宕机,其他节点不会提供服务

镜像集群

镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群的整体高可用性。

6.2、集群环境搭建(Docker方式)

参考链接:

https://blog.csdn.net/weixin_46073538/article/details/124766598

https://blog.csdn.net/yaomingyang/article/details/102922480

1、下载MQ镜像

docker pull rabbitmq:3.8-management

2、启动容器:启动后就可以访问到Web界面了[ip+1567x]

docker run -d --hostname myRabbit1 --name rabbit1 -p 15673:15672 -p 5673:5672 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:3.8-management
docker run -d --hostname myRabbit2 --name rabbit2 -p 15674:15672 -p 5674:5672 --link rabbit1:myRabbit1 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:3.8-management
docker run -d --hostname myRabbit3 --name rabbit3 -p 15675:15672 -p 5675:5672 --link rabbit1:myRabbit1 --link rabbit2:myRabbit2 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:3.8-management

3、加入集群

1、#进入rabbitmq01容器,重新初始化一下,如果是新安装则reset可以忽略重置。
docker exec -it rabbit1 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit

2、#进入rabbitmq02容器,重新初始化一下,将02节点加入到集群中
docker exec -it rabbit2 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@myRabbit1 #参数“--ram”表示设置为内存节点,忽略该参数默认为磁盘节点。
rabbitmqctl start_app
exit

3、#进入rabbitmq03容器,重新初始化一下,将03节点加入到集群中
docker exec -it rabbit3 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@myRabbit1
rabbitmqctl start_app
exit

4、配置镜像队列    

1、# 随便进入一个容器
docker exec -it rabbit1 bash
2、#设置策略匹配所有名称是amp开头的队列都存储在2个节点上的命令如下
rabbitmqctl set_policy -p / ha "^amp*" '{"ha-mode":"exactly","ha-params":2}'
#或者设置策略匹配所有名称的队列都进行高可用配置
rabbitmqctl set_policy -p / ha "^" '{"ha-mode":"all","ha-sync-mode":"automatic"}'
3、#查询策略
rabbitmqctl list_policies -p / #查看vhost下的所有的策略(policies )

参数一:策略名称,可以随便填,此外我们命名为ha(高可用);
参考二:-p / 设置哪个虚拟主机,可以使用rabbitmqctl list_policies -p / 查看vhost 下所有的策略(policies )。
参数三:队列名称的匹配规则,使用正则表达式表示;
参数四:为镜像队列的主体规则,是json字符串,分为三个属性:ha-mode | ha-params | ha-sync-mode,分别的解释如下:

  • ha-mode:镜像模式,分类:all/exactly/nodes,all存储在所有节点;exactly存储x个节点,节点的个数由ha-params指定;nodes指定存储的节点上名称,通过ha-params指定;
  • ha-params:作为参数,为ha-mode的补充;
  • ha-sync-mode:镜像消息同步方式:automatic(自动),manually(手动)

5、其他命令

查看镜像队列: rabbitmqctl list_policies 

删除镜像队列: rabbitmqctl clear_policy 

 

posted @ 2023-03-30 10:19  Java小白的搬砖路  阅读(40)  评论(0编辑  收藏  举报