RabbitMQ四种交换器类型详解及示例

交换器类型 路由规则 路由键作用 代码示例 典型用例
Direct 精确匹配 (Routing Key == Binding Key) 关键,必须精确匹配 queueBind(queue, exchange, "exact.routing.key") 点对点任务分发,RPC
Fanout 广播,忽略路由键 无关 queueBind(queue, exchange, "") (忽略 routingKey) 发布订阅,广播通知
Topic 模式匹配 (*, #通配符) 关键,支持灵活模式 queueBind(queue, exchange, "pattern.*.key") 基于多维度属性的动态路由
Headers 根据消息头属性匹配 (x-match=all/any) 无关,使用 Headers 属性 queueBind(queue, exchange, "", headersArgs) 基于复杂属性的路由(不常用)

1. Direct(直连交换机)

交换器会将消息的 路由键 (Routing Key)​ 与队列绑定时使用的 绑定键 (Binding Key)​ 进行精确比较。如果两者完全匹配,消息就会被投递到该队列。

代码示例:

创建了一个直连交换机,生产者向不同 routingKey("error"、"info")发送消息。

启动两个消费者,分别绑定特定的 routingKey。

运行结果:三个消费者都会收到生产者发送的每一条消息

java 复制代码
public class DirectExchangeDemo {
    private static final String EXCHANGE_NAME = "direct_exchange";
    
    public static void main(String[] args) throws Exception {
        // 启动消费者
        new Thread(() -> startConsumer("error")).start();
        new Thread(() -> startConsumer("info")).start();
        
        Thread.sleep(1000);
        
        // 生产者发送消息
        startProducer();
    }
    
    static void startProducer() throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            
            // 发送到不同 routingKey 的消息
            String[] routingKeys = {"error", "info", "error", "info"};
            String[] messages = {"数据库错误", "用户登录", "文件读取失败", "系统启动"};
            
            for (int i = 0; i < messages.length; i++) {
                channel.basicPublish(EXCHANGE_NAME, routingKeys[i], null, messages[i].getBytes());
                System.out.println(" [x] Sent '" + routingKeys[i] + "':'" + messages[i] + "'");
            }
        }
    }
    
    static void startConsumer(String routingKey) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            String queueName = channel.queueDeclare().getQueue();
            
            // 队列绑定特定的 routingKey
            channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
            
            System.out.println(" [*] " + routingKey + " consumer waiting for messages...");
            
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                System.out.println(" [" + routingKey + " Consumer] Received: '" + message + "'");
            };
            
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2. Fanout(广播交换机)

会将发送到该交换器的所有消息无条件地路由到所有与它绑定的队列。忽略路由键。

就像广播或发布-订阅模式。一条消息会被发送给所有"订阅者"(所有绑定的队列)。

代码示例:

创建了一个广播交换机,生产者发送的消息会被广播到所有绑定队列。

启动三个消费者,都绑定到同一个扇出交换机。

运行结果:三个消费者都会收到生产者发送的每一条消息

java 复制代码
public class FanoutExchangeDemo {
    private static final String EXCHANGE_NAME = "fanout_exchange";
    
    public static void main(String[] args) throws Exception {
        // 启动多个消费者
        new Thread(() -> startConsumer("Consumer1")).start();
        new Thread(() -> startConsumer("Consumer2")).start();
        new Thread(() -> startConsumer("Consumer3")).start();
        
        Thread.sleep(1000);
        
        // 生产者发送消息
        startProducer();
    }
    
    static void startProducer() throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
            
            for (int i = 1; i <= 3; i++) {
                String message = "广播消息 #" + i;
                // Fanout 交换机会忽略 routingKey
                channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
                System.out.println(" [x] Sent: '" + message + "'");
            }
        }
    }
    
    static void startConsumer(String consumerName) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
            String queueName = channel.queueDeclare().getQueue();
            
            // 绑定到 Fanout 交换机,不需要 routingKey
            channel.queueBind(queueName, EXCHANGE_NAME, "");
            
            System.out.println(" [*] " + consumerName + " waiting for messages...");
            
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                System.out.println(" [" + consumerName + "] Received: '" + message + "'");
            };
            
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3. Topic(主题交换机)

使用通配符匹配 routingKey

*:匹配一个单词

#:匹配零个或多个单词

代码示例:

创建了一个主题交换机,使用通配符进行路由。

启动三个消费者,分别使用不同的绑定模式:

"news.sports.":匹配所有体育新闻
"news.tech.
":匹配所有科技新闻

"news.#":匹配所有新闻。

运行结果:消费者根据绑定模式选择性接收匹配的消息

java 复制代码
public class TopicExchangeDemo {
    private static final String EXCHANGE_NAME = "topic_exchange";
    
    public static void main(String[] args) throws Exception {
        // 启动不同模式的消费者
        new Thread(() -> startConsumer("news.sports.*", "体育新闻消费者")).start();
        new Thread(() -> startConsumer("news.tech.*", "科技新闻消费者")).start();
        new Thread(() -> startConsumer("news.#", "所有新闻消费者")).start();
        
        Thread.sleep(1000);
        
        // 生产者发送消息
        startProducer();
    }
    
