centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型

目录

 一、安装RabbitMQ

1.yum安装 

Ⅰ.安装或更新yum插件

Ⅱ.安装 Erlang

Ⅲ、创建yum的RabbitMQ官方存储库

Ⅳ、安装RabbitMQ

Ⅴ、启动RabbitMQ

Ⅵ 、登录RabbitMQ的后台

Ⅶ、让guest可以远程登录 

二、java代码实战RabbitMQ的五种消息模型

前置准备

Ⅰ.新建host                  

Ⅱ.新建队列

Ⅲ、新建项目

    1.简单模式(Simple Mode) / 一对一模式

  2.工作队列模式(Work Queue Mode)

Ⅰ、公平的工厂模式

Ⅱ、按能分配

  3.发布/订阅模式(Publish/Subscribe Mode)

4.路由模式(Routing Mode)

   5.主题模式(Topic Mode)

三、补充内容

1.可能因为版本原因不能够旧方法设置头部取头部

Ⅰ、背景描述

Ⅱ、方法的代码 


 一、安装RabbitMQ

1.yum安装 

Ⅰ.安装或更新yum插件

 

sudo yum update


我这个是安装过的样子

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第1张图片

Ⅱ.安装 Erlang


RabbitMQ基于Erlang语法,高效但是需要Erlang支持
我们之后要下载的是rabbitMQ 3.10 需要

首先安装存储库

sudo yum install epel-release

 中间会询问是否安装,输入y就行了
 

创建yum的erlang数据库(直接下载版本太低了)

cd /etc/yum.repos.d
sudo touch /etc/yum.repos.d/erlang.repo
vi erlang.repo

编写文件为

[erlang-solutions]
name=CentOS $releasever - $basearch - Erlang Solutions
baseurl=https://packages.erlang-solutions.com/rpm/centos/$releasever/$basearch
gpgcheck=1
gpgkey=https://packages.erlang-solutions.com/rpm/erlang_solutions.asc
enabled=1

如果之前安过低版本要先删除
 

后面改成你的版本
sudo yum remove erlang-erts-R16B-03.18.el7.x86_64

 然后就可以安装
 

sudo yum install erlang

 我们没有添加公钥,会警告一下
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第2张图片

 然后输入y就行了

Ⅲ、创建yum的RabbitMQ官方存储库


添加 RabbitMQ 的官方存储库密钥到系统
 

sudo rpm --import https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc

添加 RabbitMQ 的官方存储库到 yum:
 

sudo touch /etc/yum.repos.d/rabbitmq.repo

然后修改这个文件
 

cd  /etc/yum.repos.d
vi rabbitmq.repo
[rabbitmq]
name=rabbitmq
baseurl=https://packagecloud.io/rabbitmq/rabbitmq-server/el/7/$basearch
enabled=1
gpgcheck=0
repo_gpgcheck=0

Ⅳ、安装RabbitMQ


查看可用版本
 

sudo yum --disablerepo="*" --enablerepo="rabbitmq" list available

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第3张图片

 可安装版本是3.10.0-1 官网最新版本是3.12.2 如果想要安装最新版本可以先使用其他方法
 

我们选择安装
 

sudo yum install rabbitmq-server


如果提示版本不足,请记住版本号之后,回到上面安装Erlang的步骤删除旧版本,重新安装erlang到正确版本
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第4张图片

这样就安装好了 

 

安装之后的路径在
 

/usr/lib/rabbitmq

Ⅴ、启动RabbitMQ

和其他的软件一样,如果需要外部服务器访问,需要更改防火墙

检查防火墙状态
 

sudo systemctl status firewalld

 如果防火墙未启用(启用了可以跳过,虚拟机也可以不开防火墙,但养成习惯服务器不会忘记)
便输入下面指令开启防火墙

sudo systemctl start firewalld

开启5672和15672 (后者是控制台)

firewall-cmd --add-port=5672/tcp --permanent
firewall-cmd --add-port=15672/tcp --permanent

 提示success就对了(我这个是重复添加) 

重新加载防火墙 

firewall-cmd --reload

提示success

 验证端口是否打开(这样出现了连接不上可以排除这里)
 

firewall-cmd --query-port=15672/tcp
firewall-cmd --query-port=5672/tcp

 出现yes就是对的了

 启动管理工具
 

rabbitmq-plugins enable rabbitmq_management 

 如果报下面的错就是15672被占用,可能是重复开启(如果提前改了配置文件再重启服务也能达到上面那个指令效果)

 我们可以查看下端口占用

sudo ss -tlnp | grep 15672
选一个允许 如果不是centos 7 还有其他的指令
sudo netstat -tlnp | grep 15672

 占用端口15672的进程是beam.smp,它是RabbitMQ服务器的核心进程。这意味着RabbitMQ管理插件已经在监听15672端口,并且正常工作。
如果不是beam.smp我们可以杀死之后,再启动管理工具
 

sudo kill    -- 实例代码  换成想杀死的队列
sudo kill 15672

Ⅵ 、登录RabbitMQ的后台
 

我们新建一个用户,并赋予管理员权限,来远程访问我们控制台

rabbitmqctl add_user admin admin

前面是用户名,后面是密码 密码还是改复杂点

  新建了一个简单用户怕不安全,可以用下面指令改密码
 

sudo rabbitmqctl change_password admin <新密码>

 赋予管理员权限

rabbitmqctl set_user_tags admin administrator

 然后我们可以去主机访问下虚拟机或者服务器(这次没有用虚拟机,没有安装图形化和浏览器就只演示主机连接虚拟机,平时在本地访问对应端口是确保真的启动了软件)
 

你的虚拟机ip或者服务器公网ip:15672

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第5张图片

 然后输入刚才新建的用户就登录上去了

Ⅶ、让guest可以远程登录 

guest拥有最高的权限但是不能在非本机登录(比如服务器就得登服务器,我用主机连接服务器的rabbit并登录guest,就会被拒绝)
可是需要guest的权限来做事情,服务器又没有图形化,直接敲命令又不熟练差点把人急死

 我们需要改配置

/etc/rabbitmq

如果有rabbitmq.config就修改,没有就新建
 

touch rabbitmq.config
vi rabbitmq.config

 确保里面有
 

[{rabbit, [{loopback_users, []}]}].

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第6张图片

 这个时候重启就行了
 

sudo systemctl restart rabbitmq-server

 重启访问 
 

