13

I have three clients each with their own RabbitMQ instances and I have an application (let's call it appA) that has its own RabbitMQ instance, the three client applications (app1, app2, app3) wants to make use of a service on appA.

The service on appA requires RPC communication, app1, app2 and app3 each has a booking.request queue and a booking.response queue.

enter image description here

With the shovel plugin, I can forward all booking.request messages from app1-3 to appA:

Shovel1 
virtualHost=appA, 
name=booking-request-shovel, 
sourceURI=amqp://userForApp1:password@app1-server/vhostForApp1
queue=booking.request
destinationURI=amqp://userForAppA:password@appA-server/vhostForAppA
queue=booking.request

setup another shovel to get booking requests from app2 and app3 to appA in the same way as above.

Now appA will respond to the request on the booking.response queue, I need the booking response message on rabbitMQ-appA to go back to the correct booking.response queue either on app1, app2 or app3, but not to all of them - how do I setup a shovel / federated queue on rabbitMQ-appA that will forward the response back to the correct rabbitMQ (app1, app2, app3) that is expecting a response in their own booking.response queue?

All these apps are using spring-amqp (in case that's relevant) Alternatively, I could setup a rabbitMQ template in Spring that listens to multiple rabbitMQ queues and consumes from each of them.

From the docs, this what a typical consumer looks like:

<rabbit:listener-container connection-factory="rabbitConnectionFactory">
    <rabbit:listener queues="some.queue" ref="somePojo" method="handle"/>
</rabbit:listener-container>

Is it possible to specify multiple connection factories in order to do this even if the connection factories are to the same instance of RabbitMQ, but just different vhosts:

enter image description here

Update:

Based on Josh's answer, I'd have multiple connection factories:

 <rabbit:connection-factory
                id="connectionFactory1"
                port="${rabbit.port1}"
                virtual-host="${rabbit.virtual1}"
                host="${rabbit.host1}"
                username="${rabbit.username1}"
                password="${rabbit.password1}"
                connection-factory="nativeConnectionFactory" />

 <rabbit:connection-factory
                id="connectionFactory2"
                port="${rabbit.port2}"
                virtual-host="${rabbit.virtual2}"
                host="${rabbit.host2}"
                username="${rabbit.username2}"
                password="${rabbit.password2}"
                connection-factory="nativeConnectionFactory" />

Then I would use the SimpleRoutingConnectionFactory to wrap both connection-factories:

<bean id="connectionFactory" class="org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory">
    <property name="targetConnectionFactories">
        <map>
            <entry key="#{connectionFactory1.virtualHost}" ref="connectionFactory1"/>
            <entry key="#{connectionFactory2.virtualHost}" ref="connectionFactory2"/>
        </map>
    </property>
</bean>

Now when I declare my rabbitMQ template, I would point it to the SimpleRoutingConnectionFactory instead of the individual connection factories:

<rabbit:template id="template" connection-factory="connectionFactory" />

... and then use the template as I would normally use it ...

<rabbit:listener-container
        connection-factory="connectionFactory"
        channel-transacted="true"
        requeue-rejected="true"
        concurrency="${rabbit.consumers}">
        <rabbit:listener queues="${queue.booking}" ref="TransactionMessageListener" method="handle"  />
</rabbit:listener-container>

// and messages are consumed from both rabbitMQ instances

... and ...

  @Autowired
  private AmqpTemplate template;

  template.send(getExchange(), getQueue(), new Message(gson.toJson(message).getBytes(), properties));

// and message publishes to both queues

Am I correct?

Cœur
  • 32,421
  • 21
  • 173
  • 232
Jan Vladimir Mostert
  • 10,270
  • 14
  • 68
  • 122

2 Answers2

8

Take a look at org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory. It will allow you to create multiple connection factories to different vhosts or different rabbitmq instances. We are using it for a multi tenant rabbitmq application.

Josh Chappelle
  • 1,449
  • 1
  • 12
  • 28
  • AbstractRoutingConnectionFactory looks promising, do you have an example of how you use it? What is determineCurrentLookupKey being used for? – Jan Vladimir Mostert Feb 23 '15 at 08:05
  • 1
    Here is the spring doc on it. [http://docs.spring.io/spring-amqp/docs/1.3.0.M1/reference/html/amqp.html#routing-connection-factory](http://docs.spring.io/spring-amqp/docs/1.3.0.M1/reference/html/amqp.html#routing-connection-factory). Let me know if you have any questions after reading that doc. – Josh Chappelle Feb 23 '15 at 15:57
  • Based on the doc, I have updated my question ... in short, the SimpleRoutingConnectionFactory wraps multiple connection factories and can then be used in the same way as a single connection factory, is that correct? – Jan Vladimir Mostert Feb 23 '15 at 19:50
  • If I can have multiple listener-containers and multiple amqp templates (I'm assuming spring allows that?) and use them either directly or via the SimpleRoutingConnectionFactory which combines them, then that solves my problem since that will allow me to operate on one vHost and selectively operate on other queues / exchanges on other vHosts at the same time (will only be able to test next week, knee deep in a dart project at the moment hence all the questions) – Jan Vladimir Mostert Feb 23 '15 at 20:08
  • Yea you can have multiple instances of SimpleMessageListenerContainer and multiple AmqpTemplate instances. But each is configured with one ConnectionFactory. If you have a fixed number of vhosts known at development time you should be able to write simple logic to determine which AmqpTemplate to use when sending a message. Then just have separate SimpleMessageListenerContainers for each vhost that you need. If you want one AmqpTemplate to work across multiple vhosts then you need AbstractRoutingConnectionFactory. – Josh Chappelle Feb 24 '15 at 01:27
  • I've got 5 foreseeable external projects that will integrate into the service and if it does get to a point where it becomes an unlimited number of projects, I'll probably avoid the XML configuration and do it with Java as that will give me more control. AbstractRoutingConnectionFactory thus solves my problem, thanks Josh! – Jan Vladimir Mostert Feb 24 '15 at 05:29
4

It's been awhile, but if you're using Spring you can create as many connection factories as you want, with their own configurations (host, user/pass, vhost, etc.), just like you did:

@Bean
@Primary
public ConnectionFactory amqpConnectionFactory1() {
    final CachingConnectionFactory connectionFactory = new CachingConnectionFactory();

    connectionFactory.setAddresses("...");
    connectionFactory.setUsername("...");
    connectionFactory.setPassword("...");
    connectionFactory.setVirtualHost("...");

    return connectionFactory;
}

@Bean
public ConnectionFactory amqpConnectionFactory2() {
    final CachingConnectionFactory connectionFactory = new CachingConnectionFactory();

    // ...

    return connectionFactory;
}

And your rabbit admin/template's as you go:

@Bean
@Primary
public RabbitAdmin rabbitAdmin1() {
    return new RabbitAdmin(amqpConnectionFactory1());
}

@Bean
public RabbitAdmin rabbitAdmin2() {
    return new RabbitAdmin(amqpConnectionFactory2());
}

// ...

@Bean
@Primary
public RabbitTemplate rabbitTemplate1() {
    RabbitTemplate rabbitTemplate = new RabbitTemplate(amqpConnectionFactory1());

    // ...

    return rabbitTemplate;
}

@Bean
public RabbitTemplate rabbitTemplate2() {
    RabbitTemplate rabbitTemplate = new RabbitTemplate(amqpConnectionFactory2());

    // ...

    return rabbitTemplate;
}

Note that you have to provide the @Primary tag to enable one main bean, once Spring doesn't know which one to choose when you don't inform the name explicitly.

With this in hands, just inject them normally along your components:

@Autowired
private RabbitTemplate template;

// ...

@Autowired
@Qualifier("rabbitTemplate2") // Needed when want to use the non-primary bean
private RabbitTemplate template;

Hope it helps! :)

bosco
  • 2,758
  • 1
  • 21
  • 29
  • How to implement org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback for different template – Ashish Feb 27 '19 at 10:07