RabbitMQ 4.1.1-Local random exchange体验

Local Random Exchange

一种 RabbitMQ 4.0+ 引入的新型交换机,主要是为 request-reply(RPC)场景 设计的。

  • 使用这种交换机时,消息只会被路由到本地节点上的队列,可以确保极低的消息发布延迟。
  • 如果有多个本地队列绑定到该交换机,它会随机选择一个队列接收消息。

关键点总结:

  • 本地传输:不会把消息发到其他节点的队列。
  • 随机选队列:多个本地队列中随机挑一个。
  • 发布快:避免了跨节点网络通信,延迟低。
  • 最适合用于 RPC 模式 ,即"请求-响应"

建议将 Local Random Exchange 和 exclusive(独占)队列搭配使用,这样可以为 RPC 场景提供更低延迟的组合。

注意

  • Exclusive 队列是 RabbitMQ 中只对某个连接开放的临时队列(通常用于响应)。
  • LRE + Exclusive Queue,可以避免消息在集群中转发,提高响应速度。

LRE 不转发消息到其他节点,所以如果当前节点没有合适的队列,消息会被直接丢弃!

所以使用时你必须确保每个节点上都至少有一个消费者绑定的队列

在 RabbitMQ 前面加负载均衡器(load balancer)会让这种交换机类型几乎无法正常工作。

原因分析

  • Local Random Exchange 依赖于消息被投递到"本地绑定队列(local queues)"
  • 如果用了负载均衡,客户端连接可能随机落在任何节点上,消息将发给该节点的本地队列
  • 如果该节点上没有消费者绑定本地队列,消息就会被丢弃

实操如下
application.properties

bash 复制代码
# JVM内存配置
# 设置较小的堆内存,避免占用过多系统资源
spring.jvm.memory=-Xmx256m -Xms128m -XX:MaxMetaspaceSize=128m

# 设置较小的线程栈大小
spring.jvm.thread-stack-size=-Xss256k

# 启用GC日志,帮助诊断内存问题
spring.jvm.gc-log=-Xlog:gc*:file=./logs/gc.log:time,uptime,level,tags:filecount=5,filesize=10m

# 设置较小的代码缓存大小
spring.jvm.code-cache=-XX:ReservedCodeCacheSize=128m

# 启用内存压缩指针基址设置,将Java堆放在4GB以上地址空间
spring.jvm.heap-base=-XX:HeapBaseMinAddress=4g

# 启用G1垃圾收集器的更积极设置
spring.jvm.gc-tuning=-XX:G1ReservePercent=10 -XX:G1HeapRegionSize=4m -XX:InitiatingHeapOccupancyPercent=30

# 禁用显式GC调用
spring.jvm.disable-explicit-gc=-XX:+DisableExplicitGC

application.yml

yaml 复制代码
#定义要使用的交换机和队列名称
spring:
  application:
    name: local-random-exchange
  #配置连接 rabbitmq服务器
  rabbitmq:

    #mq服务器的ip
    host: 127.0.0.1
    #访问端口号
    port: 5672
    #用户名称
    username: admin
    #密码
    password: 123456
    #虚拟主机
    virtual-host: my-virtual-host
    # 连接超时时间
    connection-timeout: 10000


# 日志配置
logging:
  level:
    org.springframework.amqp: INFO     # AMQP日志级别
    com.example: DEBUG                 # 应用日志级别
java 复制代码
package com.example.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * RabbitMQ 配置类,用于创建 Local Random Exchange(本地随机交换机)和绑定的 RPC 队列。
 * 本配置主要用于实现基于 RabbitMQ 的 RPC 模式,使用 Local Random Exchange 类型降低延迟。
 */
@Configuration
public class RabbitConfig {

    // Local Random Exchange 名称(自定义交换机)
    public static final String LRE_EXCHANGE = "lre.exchange";

    // RPC 使用的队列名称
    public static final String RPC_QUEUE_NAME = "rpc.queue";

    /**
     * 声明一个 Local Random Exchange(x-local-random 类型的交换机)。
     *
     * - durable: true 表示交换机会持久化
     * - autoDelete: false 表示不会在没有绑定队列时自动删除
     * - arguments: 可选参数,此处为空
     */
    @Bean
    public CustomExchange lreExchange() {
        Map<String, Object> args = new HashMap<>();
        return new CustomExchange(LRE_EXCHANGE, "x-local-random", true, false, args);
    }

    /**
     * 声明一个 RPC 队列。
     *
     * - durable: false 表示不持久化(重启后丢失)
     * - exclusive: false 表示不是只被当前连接独占
     * - autoDelete: true 表示连接断开后自动删除队列
     *
     * 如果你要模拟 RPC Client 的 exclusive 回调队列,建议用 `exclusive = true`。
     */
    @Bean
    public Queue rpcQueue() {
        return new Queue(RPC_QUEUE_NAME, false, false, true);
    }

    /**
     * 将 RPC 队列绑定到 Local Random Exchange 上。
     *
     * - routingKey 设置为 "",因为 Local Random Exchange 不关心路由键
     */
    @Bean
    public Binding binding(Queue rpcQueue, CustomExchange lreExchange) {
        return BindingBuilder.bind(rpcQueue).to(lreExchange).with("").noargs();
    }
}