http://rabbitmq的主机地址:15672/

 就能用guest登录了

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第7张图片

 登录之后去改密码
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第8张图片

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第9张图片

 一些需要高权限的事情,就用guest做,平时还是用admin

二、java代码实战RabbitMQ的五种消息模型

 

前置准备

 

Ⅰ.新建host                  

首先在后台新建host并创建响应的队列

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第10张图片 

 可以只输入一个名字

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第11张图片

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第12张图片 

然后  Add virtual host

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第13张图片
这里就有我们配置好的,点击这个加减可以选择显示的属性

Ⅱ.新建队列

 

然后我们还要创建队列

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第14张图片 

 点击add a newqueue之后

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第15张图片 

这里就有我们刚新建的host了

我们看见了队列的三种类型,分别是下面三种,我们只要经典队列就行了
 

经典队列(Classic Queue): 经典队列(也称为普通队列)是 RabbitMQ 最常见和默认的队列类型。它支持多个消费者并且提供了传统的消息传递模式。

Quorum 队列(Quorum Queue): Quorum 队列是 RabbitMQ 3.8 版本引入的一种新的队列类型。它基于 Raft 一致性协议,提供更高的可靠性和良好的扩展性。Quorum 队列适用于高吞吐量和高可靠性的应用场景。

流队列(Stream Queue): 流队列是 RabbitMQ 3.9 版本中引入的新队列类型。它是为了解决大规模事件流处理而设计的。流队列支持发布/订阅模式,具备持久化和分区等特性,能够处理大量的事件流数据。

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第16张图片 

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第17张图片 

 这里我们就建好了

Ⅲ、新建项目

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第18张图片 

 新建一个maven项目(基于IDEA 2023.2 演示 如果版本相同,缺很多东西一般是教育免费版)
在pom.xml中添加依赖
 

    
        
            com.rabbitmq
            amqp-client
            3.4.1
        
    

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第19张图片

 再编写一下工具来进行RabbitMQ的连接
 

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

import java.io.IOException;

public class MQUtils {

    public static final String QUEUE_NAME = "myqueue01";   //队列1 我们刚才新建了
    public static final String QUEUE_NAME2 = "myqueue02";  //队列2 我们没有新建 (也可以在使用的时候直接写名字)

    public static final String EXCHANGE_NAME = "myexchange01";  //路由名字 后面要定义
    public static final String EXCHANGE_NAME2 = "myexchange02";
    public static final String EXCHANGE_NAME3 = "myexchange03";

    /**
     * 获得MQ的连接
     * @return
     * @throws IOException
     */
    public static Connection getConnection() throws IOException {
        ConnectionFactory connectionFactory = new ConnectionFactory();

        connectionFactory.setHost("localhost");     // 服务器的主机名
        connectionFactory.setPort(5672);            // 服务器的主机名
        connectionFactory.setVirtualHost("myhost"); //我们虚拟出来的主机名(刚才新建的主机名)
        connectionFactory.setUsername("admin");     //我们新建的用户
        connectionFactory.setPassword("123456");    //以及我们新建用户的密码
        return connectionFactory.newConnection();  // 返回Connection用于后续操作
    }
}

    1.简单模式(Simple Mode) / 一对一模式

      首先来熟悉一下简单模式的流程

         发布者(Producer) --> 队列(Queue) --> 消费者(Consumer)
      ①.生产者将消息直接发送到一个队列。
      ②.消费者从队列中接收消息并进行处理。
         明确了需要的东西之后我们开始建包(不理解生产者消费者的,可以去我的主页的堵塞队列里面看一下,讲的 比较俗  易懂)

 首先是生产者
 

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;

/*
 * 简单模式的生成者 将消息直接发送到一个队列
 */
public class SimpleProducer {
    public static void main(String[] args) throws IOException {
        // 获取与 RabbitMQ 的连接
        Connection connection = MQUtils.getConnection();

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

        // 定义队列
        // 参数说明:
        //  - MQUtils.QUEUE_NAME: 队列名称
        //  - false: 指定队列是否持久化,false 表示不持久化
        //  - false: 指定当所有消费者客户端连接断开时,是否删除队列。false 表示不删除
        //  - false: 指定是否独占队列。false 表示队列可以被多个消费者监听
        //  - null: 其他属性,比如队列的消息过期时间等。这里为 null

        channel.queueDeclare(MQUtils.QUEUE_NAME, false, false, false, null);

        // 要发送的消息内容
        String msg = "Hello World!";

        // 发布消息到队列
        // 参数说明:
        //  - "": 交换机名称。空字符串表示直接发送到队列,不经过交换机
        //  - MQUtils.QUEUE_NAME: 队列名称
        //  - null: 其他属性,比如消息的路由键等。这里为 null
        //  - msg.getBytes(): 要发送的消息的字节数组形式

        channel.basicPublish("", MQUtils.QUEUE_NAME, null, msg.getBytes());

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

        // 关闭连接
        connection.close();
    }
}

   然后是消费者 
 

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 简单模式的消费者 从队列中接收消息并进行处理
 */
public class SimpleConsumer {

    public static void main(String[] args) throws IOException, InterruptedException {
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        //定义队列
        channel.queueDeclare(MQUtils.QUEUE_NAME,false,false,false,null);
        //创建消费者
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
        //消费者消费通道中的消息
        channel.basicConsume(MQUtils.QUEUE_NAME,true,queueingConsumer);
        //读取消息
        while(true){
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
            System.out.println(new String(delivery.getBody()));
        }
    }
}

 第二个参数是否持久化是我们之前就设置好的
已经存在的队列不可更改 durable,直接更改会报错
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第20张图片

 如果写的和我们配置的不一样就会报错

