waitzkj 2019-07-01
在之前的文章中我们建立了一个简单的日志系统。我们可以通过这个系统将日志message广播给很多接收者。
在本篇文章中,我们在这之上,添加一个新的功能,即允许接收者订阅message的一个子集。举个例子,我们将日志分成多个级别,一个接收者接收错误日志将之保存到磁盘,另一个接收者接收所有日志将之打印到控制台。
在前面的章节中,我们已经接触过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是直接忽略的。
在之前的日志系统中,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,其他的消息则被丢弃了。
若使用相同的binding key将多个queue绑定到exchange上,就和fanout的行为一样了,message会广播到binding key相同的queue去。如图的设置中,一个routing key为black的message就会同时发送到Q1和Q2。
我们将在我们的日志系统上应用这个模型,使用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
接收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"