RabbitMQ学习笔记四:消息Publish/Subscrible

前一篇我们实现了工作队列,并且我们的工作队列中的一个任务只会发给一个工作者,除非某个工作者未完成任务意外被杀死,会转发给另外的工作者,参见 [Rabbitmq学习笔记三:工作队列work queue] (http://blog.csdn.net/u010416588/article/details/54629334)。

一 发布、订阅简介

   这篇博客中,我们会做一些改变,就是把一个消息发给多个消费者,这种模式称之为发布/订阅(类似观察者模式)。
   为了验证这种模式,我们准备构建一个简单的日志系统。这个系统包含两类程序,一类程序发动日志,另一类程序接收和处理日志。
   在我们的日志系统中,每一个运行的接收者程序都会收到日志。然后我们实现,一个接收者将接收到的数据写到硬盘上,与此同时,另一个接收者把接收到的消息展现在屏幕上。
   本质上来说,就是发布的日志消息会转发给所有的接收者。

二 转换器(Exchange)

   前面的博客中我们主要的介绍都是发送者发送消息给队列,接收者从队列接收消息。下面我们会引入Exchanges,展示RabbitMQ的完整的消息模型。
   RabbitMQ消息模型的核心理念是生产者永远不会直接发送任何消息给队列,一般的情况生产者甚至不知道消息应该发送到哪些队列。
   相反的,生产者只能发送消息给转发器(Exchange)。转发器是非常简单的,一边接收从生产者发来的消息,另一边把消息推送到队列中。转发器必须清楚的知道消息如何处理它收到的每一条消息。是否应该追加到一个指定的队列?是否应该追加到多个队列?或者是否应该丢弃?这些规则通过转发器的类型进行定义。

转化器
下面列出一些转发器,direct, topic, headers and fanout。目前我们关注最后一个fanout,声明转发器类型的代码:

 channel.exchangeDeclare("logs","fanout");   
   fanout类型转发器工作原理,把所有它介绍到的消息,广播到所有它所知道的队列。不过这正是我们前述的日志系统所需要的。
Listing exchanges

在命令框通过如下命令查看exchanges

# rabbitmqctl list_exchanges

查看rabbitmq的默认exchange

   从上面列表中可以看到一些amq.* 开头的默认转换器。这些事默认创建的,但现在不大可能需要使用它们。

二 匿名转换器(nameless exchanges)

    前面说到生产者只能发送消息给转发器(Exchange),但是我们前两篇博客中的例子并没有使用到转发器,我们仍然可以发送和接收消息。这是因为我们使用了一个默认的转发器,它的标识符为””。之前发送消息的代码:
channel.basicPublish("", QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
    第一个参数为转发器的名称,我们设置为”” : 如果存在routingKey(第二个参数),消息由routingKey决定发送到哪个队列。

现在我们可以指定消息发送到的转发器:

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

三 临时队列(Temprorary queues)

    前面的博客中我们都为队列指定了一个特定的名称。能够为队列命名对我们来说是很关键的,我们需要指定消费者为某个队列。当我们希望在生产者和消费者间共享队列时,为队列命名是很重要的。
   不过,对于我们的日志系统我们并不关心队列的名称。我们想要接收到所有的消息,而且我们也只对当前正在传递的数据的感兴趣。为了满足我们的需求,需要做两件事:
   第一, 无论什么时间连接到Rabbit我们都需要一个新的空的队列。为了实现,我们可以使用随机数创建队列,或者更好的,让服务器给我们提供一个随机的名称。
   第二, 一旦消费者与Rabbit断开,消费者所接收的那个队列应该被自动删除
Java中我们可以使用queueDeclare()方法,不传递任何参数,来创建一个非持久的、唯一的、自动删除的队列且队列名称由服务器随机产生。
String queueName = channel.queueDeclare().getQueue();
一般情况这个名称与amq.gen-JzTY20BRgKO-HjmUJj0wLg 类似。

四 绑定(Bindings)

这里写图片描述
我们已经创建了一个fanout转发器和队列,我们现在需要通过binding告诉转发器把消息发送给我们的队列。转发器和队列的关系就叫做绑定。

channel.queueBind(queueName, “logs”, ””)

参数1:队列名称 ;参数2:转发器名称

在命令框查看绑定关系

rabbitmqctl list_bindings

这里写图片描述

五 完整的例子

这里写图片描述

5.1 消息发送

生成程序,发出的日志消息,跟之前教程没有太多不同。最大的变化是我们想要发送消息给日志转发器而不是无名的。下面是EmitLog.java

package com.gta.goldnock.mq.exchange;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * 
* @ClassName: EmitLog
* @Description: TODO(日志消息发送类)
* @author yuhuan.gao
* @date 2017年1月22日 下午3:17:28
*
 */
public class EmitLog{  
    private final static String EXCHANGE_NAME = "ex_log";  

    public static void main(String[] args) throws IOException, TimeoutException{  
        // 创建连接和频道  
        ConnectionFactory factory = new ConnectionFactory();  
        factory.setHost("localhost");  
        Connection connection = factory.newConnection();  
        Channel channel = connection.createChannel();  
        // 声明转发器和类型  
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout" );  

        String message = new Date().toString()+" : log something";  
        // 往转发器上发送消息  
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());  

        System.out.println(" [x] Sent '" + message + "'");  

        channel.close();  
        connection.close();  

    }  

}  

如你所见,在建立连接后,我们申明转发器。此步骤是必要的,因为对非现有Exchange的发布是禁止的。
如果没有队列绑定到Exchange,消息将丢失,但对我们来说没有问题;如果没有消费者正在侦听,我们可以安全地丢弃消息。

5.2 消息接收

接收消息并打印到控制台:
ReceiveLogs .java

package com.gta.goldnock.mq.exchange;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

/**
 * 
* @ClassName: ReceiveLogs
* @Description: TODO(日志消息接收类)
* @author yuhuan.gao
* @date 2017年1月22日 下午3:31:17
*
 */
public class ReceiveLogs {

    //定义转发器
    private static final String EXCHANGE_NAME = "ex_log";

    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接和通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //申明交换机名称和类型
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        //创建一个非持久的、唯一的且自动删除的队列  
        String queueName = channel.queueDeclare().getQueue();
        //为转发器指定队列,设置binding  
        channel.queueBind(queueName, EXCHANGE_NAME, "");
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        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(" [x] Received '" + message + "'");
                   }
        };
        //指定接收者,第二个参数为自动应答,(消息接收失败不会转发到其他接收者)
        channel.basicConsume(queueName, true, consumer);
    }
}