    static void startProducer() throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            
            // 发送到不同主题的消息
            String[][] messages = {
                {"news.sports.basketball", "篮球"},
                {"news.sports.football", "足球"},
                {"news.tech.ai", "AI"},
                {"news.tech.blockchain", "区块链应用"},
                {"news.weather", "天气预报"}
            };
            
            for (String[] msg : messages) {
                String routingKey = msg[0];
                String message = msg[1];
                channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
                System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
            }
        }
    }
    
    static void startConsumer(String bindingKey, String consumerName) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            String queueName = channel.queueDeclare().getQueue();
            
            // 使用通配符绑定
            channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
            
            System.out.println(" [*] " + consumerName + " [" + bindingKey + "] waiting...");
            
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                String routingKey = delivery.getEnvelope().getRoutingKey();
                System.out.println(" [" + consumerName + "] Received '" + routingKey + "':'" + message + "'");
            };
            
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.Headers(头交换机)

忽略 routingKey,根据消息的 headers 属性进行匹配

x-match: all:必须匹配所有指定的 headers(AND 逻辑)

x-match: any:只需匹配任意一个指定的 headers(OR 逻辑)

代码示例:

创建一个头交换机,基于消息的 headers 属性进行路由。

启动两个消费者,使用不同的匹配策略:

"all"模式:必须匹配所有指定的 headers

"any"模式:只需匹配任意一个指定的 headers

运行结果:完全忽略 routingKey,消费者根据 headers 匹配条件选择性接收消息

java 复制代码
public class HeadersExchangeDemo {
    private static final String EXCHANGE_NAME = "headers_exchange";
    
    public static void main(String[] args) throws Exception {
        // 启动不同匹配条件的消费者
        new Thread(() -> startConsumer("all", createArgs("all", "alert", "high"))).start();
        new Thread(() -> startConsumer("any", createArgs("any", "type", "alert"))).start();
        
        Thread.sleep(1000);
        
        // 生产者发送消息
        startProducer();
    }
    
    static void startProducer() throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS);
            
            // 发送带有不同 headers 的消息
            Map<String, Object> headers1 = new HashMap<>();
            headers1.put("type", "alert");
            headers1.put("priority", "high");
            
            Map<String, Object> headers2 = new HashMap<>();
            headers2.put("type", "info");
            headers2.put("priority", "low");
            
            Map<String, Object> headers3 = new HashMap<>();
            headers3.put("type", "alert");
            headers3.put("priority", "medium");
            
            AMQP.BasicProperties props1 = new AMQP.BasicProperties.Builder().headers(headers1).build();
            AMQP.BasicProperties props2 = new AMQP.BasicProperties.Builder().headers(headers2).build();
            AMQP.BasicProperties props3 = new AMQP.BasicProperties.Builder().headers(headers3).build();
            
            // Headers 交换机会忽略 routingKey
            channel.basicPublish(EXCHANGE_NAME, "", props1, "高危警报".getBytes());
            channel.basicPublish(EXCHANGE_NAME, "", props2, "普通信息".getBytes());
            channel.basicPublish(EXCHANGE_NAME, "", props3, "中等警报".getBytes());
            
            System.out.println(" [x] Sent 3 messages with different headers");
        }
    }
    
    static void startConsumer(String consumerName, Map<String, Object> bindingArgs) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.HEADERS);
            String queueName = channel.queueDeclare().getQueue();
            
            // 使用 headers 进行绑定
            channel.queueBind(queueName, EXCHANGE_NAME, "", bindingArgs);
            
            System.out.println(" [*] " + consumerName + " consumer waiting...");
            
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                Map<String, Object> headers = delivery.getProperties().getHeaders();
                System.out.println(" [" + consumerName + " Consumer] Received headers " + headers + ": '" + message + "'");
            };
            
            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    static Map<String, Object> createArgs(String matchType, String key, String value) {
        Map<String, Object> args = new HashMap<>();
        args.put("x-match", matchType);  // "all" 或 "any"
        args.put(key, value);
        return args;
    }
}

5.maven引用

xml 复制代码
<dependencies>
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.16.0</version>
    </dependency>
</dependencies>

6.参考资料

《RabbitMQ实战指南》

相关推荐
开开心心就好9 分钟前
本地文件搜索工具,双击Ctrl就能呼出搜索
java·服务器·前端·支持向量机·ffmpeg·音视频·模拟退火算法
独隅13 分钟前
Claude 高效使用指南深度评测
java·开发语言·claude·claude code
ByteCoder.21 分钟前
班前十分钟的清单,按体检引导单来写
java·服务器·前端·助播虾
海的透彻23 分钟前
如何让docker容器启动时用自定义的用户
java·docker·dubbo
疯狂学习GIS27 分钟前
将谷歌地球引擎账号升级为Contributor与Partner层级的方法
java·数据库·人工智能
GG.WinGel39 分钟前
质数数目(蓝桥杯练习题)
java·数据结构·算法·青少年编程·蓝桥杯
泡泡糖的中文规格书44 分钟前
HDLbits 2.2 学习笔记 · 多路复用器
java·开发语言
John_Hui1 小时前
第 3 篇:配置 JDK 17 环境
java·开发语言·ubuntu
秋天的怀念_David1 小时前
算法一千零一夜 · 第十七夜 长街点灯
java·程序人生·算法
不吃鱼的羊1 小时前
如何应对恐惧
java