Exception in thread "main" java.io.IOException
	at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:106)
	at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:102)
	at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:124)
	at com.rabbitmq.client.impl.ChannelN.queueDeclare(ChannelN.java:833)
	at com.rabbitmq.client.impl.ChannelN.queueDeclare(ChannelN.java:61)
	at com.cece.simplemode.SimpleProducer.main(SimpleProducer.java:28)
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'myqueue01' in vhost 'myhost': received 'false' but current is 'true', class-id=50, method-id=10)
	at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:67)
	at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:33)
	at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:343)
	at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:216)
	at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:118)
	... 3 more
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'myqueue01' in vhost 'myhost': received 'false' but current is 'true', class-id=50, method-id=10)
	at com.rabbitmq.client.impl.ChannelN.asyncShutdown(ChannelN.java:478)
	at com.rabbitmq.client.impl.ChannelN.processAsync(ChannelN.java:315)
	at com.rabbitmq.client.impl.AMQChannel.handleCompleteInboundCommand(AMQChannel.java:144)
	at com.rabbitmq.client.impl.AMQChannel.handleFrame(AMQChannel.java:91)
	at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:552)
	at java.lang.Thread.run(Thread.java:750)

 我们首先启动消费者方法 它会整装待发
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第21张图片

 然后我们启动消费者方法
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第22张图片

 消费者启动完成
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第23张图片

 我们看见消费者接收到了,生产者发来到消息
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第24张图片

 多次运行生产者,消费者就会得到多次消息
 而且改变消费者方法,也会接收到不同的消息
 这就是简单模式 他们用队列一对一来传递东西
 

  2.工作队列模式(Work Queue Mode)

  我们发现生产者传递一次,只有一个消费者能够接收到消息,效率很低下
  所以我们使用工作队列模式:

生产者将消息发送到一个队列。

多个消费者同时监听同一个队列,竞争接收消息。

每个消息只会被一个消费者接收和处理。

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第25张图片

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第26张图片
我们新建包workqueuemode,把刚才的生产者消费者类都复制过去,消费者复制两个,并且重命名



Ⅰ、公平的工厂模式



首先是生产类

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 模式的生产者 经过一个队列  发生多条消息 给消费者
 * 是分发,两个人得到的是总量
 */
public class WorkProductor {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

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

        // 声明队列
        // 参数说明:
        // - queue: 队列名称
        // - durable: 队列是否持久化,false表示非持久化,消息不会保存到磁盘
        // - exclusive: 队列是否为专用队列,true表示只能被当前连接使用,连接关闭后队列会被删除
        // - autoDelete: 队列是否自动删除,true表示当没有消费者订阅该队列时自动删除队列
        // - arguments: 额外的参数,例如消息存活时间、最大长度等
        channel.queueDeclare(MQUtils.QUEUE_NAME, true, false, false, null);

        // 循环发送消息到队列
        for (int i = 0; i < 100; i++) {
            String msg = "Hello-->" + i;
            // 发布消息到队列
            // 参数说明:
            // - exchange: 交换机名称,空字符串表示默认的直连交换机
            // - routingKey: 路由键,用于将消息路由到指定的队列
            // - props: 消息的属性,例如消息持久化标记、优先级等
            // - body: 消息体,即要发送的消息内容
            channel.basicPublish("", MQUtils.QUEUE_NAME, null, msg.getBytes());
            System.out.println("发生了:" + msg);
            Thread.sleep(10); // 睡眠一会儿
        }

        // 关闭通道和连接
        channel.close();
        connection.close();
    }
}

然后是消费类1 
 

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 工厂模式的消费者 从队列中接收消息并进行处理 休眠时间很短
 */
public class WorkConsumer01 {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

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

        // 声明队列
        // 参数说明:
        // - queue: 队列名称
        // - durable: 队列是否持久化,false表示非持久化,消息不会保存到磁盘
        // - exclusive: 队列是否为专用队列,true表示只能被当前连接使用,连接关闭后队列会被删除
        // - autoDelete: 队列是否自动删除,true表示当没有消费者订阅该队列时自动删除队列
        // - arguments: 额外的参数,例如消息存活时间、最大长度等
        channel.queueDeclare(MQUtils.QUEUE_NAME, true, false, false, null);

        // 创建一个 QueueingConsumer 对象,用于消费通道中的消息
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);

        // 消费者订阅队列中的消息
        // 参数说明:
        // - queue: 要订阅的队列名称
        // - autoAck: 是否自动确认消息消费完成,true表示自动确认,消息一旦被接收就会从队列中删除
        // - consumer: 消息的消费者
        channel.basicConsume(MQUtils.QUEUE_NAME, true, queueingConsumer);

        // 循环接收并处理消息
        while (true) {
            // 等待下一条消息的到达
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();

            // 获取消息内容并进行处理
            String message = new String(delivery.getBody());
            System.out.println("工厂消费者1接收到: " + message);

            // 模拟处理消息的耗时操作
            Thread.sleep(10);
        }
    }
}


消费类2
 

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 工厂模式的消费者 从队列中接收消息并进行处理 休眠时间很长
 */
public class WorkConsumer02 {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

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

        // 声明队列
        // 参数说明:
        // - queue: 队列名称
        // - durable: 队列是否持久化,false表示非持久化,消息不会保存到磁盘
        // - exclusive: 队列是否为专用队列,true表示只能被当前连接使用,连接关闭后队列会被删除
        // - autoDelete: 队列是否自动删除,true表示当没有消费者订阅该队列时自动删除队列
        // - arguments: 额外的参数,例如消息存活时间、最大长度等
        channel.queueDeclare(MQUtils.QUEUE_NAME, true, false, false, null);

        // 创建一个 QueueingConsumer 对象,用于消费通道中的消息
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);

        // 消费者订阅队列中的消息
        // 参数说明:
        // - queue: 要订阅的队列名称
        // - autoAck: 是否自动确认消息消费完成,true表示自动确认,消息一旦被接收就会从队列中删除
        // - consumer: 消息的消费者
        channel.basicConsume(MQUtils.QUEUE_NAME, true, queueingConsumer);

        // 循环接收并处理消息
        while (true) {
            // 等待下一条消息的到达
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();

            // 获取消息内容并进行处理
            String message = new String(delivery.getBody());
            System.out.println("工厂消费者2接收到: " + message);

            // 模拟处理消息的耗时操作
            Thread.sleep(1000);
        }
    }
}

 先启动两个消费者

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第27张图片

然后启动生产者
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第28张图片

生产者生产完毕一共100条(0开始的)

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第29张图片

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第30张图片

 我们发现消费者1收到的是0,2,4这样的,消费者2是1,3,5这样
说明他们是一人获得一个,这个人获得了,下个人再获得一个
可是消费者1很快就运行完了,消费2却还要很久

我们可以这样理解,现在有两个小朋友,一个阿姨给他们分糖,分的很快,左边甩一个,右边甩一个,小朋友甲吃的也很快,恨不得甩一个吃一个,小朋友乙却是吃的很慢。发完之后,甲和阿姨就目瞪口呆看着它吃。

