上一篇最后提到了mandatory这个参数,对于设置mandatory参数个人感觉还是很重要的,尤其在RabbitMQ镜像队列发生故障转移时。

模拟个测试环境如下:

首先在集群队列中增加两个镜像队列的策略:

对于ha-promote-on-shutdown这个参数,可以参考文档,其作用就是当集群中master出现故障时强制进行故障转移从而选出新的master节点,这里的master出现故障表示的是人为的故障比如通过命令行rabbitmqctl.bat start_app之类的关闭RabbitMQ实例或者说是关闭电脑之类的。因为这种强制切换master节点的情况通常发生在断电之类的非可控因素上,所以通过设置这个参数为always模拟非可控因素。

当然设置这个参数会存在一定风险,文档里也说了,会发生消息不同步也就是会丢消息。

然后创建四个队列和两个Echange,采用绑定Exchange的topic模式

然后先贴一下测试代码在进行说明

C#代码

       List<string> hosts = new List<string>();
hosts.Add("192.168.1.1");
hosts.Add("192.168.1.2");
int curHostIndex = ;
string exchange = "always.exchange";
string touteKey = "yu.1";
byte[] msg = Encoding.UTF8.GetBytes("hello");
ConnectAgain:
ConnectionFactory factory = new ConnectionFactory();
factory.UserName = "admin";
factory.Password = "admin";
factory.VirtualHost = "/";
factory.HostName = hosts[curHostIndex];
IConnection conn = factory.CreateConnection();
IModel channel = conn.CreateModel();
IBasicProperties props = channel.CreateBasicProperties();
props.ContentType = "text/plain";
props.DeliveryMode = ;
for (int i = ; i < ; i++)
{
try
{
channel.ConfirmSelect();
channel.BasicAcks += (sender, eventArgs) => { };
channel.BasicReturn += (sender, eventArgs) => Console.WriteLine("消息投递失败 " + eventArgs.ReplyText);
channel.BasicPublish(exchange, touteKey, true, props, msg);
bool success = channel.WaitForConfirms(new TimeSpan(, , , , ));
if (!success)
Console.WriteLine("表示消息投递失败 ");
}
catch (Exception ex)
{ //发生链接异常时换个IP进行连接
channel.Close();
conn.Close();
if (curHostIndex == )
curHostIndex = ;
else
curHostIndex = ;
goto ConnectAgain;
}
}

Java代码:

 public static void publish() throws Exception {
List<String> hosts = new ArrayList<String>();
hosts.add("192.168.1.1");
hosts.add("192.168.1.2");
int curHostIndex = 1;
String exchange = "common.exchange";
String routeKey = "yu.1";
byte[] msg = "hello".getBytes("UTF-8");
ConnectAgain:
while (true) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(hosts.get(curHostIndex));
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
factory.setVirtualHost("/");
// 创建一个新的连接
Connection connection = factory.newConnection();
// 创建一个频道
Channel channel = connection.createChannel();
channel.addConfirmListener(new ConfirmListener() {
public void handleAck(long l, boolean b) throws IOException {
System.out.println(l);
} public void handleNack(long l, boolean b) throws IOException {
System.out.println(l);
}
});
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
System.out.println("响应状态码-ReplyCode:" + i);
System.out.println("响应内容-ReplyText:" + s);
System.out.println("Exchange:" + s1);
System.out.println("RouteKey" + s2);
System.out.println("投递失败的消息:" + new String(bytes, "UTF-8"));
}
});
for (int i = 0; i < 5000000; i++) {
try {
channel.confirmSelect();
channel.basicPublish(exchange, routeKey, true, MessageProperties.PERSISTENT_TEXT_PLAIN, msg);
boolean sucess = channel.waitForConfirms(10);
System.out.println(sucess);
} catch (Exception ex) {
try {
connection.abort();
} catch (Exception e) {
e.printStackTrace();
}
if (curHostIndex == 0)
curHostIndex = 1;
else
curHostIndex = 0;
continue ConnectAgain;
}
}
} }

先测试下always.exchange也就是非人为因素导致的故障转移的情况,先开启客户端让客户端跑着,然后通过命令行停止master节点(也就是Node为WinServer12-1),停止时消息队列的消息状态为