方式一、手动监听

java 复制代码
package com.example.product;

import com.example.config.RabbitConfig;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.UUID;

/**
 * 模拟 RPC 客户端,用于通过 RabbitMQ 的 Local Random Exchange 发送请求并接收异步响应。
 */
@Component
public class RpcClient {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 向服务器发送请求,并设置回调队列接收响应。
     *
     * @param message 请求消息内容
     * @return 返回一个确认字符串(实际响应在回调中处理)
     */
    public String sendRequest(String message) throws Exception {
        // 生成唯一标识 correlationId,用于标识请求-响应配对
        String correlationId = UUID.randomUUID().toString();

        // 临时生成一个独占的匿名回调队列(例如 amq.gen-xxxxxx)
        String replyQueue = rabbitTemplate.execute(channel -> channel.queueDeclare().getQueue());

        // 设置 RabbitTemplate 的回调地址(其实不会生效于 send 模式,仅用于演示)
        rabbitTemplate.setReplyAddress(replyQueue);
        rabbitTemplate.setReplyTimeout(5000); // 设置超时时间(ms)
        rabbitTemplate.setCorrelationKey("correlation_id"); // 设置用于匹配的属性名(可选)

        // 设置监听器容器,监听回调队列中的响应消息
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(rabbitTemplate.getConnectionFactory());
        container.setQueueNames(replyQueue); // 指定监听的队列
        container.setMessageListener(new MessageListenerAdapter(new Object() {
            // 定义接收到消息后的处理方法(方法名必须与监听器默认匹配或显式指定)
            @SuppressWarnings("unused")
            public void handleMessage(byte[] reply) {
                String response = new String(reply, StandardCharsets.UTF_8);
                System.out.println("Got reply: " + response);
                // 实际中这里应唤醒等待线程或放入响应Map中(基于 correlationId)
            }
        }));
        container.start(); // 启动监听器容器

        // 构造请求消息,设置 reply_to 和 correlation_id 属性
        MessageProperties props = new MessageProperties();
        props.setReplyTo(replyQueue);             // 告诉服务端响应要发到这个队列
        props.setCorrelationId(correlationId);    // 服务端会原样返回,用于客户端识别对应响应

        Message request = new Message(message.getBytes(), props);

        // 通过 RabbitTemplate 发送消息到本地随机交换机(Local Random Exchange)
        rabbitTemplate.send(RabbitConfig.LRE_EXCHANGE, "", request);

        return "Request sent with correlationId: " + correlationId;
    }
}

方式二、推荐写法
也可以用使用 Spring AMQP 的官方推荐 RPC 模式(即 convertSendAndReceive())的实现方式。这种方式完全利用了 RabbitTemplate 的自动 reply-to、correlationId、超时机制 ------ 更加简单可靠

java 复制代码
package com.example.product;

import com.example.config.RabbitConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class RpcClient {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public String sendRequest(String message) {
        // 设置超时时间(可选)
        rabbitTemplate.setReplyTimeout(5000);

        // 使用 convertSendAndReceive 会自动:
        // 1. 创建一个临时 reply queue(exclusive)
        // 2. 设置 reply_to 和 correlation_id
        // 3. 等待结果并返回
        Object response = rabbitTemplate.convertSendAndReceive(RabbitConfig.LRE_EXCHANGE, "", message);

        if (response != null) {
            return "Received response: " + response.toString();
        } else {
            return "No response received (timeout or error)";
        }
    }
}

两者优点总结

功能 原来方式(手动监听) convertSendAndReceive()(推荐)
reply_to自动处理 ❌ 手动 ✅ 自动
correlation_id 匹配 ❌ 手动 ✅ 自动
超时控制 ❌ 复杂 ✅ 简单
代码复杂度
推荐程度 ✅✅✅

RPC服务端处理

方式一 手动

java 复制代码
package com.example.consumer;

import com.example.config.RabbitConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class RpcServer {

    /**
     * RabbitMQ RPC 服务端处理方法
     * 
     * 使用 @RabbitListener 监听指定队列,当接收到客户端请求时,手动获取 reply_to 和 correlation_id,
     * 并通过底层 channel 手动发送响应消息。
     *
     * @param message        收到的消息正文
     * @param correlationId  唯一标识此次 RPC 请求的 ID(由客户端生成并设置)
     * @param replyTo        回调队列(客户端临时队列)
     * @param requestMessage 原始 AMQP 消息对象
     * @param channel        底层通信通道,用于手动发送响应
     * @return null(返回值不会被用来发送响应,因为我们是手动发送的)
     */
    @RabbitListener(queues = RabbitConfig.RPC_QUEUE_NAME)
    public String handleRpc(String message,
                            @Header(AmqpHeaders.CORRELATION_ID) String correlationId,
                            @Header(AmqpHeaders.REPLY_TO) String replyTo,
                            Message requestMessage,
                            Channel channel) throws IOException {

        // 构造服务端响应内容
        String response = "Processed: " + message;

        // 打印收到的信息和即将回应的队列
        System.out.println("replyTo: " + replyTo + ", Server received: " + message + ", correlationId: " + correlationId);

        // 构造响应消息的属性,确保带上原始 correlationId
        MessageProperties replyProps = new MessageProperties();
        replyProps.setCorrelationId(correlationId);

        // 构造响应消息对象
        Message reply = new Message(response.getBytes(), replyProps);

        // 手动发送响应消息到客户端指定的临时队列
        channel.basicPublish("", replyTo, null, reply.getBody());

        // 因为手动处理了响应,不需要 Spring 自动回传
        return null;
    }
}

