赞
踩
- RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产
者甚至都不知道这些消息传递传递到了哪些队列中。- 相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。
直接(direct):处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 abc ,则只有被标记为 abc 的消息才被转发,不会转发 abc.def,也不会转发 dog.ghi,只会转发 abc。
主题(topic):将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多个词,符号 * 匹配不多不少一个词。因此 abc.# 能够匹配到 abc.def.ghi,但是 abc.* 只会匹配到 abc.def。
标题(headers):不处理路由键。而是根据发送的消息内容中的headers属性进行匹配。在绑定 Queue 与 Exchange 时指定一组键值对;当消息发送到RabbitMQ 时会取到该消息的 headers 与 Exchange 绑定时指定的键值对进行匹配;如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 属性是一个键值对,可以是 Hashtable,键值对的值可以是任何类型。而 fanout,direct,topic 的路由键都需要要字符串形式的。
匹配规则 x-match 有下列两种类型:
x-match = all :表示所有的键值对都匹配才能接受到消息
x-match = any :表示只要有键值对匹配就能接受到消息
扇出(fanout):不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout 交换机转发消息是最快的。
通过空字符串("")进行标识的交换机是默认交换
channel.basicPublish("", TASK_QUEUE_NAME, null, message.getBytes("UTF-8"));
第一个参数是交换机的名称。空字符串 表示默认或无名称交换机:消息能路由发送到队列中其实是由 routingKey(bindingkey) 绑定指定的 key
之前的章节我们使用的是具有特定名称的队列(还记得 hello 和 ack_queue 吗?)。队列的名称我们
来说至关重要-我们需要指定我们的消费者去消费哪个队列的消息。
每当我们连接到 Rabbit 时,我们都需要一个全新的空队列,为此我们可以创建一个具有随机名称
的队列,或者能让服务器为我们选择一个随机队列名称那就更好了。其次一旦我们断开了消费者的连
接,队列将被自动删除。
String queueName = channel.queueDeclare().getQueue();
注意 先启动两个消费者再启动生产者。 生产者生产消息后,如果没有对应的消费者接收,则该消息是遗弃的消息
ReceiveLogs01
将接收到的消息打印在控制台
ReceiveLogs02
把消息写出到文件
//消费者1 public class ReceiveLogs01 { //交换机名称 private static final String EXCHANGE_NAME = "logs"; public static void main(String[] args) throws IOException, TimeoutException { Channel channel = RabbitMQUtils.getChannel(); //声明一个交换机 channel.exchangeDeclare(EXCHANGE_NAME,"fanout"); //声明一个队列 临时队列 /** * 生成一个临时的队列,队列的名称是随机的 * 当消费者断开与队列的连接的时候 队列就自动删除 */ String queueName = channel.queueDeclare().getQueue(); /** * 绑定交换机与队列 */ channel.queueBind(queueName,EXCHANGE_NAME,""); System.out.println("等待接收消息,把接收到的消息打印在屏幕上..."); //接收消息 //消费者取消消息时回调接口 DeliverCallback deliverCallback = (consumerTag,message) ->{ System.out.println("控制台打印接收到的消息:"+new String(message.getBody(),"UTF-8")); }; channel.basicConsume(queueName,true,deliverCallback,consumerTag -> {}); } }
//生产者EmitLog 发送消息给两个消费者进行消费 public class EmitLog { //交换机名称 public static final String EXCHANGE_NAME = "logs"; public static void main(String[] args) throws IOException, TimeoutException { Channel channel = RabbitMQUtils.getChannel(); /** * 声明一个exchange * 1.exchange的名称 * 2.exchange的类型 */ channel.exchangeDeclare(EXCHANGE_NAME,"fanout"); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()){ String message = scanner.next(); channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8")); System.out.println("生产者发出消息:"+message); } } }
- C1 消费者:绑定 console 队列,routingKey 为 info、warning
- C2 消费者:绑定 disk 队列,routingKey 为 error
- 当生产者生产消息到
direct_logs
交换机里,该交换机会检测消息的 routingKey 条件,然后分配到满足 条件的队列里,最后由消费者从队列消费消息。
// 生产者 public class DirectLogs { //交换机名称 public static final String EXCHANGE_NAME = "direct_logs"; public static void main(String[] args) throws IOException, TimeoutException { Channel channel = RabbitMQUtils.getChannel(); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()){ String message = scanner.next(); channel.basicPublish(EXCHANGE_NAME,"info",null,message.getBytes("UTF-8")); System.out.println("生产者发出消息:"+message); } } }
// 消费者1 public class ReceiveLogsDirect01 { public static final String EXCHANGE_NAME="direct_logs"; public static void main(String[] args) throws IOException, TimeoutException { Channel channel = RabbitMQUtils.getChannel(); //声明一个direct交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //声明一个队列 channel.queueDeclare("console",false,false,false,null); channel.queueBind("console",EXCHANGE_NAME,"info"); channel.queueBind("console",EXCHANGE_NAME,"warning"); //接收消息 DeliverCallback deliverCallback = (consumerTag,message) -> { System.out.println("ReceiveLogsDirect01控制台打印接收到的消息:"+new String(message.getBody(),"UTF-8")); }; //消费者取消消息时回调接口 channel.basicConsume("console",true,deliverCallback,consumerTag -> {}); } }
// 消费者2 public class ReceiveLogsDirect02 { public static final String EXCHANGE_NAME="direct_logs"; public static void main(String[] args) throws IOException, TimeoutException { Channel channel = RabbitMQUtils.getChannel(); //声明一个direct交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //声明一个队列 channel.queueDeclare("disk",false,false,false,null); channel.queueBind("disk",EXCHANGE_NAME,"error"); //接收消息 DeliverCallback deliverCallback = (consumerTag,message) -> { System.out.println("ReceiveLogsDirect02控制台打印接收到的消息:"+new String(message.getBody(),"UTF-8")); }; //消费者取消消息时回调接口 channel.basicConsume("disk",true,deliverCallback,consumerTag -> {}); } }
在上一个小节中,我们改进了日志记录系统。我们没有使用只能进行随意广播的 fanout 交换机,而是使用了 direct 交换机,从而有能实现有选择性地接收日志。
尽管使用 direct 交换机改进了我们的系统,但是它仍然存在局限性——比方说我们想接收的日志类型有 info.base 和 info.advantage,某个队列只想 info.base 的消息,那这个时候direct 就办不到了。这个时候就只能使用 topic 类型
发送到类型是 topic 交换机的消息的 routing_key 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词
比如说:“stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit” 这种类型的。
当然这个单词列表最多不能超过 255 个字节。
在这个规则列表中,其中有两个替换符是大家需要注意的:
*(星号)可以代替一个位置
#(井号)可以替代零个或多个位置
// 生产者 public class EmitLogTopic { //交换机的名称 public static final String EXCHANGE_NAME = "topic_logs"; public static void main(String[] args) throws IOException, TimeoutException { Channel channel = RabbitMQUtils.getChannel(); /** * Q1-->绑定的是 * 中间带 orange 带 3 个单词的字符串(*.orange.*) * Q2-->绑定的是 * 最后一个单词是 rabbit 的 3 个单词(*.*.rabbit) * 第一个单词是 lazy 的多个单词(lazy.#) */ HashMap<String, String> bindingKeyMap = new HashMap<>(); bindingKeyMap.put("quick.orange.rabbit", "被队列 Q1Q2 接收到"); bindingKeyMap.put("lazy.orange.elephant", "被队列 Q1Q2 接收到"); bindingKeyMap.put("quick.orange.fox", "被队列 Q1 接收到"); bindingKeyMap.put("lazy.brown.fox", "被队列 Q2 接收到"); bindingKeyMap.put("lazy.pink.rabbit", "虽然满足两个绑定但只被队列 Q2 接收一次"); bindingKeyMap.put("quick.brown.fox", "不匹配任何绑定不会被任何队列接收到会被丢弃"); bindingKeyMap.put("quick.orange.male.rabbit", "是四个单词不匹配任何绑定会被丢弃"); bindingKeyMap.put("lazy.orange.male.rabbit", "是四个单词但匹配 Q2"); for (Map.Entry<String,String> bindingKeyEntry : bindingKeyMap.entrySet()){ String routingKey = bindingKeyEntry.getKey(); String message = bindingKeyEntry.getValue(); channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes("UTF-8")); System.out.println("生产者发出消息:"+message); } } }
//消费者1 public class ReceiveLogsTopic01 { //交换机的名称 public static final String EXCHANGE_NAME = "topic_logs"; //接收消息 public static void main(String[] args) throws IOException, TimeoutException { Channel channel = RabbitMQUtils.getChannel(); //声明交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); //声明队列 String queueName = "Q1"; channel.queueDeclare(queueName,false,false,false,null); channel.queueBind(queueName,EXCHANGE_NAME,"*.orange.*"); System.out.println("等待接收消息..."); DeliverCallback deliverCallback = (consumerTag,message) -> { System.out.println(new String(message.getBody(),"UTF-8")); System.out.println("接收队列:"+queueName+" 绑定键:"+message.getEnvelope().getRoutingKey()); }; //接收消息 channel.basicConsume(queueName,true,deliverCallback,consumerTag ->{}); } }
//消费者2 public class ReceiveLogsTopic02 { //交换机的名称 public static final String EXCHANGE_NAME = "topic_logs"; //接收消息 public static void main(String[] args) throws IOException, TimeoutException { Channel channel = RabbitMQUtils.getChannel(); //声明交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); //声明队列 String queueName = "Q2"; channel.queueDeclare(queueName,false,false,false,null); channel.queueBind(queueName,EXCHANGE_NAME,"*.*.rabbit"); channel.queueBind(queueName,EXCHANGE_NAME,"lazy.#"); System.out.println("等待接收消息..."); DeliverCallback deliverCallback = (consumerTag,message) -> { System.out.println(new String(message.getBody(),"UTF-8")); System.out.println("接收队列:"+queueName+" 绑定键:"+message.getEnvelope().getRoutingKey()); }; //接收消息 channel.basicConsume(queueName,true,deliverCallback,consumerTag ->{}); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。