1.背景

了解过RabbitMQ的Fanout模式,应该知道它原本的Fanout模式就是用来做广播的。但是它的广播有一点区别,来回顾下它的含义:Fanout类型没有路由键的概念,只要队列绑定到了改exchange上面,就会接收到所有的消息。

使用过程一般就是先new 出一个Fanout类型的交换机,然后往这个交换机上绑定多个队列queue,不同的消费者各自监听不同的队列,这就实现了广播效果,因为同一个消息,会分发到所有队列中。

举个例子:

应用A监听了队列A,应用B监听了队列B,Fanout类型交换机同时绑定了队列A和B.假设生产者端发送了一条消息到Fanout类型交换机,交换机就会把消息分发到所有队列,这时应用A和应用B会收到同一条消息,这就是广播。

说了上面一大堆,只是为了强调,对于RabbitMQ的原本Fanout模式,它的设计就是多个消费者必须监听不同的队列,多个消费者之间才会形成广播关系。

那么问题来了,假如在Fanout工作模式下,多个消费者同时监听的是同一个队列,会怎样?实践过的同学应该都知道,这种情况下,这些消费者会形成竞争关系,现象是同一个消息只会被其中一个消费者接收,达不到广播的效果。。

2.需求

假如现在有一个需求,要做到对同一个应用的多个节点进行广播,怎么实现?

注意,这里所说的同一个应用多个节点,通俗点理解就是一个war包,布在多个服务器节点上。

在实际部署集群时,为了高可用,同一个应用可能会部署多个节点,那假如工程里已经通过配置定义某个队列,那多个节点它们定义的队列就会是相同的,那按照上面的背景,那这些节点间肯定就会存在竞争关系,即便是Fanout模式的交换机,一条消息也只能被其中一个节点接收,其他节点收不到,达不到广播的效果。那该如何做?

相信看到这里,有人会问,为何会有 对同一个应用的多个节点进行广播的需求场景?为什么要有这个需求。生产中的业务系统很多,自然而然场景就很多。

举两个经典的例子:

1.想要同时刷新所有节点的缓存

业务系统离不开缓存,有时会用内存缓存,假如我要刷新所有节点的内存缓存,多个节点前可能有负载均衡例如nginx之类的,我只需要访问其中一个节点,然后让这个节点做广播通知所有其他节点刷缓存。(广播刷缓存)

2.websocket会话寻找

websocket是比较受欢迎的实时消息推送方案。用过websocket应该知道,websocket只能与多个节点中的其中一个节点做长连接会话保持,也就是说用户的会话只会存在于一个节点上,假设服务端要主动向用户推一条消息,必须要知道用户的会话在哪个节点上,怎么得知?可以通过广播,通过消息广播,把消息发到多个节点上,然后节点收到消息只需要判断用户会话是否就在本节点上,假如在则主动推消息,不在,则丢弃这条消息。

类似上面这两种需求,就需要用到广播,并且是对同一个应用的多个节点进行广播。当然不用广播肯定也有其他通知方案,本文我们只讨论用MQ怎么做到。

3.思路

假如继续用RabbitMQ的Fanout模式,怎么做到对同一个应用的多个节点进行广播?

要起到广播效果,关键就是让多个应用节点间不要存在竞争关系或者存在竞争关系时它们的消息怎么共享?可以从这两个方向解决这个问题。

方法可能很多种,在这里,我只描述两种比较容易实现的方案。

方案1

过程大致如下

  1. 应用启动,多个节点监听同一个队列(此时多个节点是竞争关系,一条消息只会发到其中一个节点上)

  2. 消息生产者发送消息,同一条消息只被其中一个节点收到

  3. 收到消息的节点通过redis的发布订阅模式来通知其他兄弟节点

这种方案是最容易想到的,思路就是依赖其他组件来做消息共享,例如redis这种可以替换成其他方案,只要能做到消息共享就行,那么最终的效果就肯定是广播效果了。

方案2

过程大致如下

  1. 应用启动,利用监听器生成唯一ID

  2. 生成的唯一ID,通过文件写入的方式写到配置文件中

  3. spring启动,把这个唯一ID加载为全局属性(为何要用唯一ID,就是为了用这个ID作为该节点的监听队列名,当然前缀可以用相同的,后缀用唯一ID区分即可,举个例子就是:节点1监听队列 kunghsu-123 节点2监听队列 kunghsu-456.必须保证它们的唯一ID是唯一的,不然还是会存在竞争关系)

  4. 多个节点监听了多个队列(让每个队列名都不同,目的就是让他们不存在竞争关系,没有竞争关系就不用做消息共享,只管由MQ分发即可,这时同一条消息就会发到多个节点上)

  5. 到MQ控制台,将所有节点生成的队列手动绑定到指定的Fanout交换机上(这一步是手动的,当然也可以通过API做到,下面会说到)

  6. 生产者发送消息指定的Fanout交换机,交换机将同一条消息被分发到多个节点上

  7. 广播效果达成!