图片消息的总数虽然不准确(页面存在延迟) ,但截取的是master停止时刻的消息状态,也够用了,这时候发现slaver节点会切换为master节点(也就是Node为DESKTOP-078UA43),并继续接受消息,客户点也没有发生异常通知(因为订阅了BasicReturn事件并且开启了madartory,如果消息投递失败,我们可以得到通知,待会也会测试到)。

然后我们让集群多跑会然后在消息有明显变化的时候在开启老的挂掉的当前为slaver的节点,当前队列消息的状态如下,master为DESKTOP-078UA43

在开启slaver后我们在立即停止当前的master节点(也就是Node为WinServer12-1),这时候发现集群的master又切回到了DESKTOP-078UA43同时队列中的消息也跟着清除了。。也就是说在DESKTOP-078UA43之前挂掉到重启启动期间WinServer12-1接收到的消息全部丢掉了。。。由此我们可知,RabbitMQ镜像集群发生非可控因素造成的master故障为了保证可用性,会丢消息。

而对于客户端而已,消息都是可靠投递的,所以监听事件并不会触发。

当然也可以通过设置ha-sync-mode参数进行调整,默认情况下,新加入的节点不会同步已存在节点内的消息,设置为automatic后会进行同步。不过如果没同步完master挂掉的话消息还是会丢掉的

然后测试下common.exchange会发生的情况,测试这个的时候就是体现mandatory作用的时刻了!

还是先在集群正常的情况下选取个时间点关掉主节点,当前master为DESKTOP-078UA43

然后WinServer12-1变为新的master,此刻发现正常接收消息,而且对客户端而言,消息也是正常投递的。然后打开被关闭的DESKTOP-078UA43节点,它会以slaver身份回归集群,开启前观察下当前队列状态

然后开始操作!发现队列状态如下,NaN,难道说队列停止接受数据了么!!!(如果停止接受数据,客户端同步调用发送时会发送失败么?)而且无法将master进行切换了。

这时候如果在启动WinServer12-1会发现,消息还是WinServer12-1关闭时刻的消息,WinServer12-1关闭期间DESKTOP-078UA43尽管在接受消息,但实际消息并没有被RabbitMQ可靠存储(比较master都没有了。。);

观察下调试的代码,发现消息还是在正常向RabbitMQ投递。

客户端为了保证向RabbitMQ投递消息的可靠,及开启了Conform模式,但此刻同步返回的RabbitMQ处理结果是消息处理完成。那岂不是NaN期间RabbitMQ把消息都吞了?而客户端还傻傻的以为发送成功了。。

这时候就体现开启mandatory同时订阅 channel.BasicReturn += (sender, eventArgs) => Console.WriteLine("消息投递失败 " + eventArgs.ReplyText);事件的作用了。。因为这时候RabbitMQ会反馈给你消息实际上并没有投递成功的信息。

这里包含了持久化失败的原因,同时包含发送消息的详细信息,方便客户端对消息进行在处理。

其实说了这么多,最后想说的是对于消息的一致性,最好还是不要全部依赖于RabbitMQ,实现最终一致性并保证幂等性才是相对可靠的方案。