这是因为两个消费者都订阅了同一个队列,并且队列默认采用是自动确认机制,消息发过去后就自动确认,队列不清楚每个消息具体什么时间处理完,所以平均分配消息数量。

好像分吃的时候很公平,可是平时处理业务的时候,我们应该让做的快的消费者,多接收些消息来消费。
所以我们要修改原来的工厂代码
 

Ⅱ、按能分配

 

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第31张图片 

 新建我们新工厂类软件包

直接复制之前工厂的软件包
 

①channel.basicQos(1);限制队列一次发一个消息给消费者,等消费者有了反馈,再发下一条

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第32张图片

 

②channel.basicAck 消费完消息后手动反馈,处理快的消费者就能处理更多消息

③basicConsume 中的参数改为false
 

消费者1和2都如下增加

package com.cece.workqueuemode2;

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 工厂模式的消费者 从队列中接收消息并进行处理 休眠时间很短
 */
public class WorkConsumer01 {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

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

        // 声明队列
        // 参数说明:
        // - queue: 队列名称
        // - durable: 队列是否持久化,false表示非持久化,消息不会保存到磁盘
        // - exclusive: 队列是否为专用队列,true表示只能被当前连接使用,连接关闭后队列会被删除
        // - autoDelete: 队列是否自动删除,true表示当没有消费者订阅该队列时自动删除队列
        // - arguments: 额外的参数,例如消息存活时间、最大长度等
        channel.queueDeclare(MQUtils.QUEUE_NAME, true, false, false, null);

        //同一时刻服务器只发送一条消息给消费者
        channel.basicQos(1);

        // 创建一个 QueueingConsumer 对象,用于消费通道中的消息
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);

        // 消费者订阅队列中的消息
        // 参数说明:
        // - queue: 要订阅的队列名称
        // - autoAck: 是否自动确认消息消费完成,true表示自动确认,消息一旦被接收就会从队列中删除
        // - consumer: 消息的消费者
        channel.basicConsume(MQUtils.QUEUE_NAME, false, queueingConsumer);

        // 循环接收并处理消息
        while (true) {
            // 等待下一条消息的到达
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();

            // 获取消息内容并进行处理
            String message = new String(delivery.getBody());
            System.out.println("工厂消费者1接收到: " + message);

            // 模拟处理消息的耗时操作
            Thread.sleep(10);

            //手动确定返回状态,不写就是自动确认
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        }
    }
}

 

package com.cece.workqueuemode2;

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 工厂模式的消费者 从队列中接收消息并进行处理 休眠时间很长
 */
public class WorkConsumer02 {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

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

        // 声明队列
        // 参数说明:
        // - queue: 队列名称
        // - durable: 队列是否持久化,false表示非持久化,消息不会保存到磁盘
        // - exclusive: 队列是否为专用队列,true表示只能被当前连接使用,连接关闭后队列会被删除
        // - autoDelete: 队列是否自动删除,true表示当没有消费者订阅该队列时自动删除队列
        // - arguments: 额外的参数,例如消息存活时间、最大长度等
        channel.queueDeclare(MQUtils.QUEUE_NAME, true, false, false, null);

        //同一时刻服务器只发送一条消息给消费者
        channel.basicQos(1);

        // 创建一个 QueueingConsumer 对象,用于消费通道中的消息
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);

        // 消费者订阅队列中的消息
        // 参数说明:
        // - queue: 要订阅的队列名称
        // - autoAck: 是否自动确认消息消费完成,true表示自动确认,消息一旦被接收就会从队列中删除
        // - consumer: 消息的消费者
        channel.basicConsume(MQUtils.QUEUE_NAME, false, queueingConsumer);

        // 循环接收并处理消息
        while (true) {
            // 等待下一条消息的到达
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();

            // 获取消息内容并进行处理
            String message = new String(delivery.getBody());
            System.out.println("工厂消费者2接收到: " + message);

            // 模拟处理消息的耗时操作
            Thread.sleep(1000);

            //手动确定返回状态,不写就是自动确认
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        }
    }
}

 

然后我们继续操作,发现
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第33张图片

 消费者1(休息时间短的)几乎接收了所有的消息
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第34张图片
 消费者2(休息时间短的)就寥寥几条消息
我们就实现了按能分配

 

 

  3.发布/订阅模式(Publish/Subscribe Mode)

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第35张图片 

 

发布/订阅模式也被称为广播模式(Broadcast Mode)。 

在发布/订阅模式中,消息会被广播到所有订阅了相应交换机的队列。

生产者发送消息到交换机,而不直接发送到队列。

多个消费者可以创建各自的队列并绑定到交换机上,从而接收消息。

这种模式可用于实现广播通知、日志分发等场景。

一个生产者将消息发布到交换机。

多个队列通过绑定到该交换机来接收消息。

每个队列都有自己的消费者。

 发布/订阅 与工厂类的区别就是
 工厂类是共用一个队列  多个消费者争夺有限的消息

,而 发布/订阅 是消费者一人一个队列,可以接收到相同的消息


综上所述我们还要新建一个队列,以及一个交换机

新建队列(是否持久化看自己选择,使用的时候不能搞错)
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第36张图片

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第37张图片

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第38张图片
 新建一个交换机

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第39张图片

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第40张图片

 订阅是扇形交换机,我们type选择Fanout

直连交换机(Direct Exchange): 直连交换机将消息通过路由键与绑定键进行精确匹配,并将消息发送到匹配的队列中。该类型的交换机通常用于点对点的消息传递。

主题交换机(Topic Exchange): 主题交换机通过使用通配符匹配路由键与绑定键,将消息发送到符合匹配规则的队列中。通配符可以使用 "*" 表示单个词,"#" 表示零个或多个词。该类型的交换机广泛应用于灵活的发布/订阅模式。

扇形交换机(Fanout Exchange): 扇形交换机将消息广播给所有绑定到它的队列,忽略路由键。该类型的交换机适用于一对多的消息广播场景,消息发送给所有订阅者。

头交换机(Headers Exchange): 头交换机根据消息的头部信息(headers)进行匹配,并将消息发送到符合匹配规则的队列中。相比于其他交换机类型,头交换机的匹配是更复杂的。

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第41张图片

 这样就新建好了


我们接下来要把两个队列绑在交换机上
虽然队列和交换机都是一个host衍生出来的,但是他们又不会互相认识。
Ⅰ、直接控制台绑定