这种方案,也比较容易。这样做,就是为了让多个节点间是广播关系。总的来说不麻烦,其中第五步手动操作其实有点挫,这种手动操作步骤其实是应该转成自动化,让应用程序来完成,方便以后自动化建设。

这种方案的spring配置也比较简单,参考Fanout模式的配置即可。本文重点在这个思路的实现过程。

只列举部分代码如下:

消息生产者

  1. <!-- 只申明交换机,不定义队列 -->
  2. <rabbit:fanout-exchange name="exchangeFour" durable="true" auto-delete="false" >
  3. </rabbit:fanout-exchange>
  4. <!--定义rabbit template用于数据的接收和发送 -->
  5. <rabbit:template id="amqpTemplate4" connection-factory="connectionFactory2"
  6. exchange="exchangeFour" />

消息消费者

  1. <rabbit:queue name="${queue-name-fanout}" durable="true"
  2. auto-delete="false" exclusive="false" declared-by="connectAdmin2" />
  3. <bean id="fanoutTwoConsumer" class="com.lunch.foo.rabbitmq.FanoutTwoConsumer"></bean>
  4. <rabbit:listener-container
  5. connection-factory="connectionFactory2">
  6. <rabbit:listener queues="${queue-name-fanout}" ref="fanoutOneConsumer" />
  7. </rabbit:listener-container>

另外,RabbitMQ的客户端API支持让我们 将队列绑定到指定的交换机上。具体可参考我的工具类代码。

