在前面的两篇博客中

遇到的实例都是一个消息只发送给一个消费者(工作者),他们的消息模型分别为(P代表生产者,C代表消费者,红色代表队列):

这次我们来看下将一个消息发送给多个消费者(工作者),这种模式一般被称为“发布/订阅”模式。其工作模型为(P代表生产者,X代表Exchange(路由器/交换机),C代表消费者,红色代表队列):

我们发现,工作模型中首次出现路由器,并且每个消费者有单独的队列。生产者生成消息后将其发送给路由器,然后路由器转送到队列,消费者各自到自己的队列里面获取消息进行消费。在实际的应用场景中,生产者一般不会直接将消息发送给队列,而是发送给路由器进行中转,Exchange必须清楚的知道怎么处理收到的消息:是将消息发送到一个特定队列还是多有队列,或者直接废弃消息。这种才符合RabbitMQ消息模型的核心思想

接下来我们详细展开今天的话题:

一、Exchange

Exchange在我们的工作模型中首次出现,因此需要详细介绍下。

Exchange分为4种类型:

Direct:完全根据key进行投递的,例如,绑定时设置了routing key为”abc”,那么客户端提交的消息,只有设置了key为”abc”的才会投递到队列。
Topic:对key进行模式匹配后进行投递,符号”#”匹配一个或多个词,符号”*”匹配正好一个词。例如”abc.#”匹配”abc.def.ghi”,”abc.*”只匹配”abc.def”。
Fanout:不需要key,它采取广播模式,一个消息进来时,投递到与该交换机绑定的所有队列。
Headers:我们可以不考虑它。

今天我们的实例采用fanout类型的exchange。

尽管首次出现,但是其实我们前面的案例中也有用到exchange,只是我们没有给他名字,用的是RabbitMQ默认的,比如下面这段代码,我们将路由器名这个参数传入了“”,如果我们需要自己声明exchange的话,这个就不能传入“”了,而是传入自己定义好的值。

二、临时队列

前面两篇博客中,我们都在使用队列的时候给出了定义好的名字,这在生产者和消费者共用相同队列的时候很有必要,但是我们有了exchange,生产者不需要知道有哪些队列,因此队列名字可以不用指定了,而是通过RabbitMQ 接口自己去生成临时队列,队列名字也由RabbitMQ自动生成。通过

可以声明一个非持久的、通道独占的、自动删除的队列,getQueue()方法可以获取随机队列名字。这个名字用来在队列和exchange之间建立binding关系的时候使用:

三、代码实现

