交换器类型
在RabbitMQ中,生产者的消息不是直接发送到队列中,而是先发送交换器,由交换器通过规则路由到对应的队列中。
- 一个交换器可以绑定多个队列
- 特定的交换器上,一个队列能绑定多个路由键
- 一个队列上能绑定多个消费者,队列采用轮询的方式将消息发送给每个消费者。
RabbitMQ 一共有四种交换器类型,分别为 direct、fanout、topic、headers。其中headers类型几乎和direct功能一样,所以完全可以忽略。
Direct Exchange
- 路由键完全匹配,即发送消息时指定的RoutingKey与队列绑定到交换器上的Key要完全匹配。
注意:不要错误的认为队列中绑定的Key为“#”就能收到全部消息,在Direct交换器中“#”也只是一个普通的字符。
- RabbitMQ中包含一个默认的交换器,如下图。交换器的名字为空白字符串,图中显示 (AMQP default)
Fanout Exchange
- 不需要路由键,消息以广播形式发出,所有到绑定到交换机上的队列都会收到复制的消息。
- Fanout交换机转发消息是最快的
Topic Exchange
- 要求路由键与队列上绑定KEY的规则相匹配。
- 其中匹配规则中有两个特殊的标识符 "#" 和 "*","*" 表示匹配一个字符,"#" 表示匹配一个或多个字符。
- 消息 (usa.news) -> 队列 (usa.*)
- 消息 (usa.news) -> 队列(usa.#)
- 消息 (usa.news) -> 队列(#)
- 消息 (usa.news.it) -> 队列 (usa.*.it)
- 消息 (usa.news.it) -> 队列 (usa.*.*)
- 消息 (usa.news.it) -> 队列 (usa.#)
- 消息 (usa.news.it) -> 队列 (#.it)
- 消息 (usa.news.it) -> 队列 (#)
Java 代码实现
需求说明
有两种日志级别 INFO 和 ERROR 。
- 假设我们要做日志分析,由于日志格式的不同,需要把INFO日志存放 q_info 队列,Error日志存放到q_error队列。
- 假设有多个部门都要做日志分析,A部门用作日志做系统安全分析,B部门用作用户行为分析。A 部门使用 q_department_a队列,B部门使用q_department_b队列。
- 假设要收集多个系统的日志,其中包括订单系统、商品系统、库存系统等。只分析所有系统ERROR日志和商品系统的INFO日志。
针对以上几个需要我们可以分别用Direct 、Fanout 和 Topic 来实现。
Direct
需求1使用direct类型的交换器实现
日志生产者 DirectProducer
public class DirectProducer {
//交换器名称
public final static String EXCHANGE_NAME = "direct_log_exchange";
//定义日志级别
private static String[] logLevels = {"INFO","ERROR"};
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明交换器为Direct类型
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
for(String logLevel : logLevels){
for(int i=0;i<3;i++){
TimeUnit.MILLISECONDS.sleep(100);
String date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").format(new Date());
String message = date + " " + logLevel + ": this is log message";
System.out.println("Sent -> " + message);
//发送消息
channel.basicPublish(EXCHANGE_NAME, logLevel, null, message.getBytes());
}
}
channel.close();
connection.close();
}
输出结果如下:
Sent -> 2018-12-11T11:24:21.323 INFO: this is log message
Sent -> 2018-12-11T11:24:21.427 INFO: this is log message
Sent -> 2018-12-11T11:24:21.528 INFO: this is log message
Sent -> 2018-12-11T11:24:21.628 ERROR: this is log message
Sent -> 2018-12-11T11:24:21.728 ERROR: this is log message
Sent -> 2018-12-11T11:24:21.828 ERROR: this is log messag
日志消费者 ErrorLogDirectConsumer
public class ErrorLogDirectConsumer {
private static final String QUEUE_NAME = "q_error";
private static final String ROUTING_KEY = "ERROR";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明交换器,此处需要声明Direct类型
channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//此处需要绑定KEY为ERROR
channel.queueBind(QUEUE_NAME, DirectProducer.EXCHANGE_NAME, ROUTING_KEY);
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("["+envelope.getExchange()+"] ["+envelope.getRoutingKey() +"] ["+envelope.getDeliveryTag()+"] : "+ message);
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
输出结果如下:
[direct_log_exchange] [ERROR] [7] : 2018-12-11T11:24:21.628 ERROR: this is log message
[direct_log_exchange] [ERROR] [8] : 2018-12-11T11:24:21.728 ERROR: this is log message
[direct_log_exchange] [ERROR] [9] : 2018-12-11T11:24:21.828 ERROR: this is log message
此处仅打印出了ERROR日志,说明只有KEY绑定ERROR的队列收到了消息。
日志消费者 InfoLogDirectConsumer
与ERROR日志消费者一样,需要修改以下部分。
public class InfoLogDirectConsumer {
private static final String QUEUE_NAME = "q_info";
private static final String ROUTING_KEY = "INFO";
.......
}
输出结果如下:
[direct_log_exchange] [INFO] [7] : 2018-12-11T11:24:21.323 INFO: this is log message
[direct_log_exchange] [INFO] [8] : 2018-12-11T11:24:21.427 INFO: this is log message
[direct_log_exchange] [INFO] [9] : 2018-12-11T11:24:21.528 INFO: this is log message
此处仅打印出了INFO日志,说明只有KEY绑定为INFO的队列收到了消息。
Fanout
需求2使用fanout类型的交换器实现
生产者 FanoutProducer
public class FanoutProducer {
//交换器名称
public final static String EXCHANGE_NAME = "fanout_log_exchange";
//定义日志级别
private static String[] logLevels = {"INFO","ERROR"};
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明交换器,此处需要声明BuiltinExchangeType.FANOUT
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
for(String logLevel : logLevels){
for(int i=0;i<3;i++){
TimeUnit.MILLISECONDS.sleep(100);
String date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").format(new Date());
String message = date + " " + logLevel + ": this is log message";
System.out.println("Sent -> " + message);
channel.basicPublish(EXCHANGE_NAME, logLevel, null, message.getBytes());
}
}
channel.close();
connection.close();
}
输出结果如下:
Sent -> 2018-12-11T11:39:11.188 INFO: this is log message
Sent -> 2018-12-11T11:39:11.292 INFO: this is log message
Sent -> 2018-12-11T11:39:11.392 INFO: this is log message
Sent -> 2018-12-11T11:39:11.493 ERROR: this is log message
Sent -> 2018-12-11T11:39:11.593 ERROR: this is log message
Sent -> 2018-12-11T11:39:11.694 ERROR: this is log message
部门A消费者 DepartmentAFanoutConsumer
public class DepartmentAFanoutConsumer {
//定义队列名称
private static final String QUEUE_NAME = "q_department_a";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明交换器为Fanout类型
channel.exchangeDeclare(FanoutProducer.EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//此处绑定的key不起作用,但是参数要求必须填写,这里使用“”作为参数。
channel.queueBind(QUEUE_NAME, FanoutProducer.EXCHANGE_NAME, "");
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("["+envelope.getExchange()+"] ["+envelope.getRoutingKey() +"] ["+envelope.getDeliveryTag()+"] : "+ message);
}
};
//执行持续消费
channel.basicConsume(QUEUE_NAME, true, consumer);
}
输出结果如下:
[fanout_log_exchange] [INFO] [1] : 2018-12-11T11:39:11.188 INFO: this is log message
[fanout_log_exchange] [INFO] [2] : 2018-12-11T11:39:11.292 INFO: this is log message
[fanout_log_exchange] [INFO] [3] : 2018-12-11T11:39:11.392 INFO: this is log message
[fanout_log_exchange] [ERROR] [4] : 2018-12-11T11:39:11.493 ERROR: this is log message
[fanout_log_exchange] [ERROR] [5] : 2018-12-11T11:39:11.593 ERROR: this is log message
[fanout_log_exchange] [ERROR] [6] : 2018-12-11T11:39:11.694 ERROR: this is log message
部门B消费者 DepartmentBFanoutConsumer
public class DepartmentBFanoutConsumer {
//定义队列名称
private static final String QUEUE_NAME = "q_department_b";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明交换器为Fanout类型
channel.exchangeDeclare(FanoutProducer.EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//此处绑定的key不起作用,但是参数要求必须填写,这里使用“”作为参数。
channel.queueBind(QUEUE_NAME, FanoutProducer.EXCHANGE_NAME, "");
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("["+envelope.getExchange()+"] ["+envelope.getRoutingKey() +"] ["+envelope.getDeliveryTag()+"] : "+ message);
}
};
//执行持续消费
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
输出结果如下:
[fanout_log_exchange] [INFO] [1] : 2018-12-11T11:39:11.188 INFO: this is log message
[fanout_log_exchange] [INFO] [2] : 2018-12-11T11:39:11.292 INFO: this is log message
[fanout_log_exchange] [INFO] [3] : 2018-12-11T11:39:11.392 INFO: this is log message
[fanout_log_exchange] [ERROR] [4] : 2018-12-11T11:39:11.493 ERROR: this is log message
[fanout_log_exchange] [ERROR] [5] : 2018-12-11T11:39:11.593 ERROR: this is log message
[fanout_log_exchange] [ERROR] [6] : 2018-12-11T11:39:11.694 ERROR: this is log message
从结果中可以看出部门A消费者和部门B消费者都打印出了所有消息。从而说明Fanout类型的交换器是以广播的方式发送消息。
Topic
需求3使用topic类型的交换器实现
生产者 TopicProducer
public class TopicProducer {
//交换器名称
public final static String EXCHANGE_NAME = "topic_log_exchange";
//定义日志级别
private static String[] logLevels = {"INFO","ERROR"};
//定义订单、商品和库存系统
private static String[] systems = {"ORDER","PRODUCT","INVENTORY"};
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明交换器为Topic类型
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
for(String logLevel : logLevels){
for(String system : systems){
TimeUnit.MILLISECONDS.sleep(100);
String routingKey = system + "." + logLevel;
String date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").format(new Date());
String message = date + " " + routingKey + ": this is log message";
System.out.println("Sent -> " + message);
//发送消息
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
}
}
channel.close();
connection.close();
}
}
输出结果如下:
Sent -> 2018-12-11T12:52:11.521 ORDER.INFO: this is log message
Sent -> 2018-12-11T12:52:11.623 PRODUCT.INFO: this is log message
Sent -> 2018-12-11T12:52:11.724 INVENTORY.INFO: this is log message
Sent -> 2018-12-11T12:52:11.825 ORDER.ERROR: this is log message
Sent -> 2018-12-11T12:52:11.926 PRODUCT.ERROR: this is log message
Sent -> 2018-12-11T12:52:12.027 INVENTORY.ERROR: this is log message
商品系统INFO日志消费者 ProductInfoLogTopicConsumer
public class ProductInfoLogTopicConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(TopicProducer.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 创建一个随机队列
String queueName = channel.queueDeclare().getQueue();
// 此处绑定的KEY为PRODUCT.INFO
channel.queueBind(queueName, TopicProducer.EXCHANGE_NAME, "PRODUCT.INFO");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("[" + envelope.getExchange() + "] [" + envelope.getRoutingKey()
+ "] [" + envelope.getDeliveryTag() + "] : " + message);
}
};
// 执行持续消费
channel.basicConsume(queueName, true, consumer);
}
输出结果如下:
[topic_log_exchange] [PRODUCT.INFO] [1] : 2018-12-11T13:00:50.316 PRODUCT.INFO: this is log message
从结果中可以看出只消费了商品系统中的INFO日志。
所有系统ERROR日志消费者 SystemErrorLogTopicConsumer
public class SystemErrorLogTopicConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(TopicProducer.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 创建一个随机队列
String queueName = channel.queueDeclare().getQueue();
// 此处绑定的KEY为*.ERROR,可以设置为#.ERROR
channel.queueBind(queueName, TopicProducer.EXCHANGE_NAME, "*.ERROR");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("[" + envelope.getExchange() + "] [" + envelope.getRoutingKey()
+ "] [" + envelope.getDeliveryTag() + "] : " + message);
}
};
// 执行持续消费
channel.basicConsume(queueName, true, consumer);
}
}
输出结果如下:
[topic_log_exchange] [ORDER.ERROR] [1] : 2018-12-11T12:52:11.825 ORDER.ERROR: this is log message
[topic_log_exchange] [PRODUCT.ERROR] [2] : 2018-12-11T12:52:11.926 PRODUCT.ERROR: this is log message
[topic_log_exchange] [INVENTORY.ERROR] [3] : 2018-12-11T12:52:12.027 INVENTORY.ERROR: this is log message
从结果中可以看出消费了所有系统中的错误日志。