我们点击刚才建好的交换机

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第42张图片

就进入交换机的设置界面 
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第43张图片

 点开bindings属性就可以绑定,队列
(这里绑定了,代码就不要写绑定代码了)

Ⅱ、Java代码去绑定

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第44张图片

 首先新建包subscribemode,复制工厂工厂模式的三个类,并重命名

 首先是生产者的代码
我们和之前的不同点就是 没有和队列绑定,绑定的是交换机

 

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;

/*
 * 发布和订阅模式的生产者,消息会通过交换机发到队列
 */
public class PublishProductor {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

        // 创建一个通道,用于进行消息的发布和接收等操作。
        Channel channel = connection.createChannel();


        //声明一个交换机, fanout表示使用发布/订阅模式(广播模式)。这是我们刚才定义的类型
        // 交换机名字
        // type
        // 是否持久化  如果不是持久化,可以不要这个参数
        channel.exchangeDeclare(MQUtils.EXCHANGE_NAME,"fanout",true);
        //设置要发送的消息内容。
        String msg = "Hello Fanout";
        // 发布消息到队列
        // 参数说明:
        // - exchange: 交换机名称,空字符串表示默认的直连交换机
        // - routingKey: 路由键,根据实际需求将消息路由到指定的队列
        // ,这里为空字符串表示消息将被广播给所有绑定到交换机的队列。
        // - props: 消息的属性,例如消息持久化标记、优先级等消息的属性,这里设置为null表示使用默认属性。
        // - body: 消息体,即要发送的消息内容
        channel.basicPublish(MQUtils.EXCHANGE_NAME,"",null,msg.getBytes());
        System.out.println("send:" + msg);

        // 关闭通道和MQ连接
        channel.close();
        connection.close();
    }
}

 

消费者1
 

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 发布/订阅模式 的消费者1
 * 两个消费者绑定的消息队列不同
 * 通过交换机一个消息能被   不同队列的  两个消费者同时获取
 * 一个队列可以有多个消费者,队列中的消息  只能被一个  消费者获取
 */
public class SubscribeConsumer1 {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

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

        // 声明队列
        // 参数说明:
        // - queue: 队列名称
        // - durable: 队列是否持久化,false表示非持久化,消息不会保存到磁盘
        // - exclusive: 队列是否为专用队列,true表示只能被当前连接使用,连接关闭后队列会被删除
        // - autoDelete: 队列是否自动删除,true表示当没有消费者订阅该队列时自动删除队列
        // - arguments: 额外的参数,例如消息存活时间、最大长度等

        //绑定队列1
        channel.queueDeclare(MQUtils.QUEUE_NAME, true, false, false, null);

        //绑定队列1到交换机
        channel.queueBind(MQUtils.QUEUE_NAME,MQUtils.EXCHANGE_NAME,"");

        // 创建一个 QueueingConsumer 对象,用于消费通道中的消息
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);

        // 消费者订阅队列中的消息
        // 参数说明:
        // - queue: 要订阅的队列名称
        // - autoAck: 是否自动确认消息消费完成,true表示自动确认,消息一旦被接收就会从队列中删除
        // - consumer: 消息的消费者

        //订阅队列1
        channel.basicConsume(MQUtils.QUEUE_NAME, true, queueingConsumer);

        // 循环接收并处理消息
        while (true) {
            // 等待下一条消息的到达
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();

            // 获取消息内容并进行处理
            String message = new String(delivery.getBody());
            //输出信息
            System.out.println("工厂消费者1接收到: " + message);

        }

    }
}

消费者2

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 发布/订阅模式 的消费者2
 * 两个消费者绑定的消息队列不同
 * 通过交换机一个消息能被   不同队列的  两个消费者同时获取
 * 一个队列可以有多个消费者,队列中的消息  只能被一个  消费者获取
 */
public class SubscribeConsumer2 {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

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

        // 声明队列
        // 参数说明:
        // - queue: 队列名称
        // - durable: 队列是否持久化,false表示非持久化,消息不会保存到磁盘
        // - exclusive: 队列是否为专用队列,true表示只能被当前连接使用,连接关闭后队列会被删除
        // - autoDelete: 队列是否自动删除,true表示当没有消费者订阅该队列时自动删除队列
        // - arguments: 额外的参数,例如消息存活时间、最大长度等

        //声明队列2
        channel.queueDeclare(MQUtils.QUEUE_NAME2, true, false, false, null);

        //绑定队列2到交换机
        channel.queueBind(MQUtils.QUEUE_NAME2,MQUtils.EXCHANGE_NAME,"");

        // 创建一个 QueueingConsumer 对象,用于消费通道中的消息
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);


        // 消费者订阅队列中的消息
        // 参数说明:
        // - queue: 要订阅的队列名称
        // - autoAck: 是否自动确认消息消费完成,true表示自动确认,消息一旦被接收就会从队列中删除
        // - consumer: 消息的消费者

        //订阅队列2
        channel.basicConsume(MQUtils.QUEUE_NAME2, true, queueingConsumer);

        // 循环接收并处理消息
        while (true) {
            // 等待下一条消息的到达
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();

            // 获取消息内容并进行处理
            String message = new String(delivery.getBody());

            System.out.println("工厂消费者2接收到: " + message);


        }
    }
}

我们开始测试,先启动两个消费者

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第45张图片

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第46张图片 去后台发现已经绑定上了

运行生产者,生产者1,生产者2都接收到消息了
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第47张图片
 

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第48张图片

我们就完成了,一个信息让多个消费者同时接收到

4.路由模式(Routing Mode)

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第49张图片

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第50张图片

 其实和之前那个的表面流程差不多,不过这次消费者不是用队列绑定了交换机就能取得消息。

之前那个交换机就相当于一个放补贴的地方,补贴到了之后,每个绑定了这个补贴站的都能获得一份补贴。

现在就相当于你你去取快递,你和另一个消费者都知道快递站在哪,但是快递被标记了记号,只有消费者手上的记号,和快递对的上,才能取得。总不可能你手机绑定了一个快递驿站,里面的东西你就都能随便取了吧。


 

路由模式使用一个直连交换机(Direct Exchange)进行消息路由。

在路由模式中,生产者发送消息到交换机,并指定一个路由键(Routing Key)。

消费者创建队列,并将队列与交换机进行绑定,同时指定一个绑定键(Binding Key)。