基于上面exchange和临时队列的知识铺垫,可以展开今天的代码实现了。

  1. 生产者

    public class Product {
    //exchange名字
    public static String EXCHANGE_NAME = "exchange"; public static void main(String[] args) {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = null;
    Channel channel = null;
    try {
    // 1.创建连接和通道
    connection = factory.newConnection();
    channel = connection.createChannel(); // 2.为通道声明exchange和exchange的类型
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); String msg = " hello rabbitmq, this is publish/subscribe mode";
    // 3.发送消息到指定的exchange,队列指定为空,由exchange根据情况判断需要发送到哪些队列
    channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
    System.out.println("product send a msg: " + msg);
    } catch (IOException e) {
    e.printStackTrace();
    } catch (TimeoutException e) {
    e.printStackTrace();
    } finally {
    // 4.关闭连接
    if (channel != null) {
    try {
    channel.close();
    } catch (IOException e) {
    e.printStackTrace();
    } catch (TimeoutException e) {
    e.printStackTrace();
    }
    } if (connection != null) {
    try {
    connection.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }
  2. 消费者1
    public class Consumer1 {
    
        public static void main(String[] args) {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = null;
    Channel channel = null;
    try {
    // 1.创建连接和通道
    connection = factory.newConnection();
    channel = connection.createChannel(); // 2.为通道声明exchange以及exchange类型
    channel.exchangeDeclare(Product.EXCHANGE_NAME, BuiltinExchangeType.FANOUT); // 3.创建随机名字的队列
    String queueName = channel.queueDeclare().getQueue(); // 4.建立exchange和队列的绑定关系
    channel.queueBind(queueName, Product.EXCHANGE_NAME, "");
    System.out.println(" **** Consumer1 keep alive ,waiting for messages, and then deal them");
    // 5.通过回调生成消费者并进行监听
    Consumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
    com.rabbitmq.client.AMQP.BasicProperties properties, byte[] body) throws IOException { // 获取消息内容然后处理
    String msg = new String(body, "UTF-8");
    System.out.println("*********** Consumer1" + " get message :[" + msg + "]");
    }
    };
    // 6.消费消息
    channel.basicConsume(queueName, true, consumer);
    } catch (IOException e) {
    e.printStackTrace();
    } catch (TimeoutException e) {
    e.printStackTrace();
    }
    }
    }
  3. 消费者2,核心代码同消费者1一样,只是在日志打印上将"Consumer1"改为"Consumer2"而已。这里不再列出具体代码。
  4. 先运行消费者1和2,然后运行生产者,观察控制台log打印情况:
    生产者:
    product send a msg: hello rabbitmq, this is publish/subscribe mode 消费者1
    **** Consumer1 keep alive ,waiting for messages, and then deal them
    *********** Consumer1 get message :[ hello rabbitmq, this is publish/subscribe mode] 消费者2:
    **** Consumer2 keep alive ,waiting for messages, and then deal them
    *********** Consumer2 get message :[ hello rabbitmq, this is publish/subscribe mode]

    可以看到,当生产者发出消息后,两个消费者最终都收到了消息。

  5. 我们去查看RabbitMQ管理页面:

    在Exchanges 标签页里面多了一个名为“exchange”的路由器,他的类型是fanout。点exchange 的link进入详细页面:

    发现在binding项目中有了两条绑定关系,队列的名字也可以看到。将页面切换到Queues标签页:

    出现了两个新的队列,队列名字和绑定关系中的一样,并且队列都是自动删除的、通道独占的。

  6. 然后将消费者1和消费者2都停掉,重新查看管理页面,我们发现exchange还在,binding关系不存在了,临时队列也自动删除了

RabbitMQ入门:发布/订阅(Publish/Subscribe)的更多相关文章

  1. RabbitMQ入门(3)——发布/订阅(Publish/Subscribe)

    在上一篇RabbitMQ入门(2)--工作队列中,有一个默认的前提:每个任务都只发送到一个工作人员.这一篇将介绍发送一个消息到多个消费者.这种模式称为发布/订阅(Publish/Subscribe). ...

  2. RabbitMQ入门教程(五):扇形交换机发布/订阅(Publish/Subscribe)

    原文:RabbitMQ入门教程(五):扇形交换机发布/订阅(Publish/Subscribe) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...

  3. RabbitMQ学习总结 第四篇:发布/订阅 Publish/Subscribe

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

  4. RabbitMQ入门-发布订阅模式

    兔子的Publish/Subscribe是这样的: 有个生产者P,X代表交换机,交换机绑定队列,消费者从队列中取得消息.每次有消息,先发到交换机中,然后由交换机负责发送到它已知的队列中. 生产者代码: ...

  5. RabbitMQ的发布订阅模式(Publish/Subscribe)

    一.发布/订阅(Publish/Subscribe)模式 发布订阅是我们经常会用到的一种模式,生产者生产消息后,所有订阅者都可以收到.RabbitMQ的发布/订阅模型图如下: 1.该模式下生产者并不是 ...

  6. RabbitMQ(三) -- Publish/Subscribe

    RabbitMQ(三) -- Publish/Subscribe `rabbitmq`支持一对多的模式,一般称为发布/订阅.也就是说,生产者产生一条消息后,`rabbitmq`会把该消息分发给所有的消 ...

  7. RabbitMQ官方教程三 Publish/Subscribe(GOLANG语言实现)

    RabbitMQ官方教程三 Publish/Subscribe(GOLANG语言实现) 在上一个教程中,我们创建了一个工作队列. 工作队列背后的假设是,每个任务都恰好交付给一个worker处理. 在这 ...

  8. RabbitMQ之发布订阅【译】

    在上一节中我们创建了一个工作队列,最好的情况是工作队列能够把任务恰到好处的分配给每一个worker.这一节中我们将做一些完全不同的事情--将消息传递给每一个消费者,这种模式被称为发布/订阅. 为了说明 ...

  9. 译: 3. RabbitMQ Spring AMQP 之 Publish/Subscribe 发布和订阅

    在第一篇教程中,我们展示了如何使用start.spring.io来利用Spring Initializr创建一个具有RabbitMQ starter dependency的项目来创建spring-am ...

  10. 【译】RabbitMQ:发布-订阅(Publish/Subscribe)

    在前一篇教程中,我们创建了一个工作队列,我们假设在工作队列后的每一个任务都只被调度给一个消费者.在这一部分,我们将做一些完全不一样的事情,调度同一条消息给多个消费者,也就是有名的“发布-订阅”模式.为 ...

随机推荐

  1. mybatis #{}和${}的区别是什么

    #{}和${}的区别是什么?正确的答案是:#{}是预编译处理,${}是字符串替换.(1)mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法 ...

  2. 问题:win10防火墙不能自动启动

    问题:win10防火墙不能自动启动 描述:Windows防火墙不能自动启动,每次开机要手动启动,打开service.msc,里面防火墙的启动类型为手动,按钮为灰色,不能更改为自动,怎么办? 解决方法: ...

  3. 使用transient关键字解决ehcache序列化错误

    使用Ehcache时发现个不起眼的小问题 在一个Model中有以下代码: public class MyModel implements Serializable { private static f ...

  4. BZOJ4571:[SCOI2016]美味(主席树,贪心)

    Description 一家餐厅有 n 道菜,编号 1...n ,大家对第 i 道菜的评价值为 ai(1≤i≤n).有 m 位顾客,第 i 位顾客的期望值为 bi,而他的偏好值为 xi . 因此,第 ...

  5. P1880 [NOI1995]石子合并

    题目描述 在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. 试设计出1个算法,计算出将N堆石子合并成1 ...

  6. Manifest XML signature is not valid(安装ClickOnce签名未通过验证)

    转载:http://stackoverflow.com/questions/12826798/manifest-xml-signature-is-not-valid 安装时,我的问题:  PLATFO ...

  7. Spring Cloud 微服务项目实现总架构一

    Spring Cloud 服务是一种分布式服务,包括配置管理,服务发现,断路器,智能路由,微代理,控制总线,一次性令牌,全局锁,主节点选举, 分布式session, 集群状态等公共组件. 一  注册机 ...

  8. oracle 表的创建与管理 约束

    在 Oracle 之中数据表就被称为数据库对象,而对象的操作语法一共有三种:· 创建对象:CREATE 对象类型 对象名称 [选项]:· 删除对象:DROP 对象类型 对象名称 [选项]:· 修改对象 ...

  9. OC 知识:彻底理解 iOS 内存管理(MRC、ARC)

    1. 什么是内存管理 程序在运行的过程中通常通过以下行为,来增加程序的的内存占用 创建一个OC对象 定义一个变量 调用一个函数或者方法 而一个移动设备的内存是有限的,每个软件所能占用的内存也是有限的 ...

  10. canvas 绘制刮刮卡

    思路=> 用div来展示刮奖结果,用canvas绘制刮奖前展示的图片或者文字:将canvas叠在div上方,刮奖是只需要操作canvas配合touch事件即可简单完成. canvas刮奖可以用g ...