204、RabbitMQ 之 使用 topic 类型的 Exchange 实现通配符路由

目录

★ 使用topic实现通配符路由

▲ topic类型的Exchange支持在路由key中使用通配符,路由key一般由一个或者多个单词组成,多个单词之间以"."分割。

通配符支持星号(*)和井号(#):

复制代码
 *:匹配一个单词。
 #:匹配零个或多个单词。

Q1绑定的路由key模式为*.crazyit.*,
因此它可以匹配www.crazyit.org,www.crazyit.cn、edu.crazyit.org等路由key。

Q2绑定了两个key模式,其中*.org可以匹配crazyit.org、fkjava.org等路由key,
但不能匹配www.crazyit.org、www.fkjava.org等(*只能匹配一个单词);
而edu.#则可匹配edu.crazyit.org、edu.fkjava.org、edu.fkjava、edu.org等(#可匹配多个单词)。

代码演示

需求如图:演示通配符

复制代码
Exchange绑定queue的带通配符的key:
ROUTING_PATTERNS = {"*.crazyit.*", "*.org", "edu.#"};

发送消息到Exchange的路由key:
ROUTING_KEYS = {  "www.crazyit.org" ,  "www.crazyit.cn" , "edu.crazyit.org" , 
                  "crazyit.org"  ,      "fkjava.org" ,   "edu.crazyit.org" , 
                   "edu.fkjava"   ,     "edu.org"};


exchange 和 Q1 绑定的*.crazyit.*,符合的路由key有: 
    "www.crazyit.org"     "www.crazyit.cn"    "edu.crazyit.org"
    匹配到的key是3 个
    
exchange 和 Q2 绑定的 *.org"  和  "edu.#"  ,符合的路由key有: 
   *.org"  -->  "crazyit.org"  ,   "fkjava.org" ,    "edu.org"
   "edu.#" -->  "edu.crazyit.org" , "edu.crazyit.org"  , "edu.fkjava" ,  "edu.org"
   因为 匹配的key中有两个 "edu.org"  , 所以最终匹配到的key 是6个。

如图:

topic通配符类型的Exchange代码演示:

ConstantUtil

1、先设置一些常量

ConnectionUtil

2、RabbitMQ的相关连接

Producer

3、重点是消息生产者



Consumer01

消费者没啥好说的,两个消费者除了指定消费哪个消息队列不同外,其他代码都一样。

声明消息队列,

生产者和消费者声明消息队列的作用是一样的。

都是为了防止先启动消费者或者先启动生产者时,

对应的消息队列还没创建而导致消息被丢弃的问题。

个人理解:一般都是生产者需要声明消息队列,

消费者声明消息队列的影响不大

执行结果

与期望值一致

生产者

消费者01

复制代码
    exchange 和 Q1 绑定的*.crazyit.*,符合的路由key有: 
    "www.crazyit.org"     "www.crazyit.cn"    "edu.crazyit.org"
    匹配到的key是3 个

消费者02

复制代码
   exchange 和 Q2 绑定的 *.org"  和  "edu.#"  ,符合的路由key有: 
   *.org"  -->  "crazyit.org"  ,   "fkjava.org" ,    "edu.org"
   "edu.#" -->  "edu.crazyit.org" , "edu.crazyit.org"  , "edu.fkjava" ,  "edu.org"
   因为 匹配的key中有两个 "edu.org"  , 所以最终匹配到的key 是6个。

生产者或消费者声明 Exchagne的作用解释:

完整代码:

ConstantUtil

java 复制代码
package cn.ljh.rabbitmq.util;

//常量
public class ConstantUtil
{
    // ------------topic类型的Exchange,需要的相关常量----------
    public final static String QUEUET01 = "qt_01";
    public final static String QUEUET02 = "qt_02";

    // topic 通配符类型的 Exchange
    public static final String EXCHANGE_NAME_TOPIC = "myex03.topic";

    // Exchange 绑定 Queue 队列的路由key  ,通配符类型      *:匹配一个单词。#:匹配零个或多个单词。
    public static final String[] ROUTING_TOPIC_PATTERNS = {"*.crazyit.*", "*.org", "edu.#"};

    // 生产者发送消息给Excahnge携带的路由key
    public static final String[] ROUTING_TOPIC_KEYS = { "www.crazyit.org", "www.crazyit.cn",
            "edu.crazyit.org", "crazyit.org", "fkjava.org", "edu.fkjava.org", "edu.fkjava", "edu.org"};

    //-------------------------------------------------------

    // 消息队列的名称
    public final static String QUEUE01 = "queue_01";
    public final static String QUEUE02 = "queue_02";
    // Exchange的名称
    public static final String EXCHANGE_NAME = "myex02.direct";
    // 三个路由key定义成一个数组的名称
    public static final String[] ROUTING_KEYS = {"info", "error", "warning"};

}

ConnectionUtil

java 复制代码
package cn.ljh.rabbitmq.util;

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

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

//连接工具
public class ConnectionUtil
{
    //获取连接的方法
    public static Connection getConnection() throws IOException, TimeoutException
    {
        //创建连接工厂----这个ConnectionFactory源码可以看出有构造器,所以直接new一个出来
        ConnectionFactory connectionFactory =  new ConnectionFactory();
        //设置连接信息
        connectionFactory.setHost("localhost");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("ljh");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/"); //连接虚拟主机
        //从连接工厂获取连接
        Connection connection = connectionFactory.newConnection();
        //返回连接
        return connection;
    }
}

Producer

java 复制代码
package cn.ljh.rabbitmq.producer;

import cn.ljh.rabbitmq.util.ConnectionUtil;
import cn.ljh.rabbitmq.util.ConstantUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

//消息生产者--使用 topic 类型的exchange------就是通配符
public class Producer
{
    public static void main(String[] args) throws IOException, TimeoutException
    {
        //1、创建连接
        Connection conn = ConnectionUtil.getConnection();
        //2、通过Connection获取Channel。
        Channel channel = conn.createChannel();

        //3、调用exchangeDeclare()方法声明Exchange,--------调用queueDeclare()方法声明队列,并完成队列与Exchange的绑定
        channel.exchangeDeclare(ConstantUtil.EXCHANGE_NAME_TOPIC,/* Exchange名字 */
                BuiltinExchangeType.TOPIC,/* Exchange 类型--通配符 */
                true,/* 是否持久化 */
                false,/* 是否自动栅除 */
                false,/* 是否为内部的 Exchange */
                null /* 指定 Exchange 的额外属性 */
        );


        //调用queueDeclare()方法声明队列----声明多个消息队列------声明第1个消息队列---------路由key是 通配符 *.crazyit.*
        channel.queueDeclare(ConstantUtil.QUEUET01, true, false, false, null);


        //把 Exchange 和 Queue 绑定起来,绑定第一个消息队列,路由key是通配符
        channel.queueBind(ConstantUtil.QUEUET01, ConstantUtil.EXCHANGE_NAME_TOPIC,
                ConstantUtil.ROUTING_TOPIC_PATTERNS[0], /* exchange类型是 topic,这里指定路由key --> *.crazyit.*/
                null /* 指定 Exchange 的额外属性 */);

        //声明第2个消息队列--------这个exchange绑定这个queue,绑定了2个路由key---通配符: "*.org", "edu.#"
        channel.queueDeclare(ConstantUtil.QUEUET02, true, false, false, null);

        //用循环为第2个消息队列绑定2个路由key
        for (int i = 1; i < ConstantUtil.ROUTING_TOPIC_PATTERNS.length; i++)
        {
            //把 Exchange 和 Queue 绑定起来,绑定第2个消息队列
            channel.queueBind(
                    ConstantUtil.QUEUET02,
                    ConstantUtil.EXCHANGE_NAME_TOPIC,
                    ConstantUtil.ROUTING_TOPIC_PATTERNS[i]  /* 循环绑定路由key */,
                    null  /* 指定 Exchange 的额外属性 */);
        }


        //有几个路由key,生产者就发送几条消息
        for (int i = 0; i < ConstantUtil.ROUTING_TOPIC_KEYS.length; i++)
        {
            //获取路由key,用于下面动态指定路由key的代码简洁点
            String routingKey = ConstantUtil.ROUTING_TOPIC_KEYS[i];

            //要发送的消息
            String message = "生产者发送的第【 " + (i+1) + " 】条消息的内容";

            //4、调用Channel 的 basicPublish() 方法发送消息
            channel.basicPublish(
                    ConstantUtil.EXCHANGE_NAME_TOPIC /* 指定向这个Exchange发送消息 */,
                    routingKey /* 动态指定路由key */,
                    null /*指定额外的消息的属性*/,
                    message.getBytes(StandardCharsets.UTF_8)/*消息体必须是字节数组类型-->byte[]*/
            );
            System.out.println("生产者发送【 " + (i+1) + " 】条消息完成,发送到Exchange的路由key是:"+routingKey);
        }
        //5、关闭资源
        //关闭通道
        channel.close();
        //关闭连接
        conn.close();
    }
}

Consumer01

java 复制代码
package cn.ljh.rabbitmq.consumer;

import cn.ljh.rabbitmq.util.ConnectionUtil;
import cn.ljh.rabbitmq.util.ConstantUtil;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;
// 使用 RabbitMQ Java Client 开发 消息消费者 的大致步骤如下:
//(1)创建ConnectionFactory连接工厂,设置连接信息,再通过ConnectionFactory获取Connection连接。
//(2)通过Connection获取Channel。
//(3)根据需要、调用Channel的queueDeclare()方法声明队列,  Declare:声明、宣布
//    如果声明的队列已存在,该方法直接获取已有的队列;如果声明的队列还不存在,该方法将会创建新的队列。
//(4)调用Channel 的 basicConsume()方法开始处理消息,调用该方法时需要传入一个Consumer参数,该参数相当于JMS中的消息监听器。

//消息消费者1
public class Consumer01
{

    public static void main(String[] args) throws IOException, TimeoutException
    {
        //1、创建连接工厂,设置连接信息,然后再通过连接工厂获取连接
        Connection conn = ConnectionUtil.getConnection();

        //2、通过Connection获取Channel 消息通道
        Channel channel = conn.createChannel();

        //3、调用 Channel 的 queueDeclare() 方法声明队列,
        //   如果声明的队列已存在,该方法直接获取已有的队列;如果声明的队列还不存在,该方法将会创建新的队列
        channel.queueDeclare(ConstantUtil.QUEUET01, /* 声明的队列名 */
                true,    /* 消息队列是否持久化 */
                false,  /* 是否只允许该消息消费者消费该队列的消息,独占 */
                false, /* 是否自动删除 */
                null   /* 指定消息队列额外的属性 */);


        //4、调用Channel 的 basicConsume()方法开始处理消费消息
        channel.basicConsume(
                ConstantUtil.QUEUET01 /*消费这个消费队列里面的消息*/,
                true /*消息的确认模式:是否自动确认该消息已经被消费完成并返回确认消息给消息队列*/,
                new DefaultConsumer(channel)
                {
                    //处理消息:当这个消息队列收到消息的时候,这个方法就会被触发。重写这个方法:
                    @Override
                    public void handleDelivery(String consumerTag,
                                               Envelope envelope /*消息所在的信封,存放消息的exchange、路由key这些*/,
                                               AMQP.BasicProperties properties /*消息的那些属性*/,
                                               byte[] body /*body:消息的消息体*/) throws IOException
                    {
                        //把消息体中的消息拿出来
                        String message = new String(body, "UTF-8");
                        //printf:格式化输出函数   %s:输出字符串  %n:换行
                        System.err.printf("P2PConsumer收到来自Exchange为【%s】、路由key为【%s】的消息,消息内容为%s%n",
                                envelope.getExchange(),envelope.getRoutingKey(),message);

                    }
                }
        );
    }
}

Consumer02

java 复制代码
package cn.ljh.rabbitmq.consumer;

import cn.ljh.rabbitmq.util.ConnectionUtil;
import cn.ljh.rabbitmq.util.ConstantUtil;
import com.rabbitmq.client.*;

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

//消息消费者2
public class Consumer02
{
    public static void main(String[] args) throws IOException, TimeoutException
    {
        //1、创建连接工厂,设置连接信息,然后再通过连接工厂获取连接
        Connection conn = ConnectionUtil.getConnection();

        //2、通过Connection获取Channel 消息通道
        Channel channel = conn.createChannel();

        //3、调用 Channel 的 queueDeclare() 方法声明队列,
        //   如果声明的队列已存在,该方法直接获取已有的队列;如果声明的队列还不存在,该方法将会创建新的队列
        channel.queueDeclare(ConstantUtil.QUEUET02, /* 声明的队列名 */
                true,    /* 消息队列是否持久化 */
                false,  /* 是否只允许该消息消费者消费该队列的消息,独占 */
                false, /* 是否自动删除 */
                null   /* 指定消息队列额外的属性 */);

        //4、调用Channel 的 basicConsume()方法开始处理消费消息
        channel.basicConsume(
                ConstantUtil.QUEUET02 /*消费这个名字的消费队列里面的消息*/,
                true/*消息的确认模式:是否自动确认该消息已经被消费完成并返回确认消息给消息队列*/,
                new DefaultConsumer(channel)
                {
                    //处理消息:当这个消息队列收到消息的时候,这个方法就会被触发。重写这个方法:
                    @Override
                    public void handleDelivery(String consumerTag,
                                               Envelope envelope /*消息所在的信封,存放消息的exchange、路由key这些*/,
                                               AMQP.BasicProperties properties /*消息的那些属性*/,
                                               byte[] body /*body:消息的消息体*/) throws IOException
                    {
                        //把消息体中的消息拿出来
                        String message = new String(body, "UTF-8");
                        //printf:格式化输出函数   %s:输出字符串  %n:换行
                        System.err.printf("P2PConsumer收到来自Exchange为【%s】、路由key为【%s】的消息,消息内容为%s%n",
                                envelope.getExchange(),envelope.getRoutingKey(),message);
                    }
                }
        );
    }


}

pom.xml

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.ljh</groupId>
    <artifactId>topic</artifactId>
    <version>1.0.0</version>
    <name>topic</name>

    <!--  属性  -->
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>11</java.version>
    </properties>

    <!--  依赖  -->
    <dependencies>
        <!-- RabbitMQ 的依赖库 -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.13.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>


</project>
相关推荐
hycccccch1 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
陈平安Java and C8 小时前
RabbitMQ简单介绍和安装
rabbitmq
陈平安Java and C8 小时前
RabbitMQ应用2
rabbitmq
RainbowSea9 小时前
4. RabbitMQ 发布确认的配置详细说明
java·消息队列·rabbitmq
雨会停rain13 小时前
如何提高rabbitmq消费效率
分布式·rabbitmq
hycccccch13 小时前
RabbitMQ技术方案分析
数据库·rabbitmq
小五Z1 天前
RabbitMQ高级特性--发送方确认
rabbitmq
程序员 小柴1 天前
RabbitMQ概述和安装
分布式·rabbitmq
awei09162 天前
Linux系统安装RabbitMQ
linux·运维·rabbitmq·ruby
程序员 小柴2 天前
RabbitMQ死信队列
java·rabbitmq·java-rabbitmq