RabbitMQ如何保证发送端消息的可靠投递-发生镜像队列发生故障转移时的更多相关文章

  1. RabbitMQ如何保证发送端消息的可靠投递

    消息发布者向RabbitMQ进行消息投递时默认情况下是不返回发布者该条消息在broker中的状态的,也就是说发布者不知道这条消息是否真的抵达RabbitMQ的broker之上,也因此会发生消息丢失的情 ...

  2. IM消息送达保证机制实现(二):保证离线消息的可靠投递

    1.前言 本文的上篇<IM消息送达保证机制实现(一):保证在线实时消息的可靠投递>中,我们讨论了在线实时消息的投递可以通过应用层的确认.发送方的超时重传.接收方的去重等手段来保证业务层面消 ...

  3. IM系统中如何保证消息的可靠投递(即QoS机制)(转)

    消息的可靠性,即消息的不丢失和不重复,是im系统中的一个难点.当初qq在技术上(当时叫oicq)因为以下两点原因才打败了icq:1)qq的消息投递可靠(消息不丢失,不重复)2)qq的垃圾消息少(它an ...

  4. RabbitMQ 消息的可靠投递

    mq 提供了两种方式确认消息的可靠投递 confirmCallback 确认模式 returnCallback 未投递到 queue 退回模式 在使用 RabbitMQ 的时候,作为消息发送方希望杜绝 ...

  5. IM系统中如何保证消息的可靠投递(即QoS机制)

      消息的可靠性,即消息的不丢失和不重复,是im系统中的一个难点.当初qq在技术上(当时叫oicq)因为以下两点原因才打败了icq:1)qq的消息投递可靠(消息不丢失,不重复)2)qq的垃圾消息少(它 ...

  6. springboot项目整合rabbitMq涉及消息的发送确认,消息的消费确认机制,延时队列的实现

    1.引入maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactI ...

  7. springboot rabbitMQ 死信对列 实现消息的可靠消费

    1 引入 maven 依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifac ...

  8. 在服务端处理同步发送小消息的性能上Kafka>RocketMQ>RabbitMQ

    在发送小消息的场景中,三个消息中间件的表现区分明显: Kafka的吞吐量高达17.3w/s,远超其他两个产品.这主要取决于它的队列模式保证了写磁盘的过程是线性IO.此时broker磁盘IO已达瓶颈. ...

  9. 【RabbitMQ】如何进行消息可靠投递【上篇】

    说明 前几天,突然发生线上报警,钉钉连发了好几条消息,一看是RabbitMQ相关的消息,心头一紧,难道翻车了? [橙色报警] 应用[xxx]在[08-15 16:36:04]发生[错误日志异常],al ...

随机推荐

  1. Docker for Windows(五)实践搭建SqlServer服务&执行数据库操作

    上一篇我们已经搭建了一个mysql数据库服务了:Docker for Windows(四)实践搭建&删除MySQL服务,发现用Docker确实是方便且容易,但上一篇主要是服务的搭建删除等基础操 ...

  2. Tab Key not working when using Xfce remote desktop

    Xfce 远程桌面Tab键设置 Use CTRL-tab instead of  tab The XFCE Terminal has kidnapped the tab key for a featu ...

  3. Android:Building " " Gradle project info 问题

    Android Studio新建或者打开项目的时候,一直卡在Building "" Gradle project info 进度上不动,猜测是网络原因下载gradle不成功. 两种 ...

  4. Android资源文件说明

    一. Android资源文件简介 1. Android应用资源的作用 (1) Android项目中文件分类 在Android工程中, 文件主要分为下面几类 : 界面布局文件, Java src源文件, ...

  5. solr学习笔记

    目录 前言 linux部署 使用 配置 使用 前言 solr是apach基于Lucene开发的成熟的框架,这里我们学习如何部署.使用.关于集群会在后面继续添加 linux部署 mkdir /usr/l ...

  6. SQLSERVER2012里的扩展事件初尝试(下)

    SQLSERVER2012里的扩展事件初尝试(下) SQLSERVER2012里的扩展事件初尝试(上) 我们继续文章扩展事件在Denali CTP3里的新UI(二)里的这个实验 脚本文件下载:http ...

  7. Vue2学习笔记:v-show指令

    v-show指令:v-show="true/false" 控制元素显示/隐藏 1.使用 <!DOCTYPE html> <html> <head> ...

  8. 山寨"每日故宫"应用的图层遮罩效果

    山寨"每日故宫"应用的图层遮罩效果 最终效果: 应用中的效果: 素材图片: 源码: // // ViewController.m // 每日故宫 // // Created by ...

  9. Linux 系统开机自启的配置文件

    程序开机启动的配置文件(/etc/rc.local) # 系统级别 ntsysv # 图形界面设置自启程序 chkconfig(/etc/init.d/sshd) 处理开机启动的文件 # 用户级别 # ...

  10. Linode VPS主机套餐方案降低处理方法且不影响数据

    使用Linode VPS主机产品经历过512MB内存升级至1GB内存,再升级至2GB内存,以及目前推出1GB内存方案月付10美元.比如我们在使用Linode 2GB内存方案的时候占用资源不是太多,其实 ...