RabbitMQ 如何实现对同一个应用的多个节点进行广播
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
过程大致如下
应用启动,多个节点监听同一个队列(此时多个节点是竞争关系,一条消息只会发到其中一个节点上)
消息生产者发送消息,同一条消息只被其中一个节点收到
收到消息的节点通过redis的发布订阅模式来通知其他兄弟节点
这种方案是最容易想到的,思路就是依赖其他组件来做消息共享,例如redis这种可以替换成其他方案,只要能做到消息共享就行,那么最终的效果就肯定是广播效果了。
方案2
过程大致如下
应用启动,利用监听器生成唯一ID
生成的唯一ID,通过文件写入的方式写到配置文件中
spring启动,把这个唯一ID加载为全局属性(为何要用唯一ID,就是为了用这个ID作为该节点的监听队列名,当然前缀可以用相同的,后缀用唯一ID区分即可,举个例子就是:节点1监听队列 kunghsu-123 节点2监听队列 kunghsu-456.必须保证它们的唯一ID是唯一的,不然还是会存在竞争关系)
多个节点监听了多个队列(让每个队列名都不同,目的就是让他们不存在竞争关系,没有竞争关系就不用做消息共享,只管由MQ分发即可,这时同一条消息就会发到多个节点上)
到MQ控制台,将所有节点生成的队列手动绑定到指定的Fanout交换机上(这一步是手动的,当然也可以通过API做到,下面会说到)
生产者发送消息指定的Fanout交换机,交换机将同一条消息被分发到多个节点上
广播效果达成!
这种方案,也比较容易。这样做,就是为了让多个节点间是广播关系。总的来说不麻烦,其中第五步手动操作其实有点挫,这种手动操作步骤其实是应该转成自动化,让应用程序来完成,方便以后自动化建设。
这种方案的spring配置也比较简单,参考Fanout模式的配置即可。本文重点在这个思路的实现过程。
只列举部分代码如下:
消息生产者
<!-- 只申明交换机,不定义队列 -->
<rabbit:fanout-exchange name="exchangeFour" durable="true" auto-delete="false" >
</rabbit:fanout-exchange>
<!--定义rabbit template用于数据的接收和发送 -->
<rabbit:template id="amqpTemplate4" connection-factory="connectionFactory2"
exchange="exchangeFour" />
消息消费者
<rabbit:queue name="${queue-name-fanout}" durable="true"
auto-delete="false" exclusive="false" declared-by="connectAdmin2" />
<bean id="fanoutTwoConsumer" class="com.lunch.foo.rabbitmq.FanoutTwoConsumer"></bean>
<rabbit:listener-container
connection-factory="connectionFactory2">
<rabbit:listener queues="${queue-name-fanout}" ref="fanoutOneConsumer" />
</rabbit:listener-container>
另外,RabbitMQ的客户端API支持让我们 将队列绑定到指定的交换机上。具体可参考我的工具类代码。
代码如下:
package com.lunch.foo.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Created by xuyaokun On 2019/3/10 2:26
* @desc:
*/
public class RabbitMQUtil {
private static final String HOST = "192.168.3.128";
private static final int PORT = AMQP.PROTOCOL.PORT;
private static final String USERNAME = "kunghsu";
private static final String PASSWORD = "123456";
private static final String VIRTUALHOST = "/";
public static void main(String[] args) {
String QUEUE_NAME = "queueOneX";
String EXCHANGE_NAME = "exchangeFour";
try {
queueBind(EXCHANGE_NAME, QUEUE_NAME);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
/**
* 获取会话链接
*
* @return
* @throws IOException
* @throws TimeoutException
*/
private static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost(VIRTUALHOST);
return factory.newConnection();
}
/**
* 绑定队列到指定交换机
*
* @param exchangeName
* @param queueName
* @throws IOException
* @throws TimeoutException
*/
public static void queueBind(String exchangeName, String queueName) throws IOException, TimeoutException {
Channel channel = null;
try{
channel = getConnection().createChannel();
} catch(Exception e){
System.out.println("获取RabbitMQ会话连接失败!取消做队列绑定。");
return ;
}
//默认持久化
channel.queueDeclare(queueName, true, false, false, null);
// 声明交换机:指定交换机的名称和类型(广播:fanout)
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true);
// 在消费者端队列绑定
channel.queueBind(queueName, exchangeName, "");
channel.close();
}
}
总结
RabbitMQ的Fanout模式相关的文章,网上一抓一大把,但是几乎没有人讲到 如何实现 对同一个应用的多个节点进行广播。。希望通过这篇文章,能帮助到有需要的同学。另外,假如大家有更好的方案,欢迎交流。感谢阅读!
RabbitMQ 如何实现对同一个应用的多个节点进行广播的更多相关文章
- RabbitMQ上手记录–part 4-节点集群(单机多节点)
现在互联网应用动不动就说要HA,好像不搞个HA都不好意思说自己的应用能承载高并发,大用户量访问.RabbitMQ这个经典的消息组件,也必然逃不掉单点失效的尴尬局面.当然在RabbitMQ在被广泛应用于 ...
- RabbitMQ基本用法、消息分发模式、消息持久化、广播模式
RabbitMQ基本用法 进程queue用于同一父进程创建的子进程间的通信 而RabbitMQ可以在不同父进程间通信(例如在word和QQ间通信) 示例代码 生产端(发送) import pika c ...
- 理解 OpenStack 高可用(HA)(5):RabbitMQ HA
本系列会分析OpenStack 的高可用性(HA)概念和解决方案: (1)OpenStack 高可用方案概述 (2)Neutron L3 Agent HA - VRRP (虚拟路由冗余协议) (3)N ...
- docker rabbitmq
docker run -d --hostname my1 --name dome-rabbit -p 15673:5672 -p 15674:15672 -e RABBITMQ_ERLANG_COOK ...
- rabbitmq高级消息队列
rabbitmq使用 什么是消息队列 消息(Message)是指在应用间传送的数据.消息可以非常简单,比如只包含文本字符串,也可以很复杂,可以包含嵌入对象. 消息队列是一种应用间的通信方式,消息发送后 ...
- Centos6.9下RabbitMQ集群部署记录
之前简单介绍了CentOS下单机部署RabbltMQ环境的操作记录,下面详细说下RabbitMQ集群知识,RabbitMQ是用erlang开发的,集群非常方便,因为erlang天生就是一门分布式语言, ...
- rabbitmq安装及基本操作(含集群配置)
一.rabbitmq的安装 因为rabbitmq是基于 erlang语言开发,所有要先安装erlang 1.安装erlang 这里我下载的是19.2的版本,地址为https://www.erlang. ...
- 消息队列之 RabbitMQ
https://www.jianshu.com/p/79ca08116d57 关于消息队列,从前年开始断断续续看了些资料,想写很久了,但一直没腾出空,近来分别碰到几个朋友聊这块的技术选型,是时候把这块 ...
- RabbitMQ上手记录–part 5-节点集群高可用(多服务器)
上一part<RabbitMQ上手记录–part 4-节点集群(单机多节点)>中介绍了RabbitMQ集群的一些概念以及实现了在单机上运行多个节点,并且将多个节点组成一个集群. 通常情况下 ...
随机推荐
- gcc安装(centos)
gcc 4.8 安装 [root@DS-VM-Node239 ~]# curl -Lks http://www.hop5.in/yum/el6/hop5.repo > /etc/yum.repo ...
- ios开发之--UIWebView全属性
最近的项目当中需要用到html和ios的交互,所以就凑空整理一下,所有webView相关的方法和属性,如有不对的地方,请大家不吝指教! 代码如下: 1,创建webview并设置代理 UIWebView ...
- ios 开发之 -- 极光推送,发送自定义消息,进入制定页面
在进行极光推送时候,发现版本有所更新,以前截取didfinish入口方法里面的launchOptions,获取一个本地的通知内容,进行本地展示不可用了,通过查询官方文档和网上的资料才发现,方法改变了, ...
- 1-2、superset国际化
最近由于工作需要研究开源可视化项目superset,由于其国际化做不怎么好,故而记录下国际化的过程,本篇本着『授人以鱼不如授人以渔』的原则,只叙述国际化的过程及方法,不提供直接的国际化文件. 为了方便 ...
- jquery 判断ul下是否存在li
$("ul").has("li").length > 0;$("ul > li").length > 0;$(" ...
- webpack 报错 path is not defind
webpack.config.js里的内容是这样的,注意标红的地方: 首先,绝对路径'./dist'是 没有问题的 那么,查了很多,最后看到别人的webpack.config.js里面这样写着,现在c ...
- webpack配置(一)
这里再配置的时候走了些弯路,现在,把配置前的准备工作做好很重要: 首先,安装node.js,当然,npm也就有了: 其次,安装xampp,主要是为了配置Apache: 安装好后,xampp---htd ...
- Spring Security OAuth2 授权码模式
背景: 由于业务实现中涉及到接入第三方系统(app接入有赞商城等),所以涉及到第三方系统需要获取用户信息(用户手机号.姓名等),为了保证用户信息的安全和接入方式的统一, 采用Oauth2四种模式之一 ...
- 160330、Mybatis整合Spring
转自csdn文章 http://haohaoxuexi.iteye.com/blog/1843309 Mybatis整合Spring 根据官方的说法,在ibatis3,也就是Mybatis3问世之前, ...
- ubuntu 卸载 google-chrome
sudo apt-get autoremove google-chrome-stable