路由模式

在之前的文章中我们建立了一个简单的日志系统。我们可以通过这个系统将日志message广播给很多接收者。

在本篇文章中,我们在这之上,添加一个新的功能,即允许接收者订阅message的一个子集。举个例子,我们将日志分成多个级别,一个接收者接收错误日志将之保存到磁盘,另一个接收者接收所有日志将之打印到控制台。

Bindings

在前面的章节中,我们已经接触过binding了,像下面的代码这样:

channel.queueBind(queueName,EXCHANGE_NAME,"");

binding将exchange和queue关联在了一起。更形象的表示,如:queue对exchange中的message感兴趣。

bindings可以携带一个routingKey参数。为了避免和basic_publish的参数弄混,我们称之它为binding_key.我们像下面这样创建一个binding

channel.queueBind(queueName,EXCHANGE_NAME,"black");

binding key的作用要看exchange的类型,对于fanout类型的exchange,binding key是直接忽略的。

Direct Exchange

在之前的日志系统中,message会推送到所有的消费者去。我们想让系统依据message的日志级别进行过滤。比如一个消费者只接收严重级别的日志。

fanout无法帮我们实现这样的功能,它只是无脑的进行广播。

我们使用direct类型的exchange,它的路由算法是非常简单的 - 只要message的routing_key和bind的binding_key相同即进行转发。

为了进行说明,像下图这么来设置
图片描述
如图,可以看到有两个queue绑到了类型为direct的exchange上。第一个queue绑定用了orange这个binding key,第二个则用了black和green两个binding key。

那么结果就是有routing key为orange的message路由到了Q1.而routing key为black和green的message则路由到了Q2,其他的消息则被丢弃了。

Multiple Bindings

图片描述
若使用相同的binding key将多个queue绑定到exchange上,就和fanout的行为一样了,message会广播到binding key相同的queue去。如图的设置中,一个routing key为black的message就会同时发送到Q1和Q2。

Emitting logs

我们将在我们的日志系统上应用这个模型,使用direct类型的exchange去替代fanout类型的exchange。提供日志的严重性作为routing key。接收程序可以选择要接收日志的严重性级别。
首先我们创建exchange

channel.exchangeDeclare(EXCHANGE_NAME, "direct");

然后就是发送message

channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());

我们先假设severity取值 info | warning | error

Subscribing

接收message和上一章没什么区别,只是需要给各个severity创建新的binding。

String queueName = channel.queueDeclare().getQueue();

for(String severity : argv){
  channel.queueBind(queueName, EXCHANGE_NAME, severity);
}

开始执行

图片描述

EmitLogDirect.java代码如下

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

public class EmitLogDirect {

  private static final String EXCHANGE_NAME = "direct_logs";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    try (Connection connection = factory.newConnection();
         Channel channel = connection.createChannel()) {
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        String severity = getSeverity(argv);
        String message = getMessage(argv);

        channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8"));
        System.out.println(" [x] Sent '" + severity + "':'" + message + "'");
    }
  }
  //..
}

ReceiveLogsDirect.java代码如下:

import com.rabbitmq.client.*;

public class ReceiveLogsDirect {

  private static final String EXCHANGE_NAME = "direct_logs";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();

    channel.exchangeDeclare(EXCHANGE_NAME, "direct");
    String queueName = channel.queueDeclare().getQueue();

    if (argv.length < 1) {
        System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]");
        System.exit(1);
    }

    for (String severity : argv) {
        channel.queueBind(queueName, EXCHANGE_NAME, severity);
    }
    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 -> { });
  }
}

编译代码

javac -cp $CP ReceiveLogsDirect.java EmitLogDirect.java

如果想把warning和error的日志保存到文件去,那么

java -cp $CP ReceiveLogsDirect warning error > logs_from_rabbit.log

如果想把所有的日志打印到控制台,那么

java -cp $CP ReceiveLogsDirect info warning error

发送error日志

java -cp $CP EmitLogDirect error "Run.Run. Or it will explode"

沈子平
183 声望17 粉丝

慢慢积累,一切都不会太晚.