RabbitMQ之交换器类型

交换器类型

在RabbitMQ中,生产者的消息不是直接发送到队列中,而是先发送交换器,由交换器通过规则路由到对应的队列中。

  • 一个交换器可以绑定多个队列
  • 特定的交换器上,一个队列能绑定多个路由键
  • 一个队列上能绑定多个消费者,队列采用轮询的方式将消息发送给每个消费者。

RabbitMQ 一共有四种交换器类型,分别为 direct、fanout、topic、headers。其中headers类型几乎和direct功能一样,所以完全可以忽略。

Direct Exchange

 

  • 路由键完全匹配,即发送消息时指定的RoutingKey与队列绑定到交换器上的Key要完全匹配。

注意:不要错误的认为队列中绑定的Key为“#”就能收到全部消息,在Direct交换器中“#”也只是一个普通的字符。

 

  • RabbitMQ中包含一个默认的交换器,如下图。交换器的名字为空白字符串,图中显示 (AMQP default) 

Fanout Exchange

  • 不需要路由键,消息以广播形式发出,所有到绑定到交换机上的队列都会收到复制的消息。
  • Fanout交换机转发消息是最快的

 Topic Exchange

 

  • 要求路由键与队列上绑定KEY的规则相匹配。
  • 其中匹配规则中有两个特殊的标识符 "#" 和 "*","*" 表示匹配一个字符,"#" 表示匹配一个或多个字符。
  1. 消息 (usa.news) -> 队列 (usa.*)
  2. 消息 (usa.news) -> 队列(usa.#)
  3. 消息 (usa.news) -> 队列(#)
  4. 消息 (usa.news.it) -> 队列 (usa.*.it)
  5. 消息 (usa.news.it) -> 队列 (usa.*.*)
  6. 消息 (usa.news.it) -> 队列 (usa.#)
  7. 消息 (usa.news.it) -> 队列 (#.it)
  8. 消息 (usa.news.it) -> 队列 (#)

Java 代码实现 

需求说明

 有两种日志级别 INFO 和 ERROR 。

  1. 假设我们要做日志分析,由于日志格式的不同,需要把INFO日志存放 q_info 队列,Error日志存放到q_error队列。
  2. 假设有多个部门都要做日志分析,A部门用作日志做系统安全分析,B部门用作用户行为分析。A 部门使用 q_department_a队列,B部门使用q_department_b队列。
  3. 假设要收集多个系统的日志,其中包括订单系统、商品系统、库存系统等。只分析所有系统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

 从结果中可以看出消费了所有系统中的错误日志。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值