接收消息并写入文件,其他部分一样,打印消息出做修改
ReceiveLogsToFile.java

...
Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                    AMQP.BasicProperties properties, byte[] body) throws IOException {

                    String path = this.getClass().getClassLoader().getResource("").getPath();
                    System.out.println(path);
                    File file = new File(path,"log.txt");
                    FileOutputStream out = new FileOutputStream(file);
                    out.write(body);
                    out.write("\r\n".getBytes());
                    out.flush();
                    out.close();
                   }
        };
...

运行ReceiveLogs 和ReceiveLogsToFile的main方法,再运行EmitLog,可以看到两个接收者都接受到了消息:
EmitLog:

 [x] Sent 'Sun Jan 22 16:13:07 CST 2017 : Have a good day'
 [x] Sent 'Sun Jan 22 16:13:35 CST 2017 : This is a test'
 [x] Sent 'Sun Jan 22 16:13:58 CST 2017 : Just fo fun'

ReceiveLogs

 [*] Waiting for messages. To exit press CTRL+C
 [x] Received 'Sun Jan 22 16:13:07 CST 2017 : Have a good day'
 [x] Received 'Sun Jan 22 16:13:35 CST 2017 : This is a test'
 [x] Received 'Sun Jan 22 16:13:58 CST 2017 : Just fo fun'

ReceiveLogsToFile:
这里写图片描述

这个例子实现了我们文章开头所描述的日志系统,利用了转发器的类型:fanout。
本篇说明了,生产者将消息发送至转发器,转发器决定将消息发送至哪些队列,消费者绑定队列获取消息。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值