方式二自动处理

java 复制代码
@RabbitListener(queues = RabbitConfig.RPC_QUEUE_NAME)
public String handleRpc(String message) {
    System.out.println("Server received: " + message);
    return "Processed: " + message;
}

运行结果

bash 复制代码
Request sent with correlationId: 9cf6df25-3e02-47da-96ad-23a21791b391
replay:amq.gen-CcSRdsuLJtjtXOzFUE3Eug Server received: 0测试0 correlationId:9cf6df25-3e02-47da-96ad-23a21791b391
Got reply: Processed: 0测试0
Request sent with correlationId: d1477ff0-84dd-4bf6-ba8d-d8b613fbcadc
replay:amq.gen-jnFzJQallOE6QRkZEZyn-Q Server received: 3测试1 correlationId:d1477ff0-84dd-4bf6-ba8d-d8b613fbcadc
Got reply: Processed: 3测试1
Request sent with correlationId: 2009671b-ef8d-418c-ae9b-c58c8e0dac83
replay:amq.gen--tLpLz3xs9p_BEZmqJUjFg Server received: 6测试2 correlationId:2009671b-ef8d-418c-ae9b-c58c8e0dac83
Got reply: Processed: 6测试2
Request sent with correlationId: 6637a3dd-4e24-48e5-871f-cd671ea6d9b6
replay:amq.gen-CejNGqwNk6bWPkxrQLvH7Q Server received: 9测试3 correlationId:6637a3dd-4e24-48e5-871f-cd671ea6d9b6
Got reply: Processed: 9测试3
Request sent with correlationId: c994fab1-75c4-4618-8af8-b03f2fcdfa6f
replay:amq.gen-mdKE_hhHhj_ZEgT-fIm4nw Server received: 12测试4 correlationId:c994fab1-75c4-4618-8af8-b03f2fcdfa6f
Got reply: Processed: 12测试4
Request sent with correlationId: b27d1409-d595-47f8-b920-2d4ad23288d2
replay:amq.gen-ofZgztMXNh9MMEejK6DDGA Server received: 15测试5 correlationId:b27d1409-d595-47f8-b920-2d4ad23288d2
Got reply: Processed: 15测试5
Request sent with correlationId: adc98f0d-5270-4033-86c0-e863cd56ecee
replay:amq.gen-xKkf-7LcEhOzamv892nL8A Server received: 18测试6 correlationId:adc98f0d-5270-4033-86c0-e863cd56ecee
Got reply: Processed: 18测试6
Request sent with correlationId: 87f6722d-e974-474d-a79c-9aea69401fa7
replay:amq.gen-r5jjy4ypnSDso-HZ5PuNWA Server received: 21测试7 correlationId:87f6722d-e974-474d-a79c-9aea69401fa7
Got reply: Processed: 21测试7
Request sent with correlationId: de2a03f0-9d78-4dd8-b29d-3e904b4bb1dd
replay:amq.gen-7QDoBB5wqbjLC0MidVSkbA Server received: 24测试8 correlationId:de2a03f0-9d78-4dd8-b29d-3e904b4bb1dd
Got reply: Processed: 24测试8
Request sent with correlationId: 1ce9cc12-0b24-4a19-9828-2a0dbc5ab4bc
replay:amq.gen-1rFRnN9vKCUt6HIrRLSoBw Server received: 27测试9 correlationId:1ce9cc12-0b24-4a19-9828-2a0dbc5ab4bc
Got reply: Processed: 27测试9



相关推荐
何苏三月36 分钟前
SpringCloud系列 - Seata 分布式事务(六)
分布式·spring·spring cloud
工藤学编程1 小时前
分库分表之实战-sharding-JDBC绑定表配置实战
数据库·分布式·后端·sql·mysql
gtestcandle1 小时前
rabbitmq 的多用户、多vhost使用
分布式·rabbitmq
Du_XiaoNan2 小时前
RabbitMQ第一章(MQ基础与RocketMQ使用手册)
rabbitmq·java-rocketmq·java-rabbitmq
老纪的技术唠嗑局2 小时前
单机分布式一体化数据库的架构设计与优化
数据库·分布式
SkyrimCitadelValinor2 小时前
Git【开源分布式版本控制工具】安装-配置-常用指令-Git远程仓库-IDEA使用Git
分布式·git·gitee·开源·项目管理
大咖分享课8 小时前
深度剖析:Ceph分布式存储系统架构
分布式·ceph·架构·分布式存储