Spring AMQP空队列名问题:如何获取RabbitMQ生成的队列名?
场景与问题
我希望创建空名称队列,让RabbitMQ自动生成队列名称,代码如下:
var queue = QueueBuilder .durable("") .exclusive() .autoDelete().build(); var binding = BindingBuilder.bind(queue).to(exchange).with(bindingKey).noargs(); Declarables d = new Declarables(queue, binding);
但调用getActualName()返回的是:spring.gen-vuiRwjOmRkihAE8C72rbmw_awaiting_declaration
d.getDeclarablesByType(Queue.class).get(0).getActualName();
而RabbitMQ中的实际队列名称是:amq.gen-wpaYnybu9vOdD5v2ej66IQ
原因分析
查看Spring AMQP核心的Queue构造方法,可知当队列名称为空时,Spring会先生成一个临时名称:
public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments) { super(arguments); Assert.notNull(name, "'name' cannot be null"); this.name = name; this.actualName = StringUtils.hasText(name) ? name : (Base64UrlNamingStrategy.DEFAULT.generateName() + "_awaiting_declaration"); this.durable = durable; this.exclusive = exclusive; this.autoDelete = autoDelete; }
这个带_awaiting_declaration后缀的临时名称,是队列尚未被RabbitMQ实际声明时的标识。只有当队列被成功声明到RabbitMQ后,才能获取到RabbitMQ自动生成的真实名称。
使用自动命名队列的原因是为了避免竞争条件:
When auto-delete or exclusive queues use well-known (static) names, in case of client disconnection and immediate reconnection there will be a natural race condition between RabbitMQ nodes that will delete such queues and recovering clients that will try to re-declare them. This can result in client-side connection recovery failure or exceptions, and create unnecessary confusion or affect application availability.
补充场景与报错
我们通过RabbitAdmin bean初始化连接,调用d.setAdminsThatShouldDeclare(admin),代码如下:
public Declarables someEventsDeclarables( @Qualifier("rabbitAdmin") RabbitAdmin admin, @Qualifier("AmqpExchange") Exchange exchange ) { final var bindingKey = somePrefix +".*." +someSuffix; final var cfg = new OurEventsDeclarables( exchange, "", // no queue name - RabbitMq generates it bindingKey, true ); final var declarables = cfg.declarables(); for (Declarable d : declarables.getDeclarables()) { d.setAdminsThatShouldDeclare(admin); admin.declareQueue(); } return declarables; }
运行集成测试时出现报错:
org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[spring.gen-QUh8ffN0TimELGG_kF1wFw_awaiting_declaration]
可行解决方案
通过手动声明队列并获取真实名称,再设置到队列对象中,最后声明绑定。在其他测试场景中可能需要将Declarables和AutoAckConsumer标记为@MockBean:
public class AmqpConfig{ ... @Bean("someEventsDeclarables") public Declarables someEventsDeclarables( @Qualifier("rabbitAdmin") RabbitAdmin admin, @Qualifier("AmqpExchange") Exchange exchange ) { final var bindingKey = somePrefix +".*." +someSuffix; final var cfg = new OurEventsDeclarables( exchange, "", // no queue name - RabbitMq generates it bindingKey, true ); final var declarables = cfg.declarables(); /** declare queue and bindings */ final List<Queue> queues = declarables.getDeclarablesByType(Queue.class); final List<Binding> bindings = declarables.getDeclarablesByType(Binding.class); if (queues.size() == 0) { throw new BeanCreationException("Queue for empty-queue-name is not found"); } if (bindings.size() == 0) { throw new BeanCreationException("Binding for {} is not found of empty-queue-name", bindingKey); } Queue queue = queues.get(0); Binding binding = bindings.get(0); // 声明队列并获取RabbitMQ生成的真实名称 String declareQueue = admin.declareQueue(queue); // 将真实名称设置到队列对象 queue.setActualName(declareQueue); // 声明绑定 admin.declareBinding(binding); return declarables; }
内容的提问来源于stack exchange,提问作者Endre Moen