代码如下:

  1. package com.lunch.foo.rabbitmq;
  2. import com.rabbitmq.client.*;
  3. import java.io.IOException;
  4. import java.util.concurrent.TimeoutException;
  5. /**
    * Created by xuyaokun On 2019/3/10 2:26
    * @desc:
    */
  6. public class RabbitMQUtil {
  7. private static final String HOST = "192.168.3.128";
  8. private static final int PORT = AMQP.PROTOCOL.PORT;
  9. private static final String USERNAME = "kunghsu";
  10. private static final String PASSWORD = "123456";
  11. private static final String VIRTUALHOST = "/";
  12. public static void main(String[] args) {
  13. String QUEUE_NAME = "queueOneX";
  14. String EXCHANGE_NAME = "exchangeFour";
  15. try {
  16. queueBind(EXCHANGE_NAME, QUEUE_NAME);
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. } catch (TimeoutException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. /**
    * 获取会话链接
    *
    * @return
    * @throws IOException
    * @throws TimeoutException
    */
  24. private static Connection getConnection() throws IOException, TimeoutException {
  25. ConnectionFactory factory = new ConnectionFactory();
  26. factory.setHost(HOST);
  27. factory.setPort(PORT);
  28. factory.setUsername(USERNAME);
  29. factory.setPassword(PASSWORD);
  30. factory.setVirtualHost(VIRTUALHOST);
  31. return factory.newConnection();
  32. }
  33. /**
    * 绑定队列到指定交换机
    *
    * @param exchangeName
    * @param queueName
    * @throws IOException
    * @throws TimeoutException
    */
  34. public static void queueBind(String exchangeName, String queueName) throws IOException, TimeoutException {
  35. Channel channel = null;
  36. try{
  37. channel = getConnection().createChannel();
  38. } catch(Exception e){
  39. System.out.println("获取RabbitMQ会话连接失败!取消做队列绑定。");
  40. return ;
  41. }
  42. //默认持久化
  43. channel.queueDeclare(queueName, true, false, false, null);
  44. // 声明交换机:指定交换机的名称和类型(广播:fanout)
  45. channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true);
  46. // 在消费者端队列绑定
  47. channel.queueBind(queueName, exchangeName, "");
  48. channel.close();
  49. }
  50. }

总结

RabbitMQ的Fanout模式相关的文章,网上一抓一大把,但是几乎没有人讲到 如何实现 对同一个应用的多个节点进行广播。。希望通过这篇文章,能帮助到有需要的同学。另外,假如大家有更好的方案,欢迎交流。感谢阅读!

RabbitMQ 如何实现对同一个应用的多个节点进行广播的更多相关文章

  1. RabbitMQ上手记录–part 4-节点集群(单机多节点)

    现在互联网应用动不动就说要HA,好像不搞个HA都不好意思说自己的应用能承载高并发,大用户量访问.RabbitMQ这个经典的消息组件,也必然逃不掉单点失效的尴尬局面.当然在RabbitMQ在被广泛应用于 ...

  2. RabbitMQ基本用法、消息分发模式、消息持久化、广播模式

    RabbitMQ基本用法 进程queue用于同一父进程创建的子进程间的通信 而RabbitMQ可以在不同父进程间通信(例如在word和QQ间通信) 示例代码 生产端(发送) import pika c ...

  3. 理解 OpenStack 高可用(HA)(5):RabbitMQ HA

    本系列会分析OpenStack 的高可用性(HA)概念和解决方案: (1)OpenStack 高可用方案概述 (2)Neutron L3 Agent HA - VRRP (虚拟路由冗余协议) (3)N ...

  4. docker rabbitmq

    docker run -d --hostname my1 --name dome-rabbit -p 15673:5672 -p 15674:15672 -e RABBITMQ_ERLANG_COOK ...

  5. rabbitmq高级消息队列

    rabbitmq使用 什么是消息队列 消息(Message)是指在应用间传送的数据.消息可以非常简单,比如只包含文本字符串,也可以很复杂,可以包含嵌入对象. 消息队列是一种应用间的通信方式,消息发送后 ...

  6. Centos6.9下RabbitMQ集群部署记录

    之前简单介绍了CentOS下单机部署RabbltMQ环境的操作记录,下面详细说下RabbitMQ集群知识,RabbitMQ是用erlang开发的,集群非常方便,因为erlang天生就是一门分布式语言, ...

  7. rabbitmq安装及基本操作(含集群配置)

    一.rabbitmq的安装 因为rabbitmq是基于 erlang语言开发,所有要先安装erlang 1.安装erlang 这里我下载的是19.2的版本,地址为https://www.erlang. ...

  8. 消息队列之 RabbitMQ

    https://www.jianshu.com/p/79ca08116d57 关于消息队列,从前年开始断断续续看了些资料,想写很久了,但一直没腾出空,近来分别碰到几个朋友聊这块的技术选型,是时候把这块 ...

  9. RabbitMQ上手记录–part 5-节点集群高可用(多服务器)

    上一part<RabbitMQ上手记录–part 4-节点集群(单机多节点)>中介绍了RabbitMQ集群的一些概念以及实现了在单机上运行多个节点,并且将多个节点组成一个集群. 通常情况下 ...

随机推荐

  1. STL容器:list双向链表学习

    list是一个双向列表容器,完成了标准C++数据结构中链表的所有功能; list与vector和deque类似,只不过其中的对象提供了对元素的随机访问. STL以双向链表的方式实现list,访问需要从 ...

  2. python3----练习......

    # 上行遍历 soup = BeautifulSoup(demo, 'html.parser') for parent in soup.a.parents: if parent is None: pr ...

  3. Ubuntu执行su后输入密码结果认证失败--解决办法:sudo passwd修改命令

  4. Windows远程桌面没有密码的电脑

    你如果想远程一个密码为空的机器,默认情况下是不可以的,需要进行以下设置 1.windows家庭版不支持远程桌面 2. 3.搜索“本地安全策略”

  5. oracle11g安装完成后修改字符集

    author : headsen chen date:2018-05-10  10:27:16 oracle11g完成安装后,由于默认安装的时候无法指定字符集,所以手动修改字符集和10g版本一样的字符 ...

  6. ORA-01078错误解决办法

    ORA-01078: failure in processing system parameters & LRM-00109: could not open parameter file 安装 ...

  7. linux shell 脚本使用

    定义变量 fileName=text.txt 变量名称fileName,变量名称text.txt 使用变量 $fileName 用美元符号$开头,后面加变量名称,即可使用变量 使用用户输入参数 打印第 ...

  8. ajax跨域问题解决之cors篇

    现在浏览器出于安全考虑,在域名.协议.端口不同的情况下,浏览器会认为这是跨域,ajax请求是不允许跨域的. 如果我们有跨域的需求,可以使用cors解决.其原理就是,在请求之前先发送一个OPTIONS请 ...

  9. HDU_5510_Bazinga

    Bazinga Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Sub ...

  10. HDU_5533_Dancing Stars on Me

    Dancing Stars on Me Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Ot ...