只有指定路由键和绑定键相匹配的消息,才会被分发到相应的队列。

这种模式可用于实现按需发布消息的场景。


综上所述,我们需要新建一个 直连的交换机  

 

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第51张图片

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第52张图片

 

 

这样我们就得到了我们需要的交换机 

我们可以和上面一样用代码绑定,或者控制台手动绑定,

后面为了方便,我都用代码绑定
 

新建包 routingmode 将刚才的类复制进来并改名
 
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第53张图片

 我们需要在生产者里面 ,为队列指定键值(比发布/订阅多了的是,在声明交换机之后,要将队列绑定并且指定键值给交换机),然后更改发布消息经过的交换机


消费者需要 绑定并订阅 队列,不需要再 绑定队列到交换机(生产者做了)

生产者

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/*
 路由模式的生产者,发布消息会有特定的Key,消息会被绑定特定Key的消费者获取
 */
public class RouteProductor {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

        // 创建一个通道,用于进行消息的发布和接收等操作。
        Channel channel = connection.createChannel();


        //声明一个交换机, direct表示直连交换机,将消息通过路由键与绑定键进行精确匹配
        // 交换机名字
        // type
        // 是否持久化  如果不是持久化,可以不要这个参数

        channel.exchangeDeclare(MQUtils.EXCHANGE_NAME2,"direct",true);


        //设置要发送的消息内容。
        String msg = "error";

        // 队列名 交换机名  "绑定的键"

        //绑定队列1到交换机,指定了Key为error
        channel.queueBind(MQUtils.QUEUE_NAME,MQUtils.EXCHANGE_NAME2,"error");
        //绑定队列2到交换机,指定了Key为debug
        channel.queueBind(MQUtils.QUEUE_NAME2,MQUtils.EXCHANGE_NAME2,"debug");

        // 发布消息到队列
        // 参数说明:
        // - exchange: 交换机名称,空字符串表示默认的直连交换机
        // - routingKey: 路由键,根据实际需求将消息路由到指定的队列
        // -"":路由键,根据实际需求将消息路由到指定的队列。,这里为空字符串表示消息将被广播给所有绑定到交换机的队列。
        //,这里指定为"error" 对应队列1
        // - body: 消息体,即要发送的消息内容
        channel.basicPublish(MQUtils.EXCHANGE_NAME2,"debug",null,msg.getBytes());

        System.out.println("send:" + msg);



        // 关闭通道和MQ连接
        channel.close();
        connection.close();
    }
}

消费者1

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 路由模式的消费者1
 * 可以指定Key,消费特定的消息
 */
public class RouteConsumer01 {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

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

        // 声明队列
        // 参数说明:
        // - queue: 队列名称
        // - durable: 队列是否持久化,false表示非持久化,消息不会保存到磁盘
        // - exclusive: 队列是否为专用队列,true表示只能被当前连接使用,连接关闭后队列会被删除
        // - autoDelete: 队列是否自动删除,true表示当没有消费者订阅该队列时自动删除队列
        // - arguments: 额外的参数,例如消息存活时间、最大长度等

        //绑定队列1
        channel.queueDeclare(MQUtils.QUEUE_NAME, true, false, false, null);



        // 创建一个 QueueingConsumer 对象,用于消费通道中的消息
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);

        // 消费者订阅队列中的消息
        // 参数说明:
        // - queue: 要订阅的队列名称
        // - autoAck: 是否自动确认消息消费完成,true表示自动确认,消息一旦被接收就会从队列中删除
        // - consumer: 消息的消费者

        //订阅队列1
        channel.basicConsume(MQUtils.QUEUE_NAME, true, queueingConsumer);

        // 循环接收并处理消息
        while (true) {
            // 等待下一条消息的到达
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();

            // 获取消息内容并进行处理
            String message = new String(delivery.getBody());
            //输出信息
            System.out.println("工厂消费者1接收到: " + message);

        }

    }
}

 消费者2
 

import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.QueueingConsumer;

import java.io.IOException;

/*
 * 路由模式的消费者2
 * 可以指定Key,消费特定的消息
 */
public class RouteConsumer02 {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

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

        // 声明队列
        // 参数说明:
        // - queue: 队列名称
        // - durable: 队列是否持久化,false表示非持久化,消息不会保存到磁盘
        // - exclusive: 队列是否为专用队列,true表示只能被当前连接使用,连接关闭后队列会被删除
        // - autoDelete: 队列是否自动删除,true表示当没有消费者订阅该队列时自动删除队列
        // - arguments: 额外的参数,例如消息存活时间、最大长度等
        channel.queueDeclare(MQUtils.QUEUE_NAME2, true, false, false, null);

        // 创建一个 QueueingConsumer 对象,用于消费通道中的消息
        QueueingConsumer queueingConsumer = new QueueingConsumer(channel);

        // 消费者订阅队列中的消息
        // 参数说明:
        // - queue: 要订阅的队列名称
        // - autoAck: 是否自动确认消息消费完成,true表示自动确认,消息一旦被接收就会从队列中删除
        // - consumer: 消息的消费者
        channel.basicConsume(MQUtils.QUEUE_NAME2, true, queueingConsumer);

        // 循环接收并处理消息
        while (true) {
            // 等待下一条消息的到达
            QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();

            // 获取消息内容并进行处理
            String message = new String(delivery.getBody());
            System.out.println("工厂消费者2接收到: " + message);


        }
    }
}

然后我们启动两个消费者,再启动生产者
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第54张图片
 

 发现刚好是键值为debug的队列2的消费者2得到了消息,发消息的键值改成error
就成消费者1得到了
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第55张图片

 如果发信息改成键值改成123,就没有一个得到了

 这样我们就完成了路由模式,经过直连交换机,只有键值对应才能接收到消息

   5.主题模式(Topic Mode)

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第56张图片

主题模式使用一个主题交换机(Topic Exchange)进行消息路由。

在主题模式中,生产者发送消息到交换机,并指定一个主题(Topic)。

消费者创建队列,并将队列与交换机进行绑定,同时指定一个绑定键(Binding Key)。

主题由一个或多个单词组成,单词间使用点号(.)分隔。

