RabbitMQ Guide 5: Topic Exchange

In the previous chapter, we refined our logging system, replacing the fanout exchange with the direct exchange, allowing us to selectively receive messages. Still, there are limitations: routing based on multiple criteria is not possible. In our logging system, we may want to subscribe to logs not only based on the log level, but also based on the log source. This concept comes from the unix tool syslog, which can route logs not only by log level (info/warn/crit...), but also by device (auth/cron/kern...). This would be more flexible, we might want to only listen to error level logs from 'cron', while also receiving all level logs from 'kern'. If our log system wants to achieve this function, we need to use another exchange: topic exchange (Topic Exchange).

1. Topic Exchange

  A message sent to a topic exchange cannot have an arbitrary routing key, but must be a string of words separated by dots. These words can be arbitrary, but are usually some characteristic associated with the message. For example, the following are several valid routing keys: "stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit", the routing key can have many words, and the maximum limit is 255 bytes.

  The binding key must be the same as the routing key mode. The logic of the Topic exchange is somewhat similar to the direct exchange: a message sent with a specific routing key will be sent to all queues bound with a matching binding key, however, the binding key has two special cases, as follows:

  • "*" means match any word
  • "#" means match any one or more words

  The following diagram is a good representation of the usage of these two wildcards:

  In this example, we will send all animal related messages to a routing key consisting of three words, two dots, the first word for speed and the second word for speed Color, the third word indicates the kind:

  "<speed>.<colour>.<species>"。

  We create three bindings: queue Q1 is bound to the binding key *.orange.* and queue Q2 is bound to *.*.rabbit and lazy.#.

  To sum it up:

  • Queue Q1 is interested in all animals of orange color;
  • Cohort Q2 is interested in all rabbits and all lazy animals.

  A message with route "quick.orange.rabbit" will be forwarded to these two queues, and a message with route "lazy.orange.elephant" will also be forwarded to these two queues with route "quick.orange. "fox" messages will only be forwarded to the Q1 queue, and messages routed to "lazy.brown.fox" will only be forwarded to the Q2 queue. "lazy.pink.rabbit" is only forwarded to the Q2 queue once (although it matches the binding keys *.*.rabbit and lazy.#), messages routed to "quick.brown.fox" with either binding key does not match and will therefore be discarded.

  What if the route of the message we sent was one word "orangle" or 4 words "quick.orangle.male.rabbit"? will be discarded because it does not match any of the binding keys.

  On the other hand, messages with route "lazy.orange.male.rabbit" will be forwarded to the Q2 queue because they match the "lazy.#" binding key.

  Topic exchanges are very powerful and work like other types of exchanges:

  When a queue's binding key is "#", it will receive all messages regardless of the routing key of the received message, just like a fanout exchange;

  When a queue's binding key does not use "#" and "*", it works like a direct exchange again.

2. Complete code

  Below is the complete code to use the Topic switch in our logging system, the route of the log message we want to send consists of two words: "<facility>.<severity>".

  EmitLogTopic.java

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

public class EmitLogTopic {

    private final static String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] args) 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 message = "A critical kernel error";
            String routingKey = "kern.critical";

            channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes("utf-8"));

            System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
        }
    }
}

 ReceiveLogsTopic.java

import com.rabbitmq.client.*;

public class ReceiveLogsTopic {

    private final static String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] args) throws Exception {

        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();

        if (args.length < 1) {
            System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
            System.exit(1);
        }

        for (String bindingKey : args) {
            channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
        }

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" +
                    delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
    }
}

  Start four receivers and pass in the binding keys: #, kern. , .critical, kern.* *.critical.

  Start the producer: send a message with the route "kern.critical", the content of the message is: "A critical kernel error", check the reception status respectively:

  As you can see, the queues of all bound keys have received messages normally. 

3. SpringBoot implementation

   The project is as follows:

  1. Producers

application.properties

#RabbitMq
spring.rabbitmq.host=localhost
rabbitmq.exchange.topic=topic_logs2
rabbitmq.exchange.topic.routing.key=kern.critical

  EmitLogTopic.java

 1 import org.springframework.amqp.core.AmqpTemplate;
 2 import org.springframework.beans.factory.annotation.Autowired;
 3 import org.springframework.beans.factory.annotation.Value;
 4 import org.springframework.stereotype.Component;
 5 
 6 @Component
 7 public class EmitLogTopic {
 8 
 9     @Value("${rabbitmq.exchange.topic}")
10     private String exchangeName;
11 
12     @Value("${rabbitmq.exchange.topic.routing.key}")
13     private String routingKey;
14 
15     @Autowired
16     private AmqpTemplate template;
17 
18     public void sendMessage(Object message) {
19         System.out.println("发送消息:" + message);
20         template.convertAndSend(exchangeName,routingKey,message);
21     }
22 }

  EmitLogTopicRunner.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class EmitLogTopicRunner implements ApplicationRunner {

    @Autowired
    private EmitLogTopic emitLogTopic;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        emitLogTopic.sendMessage("A critical kernel error");
    }
}

2. Consumers

  application.properties

#RabbitMq
spring.rabbitmq.host=localhost
rabbitmq.exchange.topic=topic_logs2
rabbitmq.topic.queue=topic_queue
rabbitmq.exchange.topic.binding.key=kern.critical

server.port=8081

  ReceiveLogsTopic.java

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(
        bindings = @QueueBinding(
                value = @Queue(value = "${rabbitmq.topic.queue}",autoDelete = "true"),
                exchange = @Exchange(value = "${rabbitmq.exchange.topic}",type = ExchangeTypes.TOPIC),
                key = {"#","kern.*","*.critical"}
        )
)
public class ReceiveLogsTopic {

        @RabbitHandler
        public void reeive(Object message) {
                System.out.println("接收到消息:" + message);
        }
}

  Start to view console output:

  Producer produces output:

  Consumer output:

Pay attention, don't get lost, this is a public account that programmers want to pay attention to

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324136179&siteId=291194637