转自 http://blog.csdn.net/xiaoxian8023/article/details/48729479

翻译地址:http://www.rabbitmq.com/tutorials/tutorial-three-java.html

在前面的教程中,我们创建了一个工作队列,都是假设一个任务只交给一个消费者。这次我们做一些完全不同的事儿——将消息发送给多个消费者。这种模式叫做“发布/订阅”。

为了说明这个模式,我们将构建一个简单日志系统。它包含2段程序:第一个将发出日志消息,第二个接受并打印消息。

如果在日志系统中每一个接受者(订阅者)都会的得到消息的拷贝。那样的话,我们可以运行一个接受者(订阅者)程序,直接把日志记录到硬盘。同时运行另一个接受者(订阅者)程序,打印日志到屏幕上。

说白了,发表日志消息将被广播给所有的接收者。

Exchanges(转发器)

前面的博文汇总,我们都是基于一个队列发送和接受消息。现在介绍一下完整的消息传递模式。

RabbitMQ消息模式的核心理念是:生产者没有直接发送任何消费到队列。实际上,生产者都不知道这个消费是发送给哪个队列的。

相反,生产者只能发送消息给转发器,转发器是非常简单的。一方面它接受生产者的消息,另一方面向队列推送消息。转发器必须清楚的知道如何处理接收到的消息。附加一个特定的队列吗?附加多个队列?或者是否丢弃?这些规则通过转发器的类型进行定义。

类型有:Direct、Topic、Headers和Fanout。我们关注最后一个。现在让我们创建一个该类型的转发器,定义如下:

  1. channel.exchangeDeclare("logs", "fanout");

fanout转发器非常简单,从名字就可以看出,它是广播接受到的消息给所有的队列。而这正好符合日志系统的需求。

Nameless exchange(匿名转发)

之前我们对转换器一无所知,却可以将消息发送到队列,那是可能是我们用了默认的转发器,转发器名为空字符串""。之前我们发布消息的代码是:

  1. channel.basicPublish("", "hello", null, message.getBytes());

第一个参数就是转发器的名字,空字符串表示模式或者匿名的转发器。消息通过队列的routingKey路由到指定的队列中去,如果存在的话。

现在我们可以指定转发器的名字了:

  1. channel.basicPublish( "logs", "", null, message.getBytes());

Temporary queues(临时队列)

你可能还记得之前我们用队列时,会指定一个名字。队列有名字对我们来说是非常重要的——我们需要为消费者指定同一个队列。

但这并不是我们的日志系统所关心的。我们要监听所有日志消息,而不仅仅是一类日志。我们只对对当前流动的消息感兴趣。解决这些问题,我盟需要完成两件事。

首先,每当我盟连接到RabbitMQ时,需要一个新的空队列。为此我们需要创建一个随机名字的空队列,或者更好的,让服务器选好年则一个随机名字的空队列给我们。

其次,一旦消费者断开连接,队列将自动删除。

我们提供一个无参的queueDeclare()方法,创建一个非持久化、独立的、自动删除的队列,且名字是随机生成的。

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

queueName是一个随机队列名。看起来会像amq.gen-JzTY20BRgKO-HjmUJj0wLg。

Bindings(绑定)

我们已经创建了一个广播的转发器和一个随机队列。现在需要告诉转发器转发消息到队列。这个关联转发器和队列的我们叫它Binding。

  1. channel.queueBind(queueName, "logs", "");

这样,日志转发器将附加到日志队列上去。

完整的例子:

发送端代码(生产者)EmitLog.java

  1. public class EmitLog {
  2. private final static String EXCHANGE_NAME = "logs";
  3. public static void main(String[] args) throws IOException {
  4. /**
  5. * 创建连接连接到MabbitMQ
  6. */
  7. ConnectionFactory factory = new ConnectionFactory();
  8. // 设置MabbitMQ所在主机ip或者主机名
  9. factory.setHost("127.0.0.1");
  10. // 创建一个连接
  11. Connection connection = factory.newConnection();
  12. // 创建一个频道
  13. Channel channel = connection.createChannel();
  14. // 指定转发——广播
  15. channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
  16. for(int i=0;i<3;i++){
  17. // 发送的消息
  18. String message = "Hello World!";
  19. channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
  20. System.out.println(" [x] Sent '" + message + "'");
  21. }
  22. // 关闭频道和连接
  23. channel.close();
  24. connection.close();
  25. }
  26. }