消息的主题与绑定键进行匹配,可以使用星号(*)和井号(#)进行通配符匹配。

这种模式可用于实现灵活的消息路由和订阅过滤。

发布者将消息发送到topic交换机,并指定一个主题,消费者可以根据通配符规则绑定队列来接收符合特定主题的消息。

其实就是订阅模式太过针对性了,实际使用中,更多的是处理某些特征的,而不是用某个队列单单处理某个

综上我们新建一个topic的交换机
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第57张图片

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第58张图片

 我们依旧还是用代码绑定,新建包topicmode,移动之前的并重命名
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第59张图片

 生产者 我们修改声明的主机,绑定队列的主机,消息的类型
 



import com.cece.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;

/*
     主题模式的生产者,发布消息会有特定的Key,消息会被绑定区配key值规则的消费者获取
 */
public class TopicProductor {

    public static void main(String[] args) throws IOException, InterruptedException {
        // 获取与MQ服务器的连接
        Connection connection = MQUtils.getConnection();

        // 创建一个通道,用于进行消息的发布和接收等操作。
        Channel channel = connection.createChannel();


        //声明一个交换机, direct表示直连交换机,将消息通过路由键与绑定键进行精确匹配
        // 交换机名字
        // type
        // 是否持久化  如果不是持久化,可以不要这个参数

        channel.exchangeDeclare(MQUtils.EXCHANGE_NAME3,"topic",true);


        //设置要发送的消息内容。
        String msg = "com.a.b";

        // 队列名 交换机名  "绑定的键"

        //绑定队列1到交换机,指定了Key为error
        channel.queueBind(MQUtils.QUEUE_NAME,MQUtils.EXCHANGE_NAME3,"#.medo");
        //绑定队列2到交换机,指定了Key为debug
        channel.queueBind(MQUtils.QUEUE_NAME2,MQUtils.EXCHANGE_NAME3,"com.*.*");

        // 发布消息到队列
        // 参数说明:
        // - exchange: 交换机名称,空字符串表示默认的直连交换机
        // - routingKey: 路由键,根据实际需求将消息路由到指定的队列
        // -"":路由键,根据实际需求将消息路由到指定的队列。,这里为空字符串表示消息将被广播给所有绑定到交换机的队列。
        //,这里指定为"error" 对应队列1
        // - body: 消息体,即要发送的消息内容
        channel.basicPublish(MQUtils.EXCHANGE_NAME3,"error",null,msg.getBytes());

        System.out.println("send:" + msg);



        // 关闭通道和MQ连接
        channel.close();
        connection.close();
    }
}

消费者1,消费者2都不用变
先启动消费者1,消费者2然后启动生产者

centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第60张图片

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第61张图片

 这个时候我们把发消息的键值改成
 

   channel.basicPublish(MQUtils.EXCHANGE_NAME3,"com.a.medo",null,msg.getBytes());

再运行生产者 ,发现消费者1和消费者2都接收到消息了
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第62张图片

 centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第63张图片

 至此我们就我们了五种消息模型的实战,熟练掌握之后就可以用于项目上


三、补充内容

1.可能因为版本原因不能够旧方法设置头部取头部
 

Ⅰ、背景描述

我们定义了队列来执行canal监听之后应该做的操作,为了避免重复消息多次处理数据库操作,于是使用了设置头部识别码,但是好像因为RabbitMQ的版本不对,无法用旧方法设置头部并获取,固然探寻解决方法。

 

Ⅱ、方法的代码 

(已经完成了主题交换机的定义和队列的绑定,关闭自动确认) 

消息的发送者 

import com.alibaba.fastjson.JSON;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.cece.common.config.RabbitMQConfig;
import com.cece.common.entity.Course;
import com.cece.educoureservice.service.CourseService;
import com.xpand.starter.canal.annotation.CanalEventListener;
import com.xpand.starter.canal.annotation.ListenPoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.UUID;

/**
 * 事件监听器
 */
@Slf4j
@CanalEventListener
public class CourseCanalListener {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private CourseService courseService;

    /**
     * 监听 edu_course数据库的course表
     */
    @ListenPoint(schema = "edu_course",table = "course")
    public void handleCourseUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData){
        //判断操作的类型
        if("DELETE".equals(eventType.name())){
            //遍历删除前数据行的每一列
            rowData.getBeforeColumnsList().forEach(column -> {
                //获得删除前的ID
                if("id".equals(column.getName())){
                    //发删除消息给处理删除的队列 CorrelationData可以消息的id
                    rabbitTemplate.convertAndSend(RabbitMQConfig.COURSE_EXCHANGE,
                            RabbitMQConfig.KEY_COURSE_REMOVE,
                            Long.valueOf(column.getValue()),
                            new CorrelationData(UUID.randomUUID().toString()));
                    log.debug("发送删除课程{}消息",column.getValue());
                }
            });
        }else if("INSERT".equals(eventType.name()) || "UPDATE".equals(eventType.name())){
            //获得插入或更新后的数据
            rowData.getAfterColumnsList().forEach(column -> {
                if("id".equals(column.getName())){
                    //通过id查询课程的完整信息
                    System.out.println("保存的id是"+Long.valueOf(column.getValue()));
                    Course course = 
courseService.getCourseById(Long.valueOf(column.getValue()));
                    //保存时查到的数据
                    System.out.println("保存时查到的"+course);

                    //包装到Course对象中,转换为JSON
                    String json = JSON.toJSONString(course);
                    System.out.println("要传的json为"+json);


                    //发送给添加或更新队列

                   rabbitTemplate.convertAndSend(RabbitMQConfig.COURSE_EXCHANGE,
                            RabbitMQConfig.KEY_COURSE_SAVE,
                            json,
                            new CorrelationData(UUID.randomUUID().toString()));


                    log.debug("发送保存课程消息{}",json);
                }
            });
        }
    }
}

消息的接收者
(删除的接收没有写,因为删除不要判定头部来确定是不是重复消息)

@Slf4j
@Component
public class CourseMQListener {