消费者1 ReceiveLogs2Console.java

  1. public class ReceiveLogs2Console {
  2. private static final String EXCHANGE_NAME = "logs";
  3. public static void main(String[] argv) throws IOException, InterruptedException {
  4. ConnectionFactory factory = new ConnectionFactory();
  5. factory.setHost("127.0.0.1");
  6. // 打开连接和创建频道,与发送端一样
  7. Connection connection = factory.newConnection();
  8. final Channel channel = connection.createChannel();
  9. channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
  10. // 声明一个随机队列
  11. String queueName = channel.queueDeclare().getQueue();
  12. channel.queueBind(queueName, EXCHANGE_NAME, "");
  13. System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
  14. // 创建队列消费者
  15. final Consumer consumer = new DefaultConsumer(channel) {
  16. @Override
  17. public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
  18. String message = new String(body, "UTF-8");
  19. System.out.println(" [x] Received '" + message + "'");
  20. }
  21. };
  22. channel.basicConsume(queueName, true, consumer);
  23. }
  24. }

消费者2 ReceiveLogs2File.java

  1. public class ReceiveLogs2File {
  2. private static final String EXCHANGE_NAME = "logs";
  3. public static void main(String[] argv) throws IOException, InterruptedException {
  4. ConnectionFactory factory = new ConnectionFactory();
  5. factory.setHost("127.0.0.1");
  6. // 打开连接和创建频道,与发送端一样
  7. Connection connection = factory.newConnection();
  8. final Channel channel = connection.createChannel();
  9. channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
  10. // 声明一个随机队列
  11. String queueName = channel.queueDeclare().getQueue();
  12. channel.queueBind(queueName, EXCHANGE_NAME, "");
  13. System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
  14. // 创建队列消费者
  15. final Consumer consumer = new DefaultConsumer(channel) {
  16. @Override
  17. public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
  18. String message = new String(body, "UTF-8");
  19. print2File(message);
  20. //              System.out.println(" [x] Received '" + message + "'");
  21. }
  22. };
  23. channel.basicConsume(queueName, true, consumer);
  24. }
  25. private static void print2File(String msg) {
  26. try {
  27. String dir = ReceiveLogs2File.class.getClassLoader().getResource("").getPath();
  28. String logFileName = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
  29. File file = new File(dir, logFileName + ".log");
  30. FileOutputStream fos = new FileOutputStream(file, true);
  31. fos.write(((new SimpleDateFormat("HH:mm:ss").format(new Date())+" - "+msg + "\r\n").getBytes());
  32. fos.flush();
  33. fos.close();
  34. } catch (FileNotFoundException e) {
  35. e.printStackTrace();
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. }
  39. }
  40. }

可以看到我们1个生产者用于发送log消息,2个消费者,一个用于显示,一个用于记录文件。

生产者声明了一个广播模式的转换器,订阅这个转换器的消费者都可以收到每一条消息。可以看到在生产者中,没有声明队列。这也验证了之前说的。生产者其实只关心exchange,至于exchange会把消息转发给哪些队列,并不是生产者关心的。

2个消费者,一个打印日志,一个写入文件,除了这2个地方不一样,其他地方一模一样。也是声明一下广播模式的转换器,而队列则是随机生成的,消费者实例启动后,会创建一个随机实例,这个在管理页面可以看到(如图)。而实例关闭后,随机队列也会自动删除。最后将队列与转发器绑定。

注:运行的时候要先运行2个消费者实例,然后在运行生产者实例。否则获取不到实例。

看看最终的结果吧:

轻松搞定RabbitMQ(四)——发布/订阅的更多相关文章

  1. 轻松搞定RabbitMQ(六)——主题

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48806871 翻译地址:http://www.rabbitmq.com/tutorials ...

  2. 轻松搞定RabbitMQ(一)——RabbitMQ基础知识+HelloWorld

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48679609 本文是简单介绍一下RabbitMQ,参考官网上的教程.同时加入了一些自己的理 ...

  3. 轻松搞定RabbitMQ(五)——路由选择

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48733249 翻译地址:http://www.rabbitmq.com/tutorials ...

  4. 轻松搞定RabbitMQ(二)——工作队列之消息分发机制

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48681987 上一篇博文中简单介绍了一下RabbitMQ的基础知识,并写了一个经典语言入门 ...

  5. 轻松搞定RabbitMQ(三)——消息应答与消息持久化

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48710653 这个官网的第二个例子中的消息应答和消息持久化部分.我把它摘出来作为单独的一块 ...

  6. 春节过后就是金三银四求职季,分享几个Java面试妙招,轻松搞定HR!

    春节过后就是金三银四,分享几个Java面试妙招,轻松搞定HR! 2020年了,先祝大家新年快乐! 今年IT职位依然相当热门,特别是Java开发岗位.软件开发人才在今年将有大量的就业机会.春节过后,金三 ...

  7. 【转】轻松搞定FTP之FlashFxp全攻略

    转载网址:http://www.newhua.com/2008/0603/39163.shtml 轻松搞定FTP之FlashFxp全攻略 导读: FlashFXP是一款功能强大的FXP/FTP软件,融 ...

  8. Webcast / 技术小视频制作方法——自己动手录制video轻松搞定

    Webcast / 技术小视频制作方法——自己动手录制video轻松搞定 http://blog.sina.com.cn/s/blog_67d387490100wdnh.html 最近申请加入MSP的 ...

  9. 使用BleLib的轻松搞定Android低功耗蓝牙Ble 4.0开发具体解释

    转载请注明来源: http://blog.csdn.net/kjunchen/article/details/50909410 使用BleLib的轻松搞定Android低功耗蓝牙Ble 4.0开发具体 ...

随机推荐

  1. Java面试题之Java中==和equals()和hashCode()的区别

    “==”: ==是运算符,用来比较两个值.两个对象的内存地址是否相等: “equals()”: equals是Object类的方法,默认情况下比较两个对象是否是同一个对象,内部实现是通过“==”来实现 ...

  2. javaweb学习总结(十)——HttpServletRequest对象(一)(转)

    (每天都会更新至少一篇以上,有兴趣的可以关注)转载自孤傲苍狼 一.HttpServletRequest介绍 HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器 ...

  3. 【kindeditor】kindeditor的使用

    官网:http://kindeditor.net/demo.php 效果:

  4. Linux内核解析:进程间通信:管道

    管道的定义管道的用途管道的操作管道非法read与write内核实现解析管道通信原理及其亲戚通信解析父子进程通信解析亲缘关系的进程管道通信解析管道的注意事项及其性质管道有以下三条性质shell管道的实现 ...

  5. 【Linux】多进程与多线程之间的区别

    http://blog.csdn.net/byrsongqq/article/details/6339240 网络编程中设计并发服务器,使用多进程与多线程 ,请问有什么区别?  答案一: 1,进程:子 ...

  6. 现象: shell脚本中source aaa.sh时提示 source: not found

    原因: ls -l `which sh` 提示/bin/sh -> dash 这说明是用dash来进行解析的. 改回方法: 命令行执行:sudo dpkg-reconfigure dash 在界 ...

  7. MAMP 10.10下启动报错解决方案

      cd /Applications/MAMP/Library/binmv envvars _envvars

  8. mysql利用sql脚本插入数据中文乱码

    将其中的 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;/*!40101 SET @OLD_CHARACTER_SE ...

  9. CUDA程序计时

    之前写的CUDA程序,想测量一下性能,网上很多用的是CPU端计时,很不准确.翻了一下书,发现这里应该使用事件来计时. CUDA中的事件本质上是一个GPU时间戳,这个时间戳是在用户指定的时间点上记录的. ...

  10. Codeforces 919 C. Seat Arrangements

    C. Seat Arrangements   time limit per test 1 second memory limit per test 256 megabytes input standa ...