    @Autowired
    private CourseIndexService courseIndexService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 处理保存课程消息
     * @param data
     * @param channel
     * @param message
     */
    @RabbitListener(queues = {RabbitMQConfig.QUEUE_COURSE_SAVE})
    public void handleSaveCourse(String data, Channel channel, Message message){
        System.out.println("收到保存课程:" + data);
        System.out.println("收到的message:"+message);
        //获得消息的id
        String id = message.getMessageProperties().getHeader("spring_returned_message_correlation");
        System.out.println("消息的id:"+id);
        //使用setNx名保存键,不存在就消费,存在就不消费
        ValueOperations ops = redisTemplate.opsForValue();
        try {
            if(ops.setIfAbsent(id,"0",100L, TimeUnit.SECONDS)){
                log.info("id{}不存在,执行业务",id);
                //如果为true,代表键不存在,设置成功
                //将json转换为课程对象
                Course course = JSON.parseObject(data, Course.class);
                courseIndexService.saveCourse(course);
                //处理完后,设置为1代表业务完成
                ops.set(id,"1",100L,TimeUnit.SECONDS);
                //手动确认消息处理完成 参数1 消息的标识 参数2 是否批量确认
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
                log.info("课程保存完成:{}",course);

            }else{
                //如果为false,代表键存在,不消费
                log.info("id{}存在,不执行业务",id);
                if("1".equals(ops.get(id))){
                    log.info("业务已经执行完");
                    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
                }
            }
        } catch (IOException e) {
            log.error("保存课程出现异常",e);
        }
    }

}

以上是一个很常规的消息发送
控制台输出如下

 发现
 

        //获得消息的id
        String id = message.getMessageProperties().getHeader("spring_returned_message_correlation");

结果为null,但是我们打印

UUID.randomUUID().toString()

是没有问题的,按理说发送消息的时候应该被携带上去了
继续检查 Message

 结果发现头部为空,找了半天也没有找到解决方案,
可能是版本的原因,我搜到了可以手动设置请求头
 

import org.springframework.amqp.core.*;

String id =UUID.randomUUID().toString();
System.out.println("生成的表示符是"+id);

MessageProperties properties = new MessageProperties();
properties.getHeaders().put("id", id);
Message message = new Message(json.getBytes(), properties);

发送的时候我们发message,不是发json

我本来想把json直接放前面

Message message = new Message(json, properties);

结果前面参数必须是数组
我还在诧异,接收端怎么获得数据

 message的确是没有内容(毕竟打印的是数组),但有了头部
而且从,data获得了数据
 

String id = message.getMessageProperties().getHeader("spring_returned_message_correlation");

这个语句没有获得头部,因为头部是我们自定义的
 

String id = message.getMessageProperties().getHeaders().get("id").toString();
System.out.println("自定义头部字段 id 的值为:" + id);

这样就能获取了
centos 7 安装RabbitMQ并用java代码实战RabbitMQ的五种消息模型_第64张图片

这样就能正常处理信息了
 方法修改后的代码

@Slf4j
@CanalEventListener
public class CourseCanalListener {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private CourseService courseService;

    /**
     * 监听 edu_course数据库的course表
     */
    @ListenPoint(schema = "edu_course",table = "course")
    public void handleCourseUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData){
        //判断操作的类型
        if("DELETE".equals(eventType.name())){
            //遍历删除前数据行的每一列
            rowData.getBeforeColumnsList().forEach(column -> {
                //获得删除前的ID
                if("id".equals(column.getName())){
                    //发删除消息给处理删除的队列 CorrelationData可以消息的id
                    rabbitTemplate.convertAndSend(RabbitMQConfig.COURSE_EXCHANGE,
                            RabbitMQConfig.KEY_COURSE_REMOVE,
                            Long.valueOf(column.getValue()),
                            new CorrelationData(UUID.randomUUID().toString()));
                    log.debug("发送删除课程{}消息",column.getValue());
                }
            });
        }else if("INSERT".equals(eventType.name()) || "UPDATE".equals(eventType.name())){
            //获得插入或更新后的数据
            rowData.getAfterColumnsList().forEach(column -> {
                if("id".equals(column.getName())){
                    //通过id查询课程的完整信息
                    System.out.println("保存的id是"+Long.valueOf(column.getValue()));
                    Course course = courseService.getCourseById(Long.valueOf(column.getValue()));
                    //保存时查到的数据
                    System.out.println("保存时查到的"+course);
                    //包装到Course对象中,转换为JSON
                    String json = JSON.toJSONString(course);
                    System.out.println("要传的json为"+json);

                    String id =UUID.randomUUID().toString();
                    System.out.println("生成的表示符是"+id);

                    MessageProperties properties = new MessageProperties();
                    properties.getHeaders().put("id", id);
                    Message message = new Message(json.getBytes(), properties);
//                    rabbitTemplate.convertAndSend(RabbitMQConfig.COURSE_EXCHANGE,
//                            RabbitMQConfig.KEY_COURSE_SAVE,
//                            message,
//                            new CorrelationData(UUID.randomUUID().toString()));
                    //发送给添加或更新队列
                    rabbitTemplate.convertAndSend(RabbitMQConfig.COURSE_EXCHANGE,
                            RabbitMQConfig.KEY_COURSE_SAVE,
                            message,
                            new CorrelationData(id)
                    );
                    log.debug("发送保存课程消息{}",message);
                }
            });
        }
    }
}
@RabbitListener(queues = {RabbitMQConfig.QUEUE_COURSE_SAVE})
    public void handleSaveCourse(String data, Channel channel, Message message){
        System.out.println("收到保存课程:" + data);
        System.out.println("收到的message:"+message);
        //获得消息的id
//        String id = message.getMessageProperties().getHeader("spring_returned_message_correlation");
//        System.out.println("消息的id:"+id);
        String id = message.getMessageProperties().getHeaders().get("id").toString();
        System.out.println("自定义头部字段 id 的值为:" + id);
        //使用setNx名保存键,不存在就消费,存在就不消费
        ValueOperations ops = redisTemplate.opsForValue();
        try {
            if(ops.setIfAbsent(id,"0",100L, TimeUnit.SECONDS)){
                log.info("id{}不存在,执行业务",id);
                //如果为true,代表键不存在,设置成功
                //将json转换为课程对象
                Course course = JSON.parseObject(data, Course.class);
                courseIndexService.saveCourse(course);
                //处理完后,设置为1代表业务完成
                ops.set(id,"1",100L,TimeUnit.SECONDS);
                //手动确认消息处理完成 参数1 消息的标识 参数2 是否批量确认
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
                log.info("课程保存完成:{}",course);

            }else{
                //如果为false,代表键存在,不消费
                log.info("id{}存在,不执行业务",id);
                if("1".equals(ops.get(id))){
                    log.info("业务已经执行完");
                    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
                }
            }
        } catch (IOException e) {
            log.error("保存课程出现异常",e);
        }
    }

你可能感兴趣的:(centos,rabbitmq,linux)