RabbitMQ是一种重要的消息队列中间件,在生产环境中,稳定是第一考虑。RabbitMQ厂家也深知开发者的声音,稳定、可靠是第一考虑,为了消息传输的可靠性传输,RabbitMQ提供了多种途径的消息持久化保证:Exchange持久化、Queue持久化及Message的持久化等。以保证RabbitMQ在重启或Crash等异常情况下,消息不会丢失。RabbitMQ提供了简单的参数配置来实现持久化操作。

简单说明一下各种持久化方式:(描述代码采用的是RabbitMQ.Client  SDK,  C#代码)

Queue持久化:队列是我们使用RabbitMQ进行数据传输的最多使用的方式,是进行点对点消息传递使用最多的方式。队列的持久化是通过durable=true 来实现。

var connFactory = new ConnectionFactory();
Conn = connFactory.CreateConnection();
Model = Conn.CreateModel();
Model.QueueDeclare(q, false, false, false, null);  

其中,QueueDeclare的定义:

/// <summary>(Spec method) Declare a queue.</summary>
[AmqpMethodDoNotImplement(null)]
QueueDeclareOk QueueDeclare(string queue, bool durable, bool exclusive,
bool autoDelete, IDictionary<string, object> arguments);  

参数说明:queue:队列名称。durable:设置是否执行持久化。如果设置为true,即durable=true,持久化实现的重要参数

exclusive:指示队列是否是排他性。如果一个队列被声明为排他队列,该队列仅对首次申明它的连接可见,并在连接断开时自动删除。需要注意:1. 排他队列是基于连接可见的,同一连接的不同信道Channel是可以同时访问同一连接创建的排他队列;2.“首次”,如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同;3.即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的,这种队列适用于一个客户端发送读取消息的应用场景。

autoDelete:是否自动删除。如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于发布订阅方式创建的临时队列。

消息的持久化:如果将一个队列设置为持久化,那么会创建一个持久化的队列,但并不意味着队列中的消息也会持久化存储。因此如果要保证消息在RabbitMQ出现异常时不会丢失,需要设定消息的持久化。

简要说明一下消息持久化和队列持久化的联系:

队列设置为持久化,那么在RabbitMQ重启之后,持久化的队列也会存在,并会保持和重启前一致的队列参数。

消息设置为持久化,在RabbitMQ重启之后,持久化的消息也会存在。

那么就会出现一些矛盾的地方:

1、因为消息必须依附于队列存在才有意义,那么如果队列设置为非持久化,而消息设置为持久化。在RabbitMQ重启之后,持久化的消息是否还存在呢?因为非持久化的队列可能并不存在。

2、如果设置消息持久化为true,但队列设置成排他性队列,那么在RabbitMQ重启之后,消息是否仍然存在。请自行查找分析,下次分析该问题。

              var sf = new ConnectionFactory();
using (IConnection conn = cf.CreateConnection())
{
IModel ch = conn.CreateModel();
                   Model = Conn.CreateModel();
Model.QueueDeclare(queueName, true, false, false, null); 
string message = "Hello C# SSL Client World";  byte[] msgBytes = System.Text.Encoding.UTF8.GetBytes(message);
//发送消息
ch.BasicPublish("", queueName, null, msgBytes); bool noAck = false;
BasicGetResult result = ch.BasicGet(qName, noAck);
byte[] body = result.Body;
string resultMessage = System.Text.Encoding.UTF8.GetString(body); Assert.AreEqual(message, resultMessage);
}

通过RabbitMQ SDK发送消息至MQ非常简单,通过BasicPublish即可。

BasicPublish 的定义:

   /// <summary>
/// (Spec method) Convenience overload of BasicPublish.
/// </summary>
/// <remarks>
/// The publication occurs with mandatory=false
/// </remarks>
[AmqpMethodDoNotImplement(null)]
void BasicPublish(string exchange, string routingKey, IBasicProperties basicProperties, byte[] body);

设置消息持久化,需要设置basicProperties的DeliveryMode=2 (Non-persistent (1) or persistent (2)).

设置了队列和消息持久化后,当服务重启之后,消息仍然存在。只设置队列持久化,不设置消息持久化,重启之后消息会丢失;只设置消息持久化,不设置队列持久化,在服务重启后,队列会消失,从而依附于队列的消息也会丢失。只设置消息持久化而不设置队列的持久化,毫无意义。

Exchange持久化:

为了实现一对多的消息发送,我们一般会采用发布订阅模式,通过一个发送端、多个订阅端来实现消息的分发。

发布订阅模式存在一些问题:

1、如果消费者由于网络或其他原因,与RabbitMQ的连接断开,那么RabbitMQ会自动将与其对应的队列删除,当消息程序重新连接以后,无法获取断开前未来得及消费的消息。

2、如果RabbitMQ出现故障或Crash,那么在RabbitMQ  服务重启之后,消费端未及时消费的消息也会丢失,并且如果Exchange 不设置成持久化,那么在MQ服务重启之后,Exchange也不会存在。

   /// <summary>(Spec method) Declare an exchange.</summary>
/// <remarks>
/// The exchange is declared non-passive and non-internal.
/// The "nowait" option is not exercised.
/// </remarks>
[AmqpMethodDoNotImplement(null)]
void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete,
IDictionary<string, object> arguments);

参数说明:exchange:RabbitMQ中定义的Exchange名称,type:类型,包含fanout、topic、direct、headers,durable:持久化设置。设置成true,就可以设定exchange持久化存储,autodelete:是否自动删除。

exchange是实现发布订阅的基础,其类型包含fanout、headers、direct、、topic。我们本次仅讨论类型为topic。

发布订阅模式执行消息发送的流程:

总结:

RabbitMQ要实现发布订阅持久化,按照消息的传输流程,可以分成三类:

Exchange 持久化:如果不设定Exchange持久化,那么在RabbitMQ由于某些异常等原因重启之后,Exchange会丢失。Exchange丢失, 会影响发送端发送消息到RabbitMQ。

Queue持久化:发送端将消息发送至Exchange,Exchange将消息转发至关联的Queue。如果Queue不设置持久化,那么在RabbitMQ重启之后,Queue信息会丢失。导致消息发送至Exchange,但Exchange不知道需要将该消息发送至哪些具体的队列。

Message持久化:发送端将消息发送至Exchange,Exchange将消息转发至关联的Queue,消息存储于具体的Queue中。如果RabbitMQ重启之后,由于Message未设置持久化,那么消息会在重启之后丢失。

为了保证发布订阅的持久化,必须设置Exchange、Queue、Message的持久化,才可以保证消息最终不会丢失。

虽然持久化会造成性能损耗,但为了生产环境的数据一致性,这是我们必须做出的选择。但我们可以通过设置消息过期时间、降低发送消息大小等其他方式来尽可能的降低MQ性能的降低。

扩展阅读:

1、Exchange type:topic、fanout、direct、headers的不同。

2、消息的确认机制。

3、将Exchange、Queue、Message都设置持久化,能保证消息100%会被成功消费吗?

答案肯定是否,天下没有绝对的事情,尤其是复杂的MQ。

原因简单介绍,一、如果消息的自动确认为true,那么在消息被接收以后,RabbitMQ就会删除该消息,假如消费端此时宕机,那么消息就会丢失。因此需要将消息设置为手动确认。

二、设置手动确认会出现另一个问题,如果消息已被成功处理,但在消息确认过程中出现问题,那么在消费端重启后,消息会重新被消费。

三、发送端为了保证消息会成功投递,一般会设定重试。如果消息发送至RabbitMQ之后,在RabbitMQ回复已成功接收消息过程中出现异常,那么发送端会重新发送该消息,从而造成消息重复发送。

四、RabbitMQ的消息磁盘写入,如果出现问题,也会造成消息丢失。

五、。。。。。

下期热点问题:

1、Exchange type的不同

2、消息的确认与拒绝机制

3、优先级机制

RabbitMQ发布订阅持久化方式:Exchange、Queue、Message持久化,队列设定手动确认、AutoDelete=false。可以最大程度的保证消息不丢失。

附RabbitMQ发布订阅持久化具体实现方式,参考代码:

 MQ SDK新增接口:
IMQSession新增方法:
/// <summary>
/// 创建消息消费者
/// </summary>
/// <param name="topicName">主题名称</param>
/// <param name="customTopicQueueName">自定义Topic关联队列名称</param>
/// <param name="isPersistence">是否持久化</param>
/// <returns>消息消费者</returns>
IMessageConsumer CreateTopicConsumer(string topicName, string customTopicQueueName, bool isPersistence = false);
调用方式:消费端需要明确指定需要消费的发布订阅关联队列。例如配置中心热部署,每个配置中心实例都需要指定唯一的关联队列名。
这样就可以和正常的MAC队列消费一样,消费指定队列消息。 实现方式,四个步骤:
.创建持久化Topic(即持久化Exchange):
var service = MQServiceProvider.GetDefaultMQService();
var messageText = "abc";
///创建Topic
using (var connection = service.CreateConnection())
{
var session = connection.CreateSession(MessageAckMode.IndividualAcknowledge);
var messageCreator = service.GetMessageCreator();
var message = messageCreator.CreateMessage(messageText);
message.IsPersistent = true;
var producer = session.CreateProducer();
var topic = session.DeclareTopic(topicName, true);
}
.定义消费者Consumer:
List<string> queueList = new List<string>() {
"guozhiqi1",
"guozhiqi2",
"guozhiqi3",
"guozhiqi4",
"guozhiqi5",
"guozhiqi6",
"guozhiqi7",
"guozhiqi8",
"guozhiqi9",
};
//var service = MQServiceProvider.GetDefaultMQService();
//var messageText = "abc" + DateTime.Now.ToShortTimeString();
//定义消费者
using (var connection1 = service.CreateConnection())
{
var session1 = connection1.CreateSession(MessageAckMode.IndividualAcknowledge);
foreach (var item in queueList)
{
session1.DeclareQueue(item, true);
var consumer = session1.CreateTopicConsumer(topicName, item, true);
}
}
.发送消息到Topic
//发送消息
for (int i = ; i <= ; i++)
{
using (var connection = service.CreateConnection())
{
var session = connection.CreateSession(MessageAckMode.IndividualAcknowledge);
var messageCreator = service.GetMessageCreator();
var message = messageCreator.CreateMessage(messageText);
message.IsPersistent = true;//设置持久化
message.TimeToLive = TimeSpan.FromSeconds();//设置过期时间
var producer = session.CreateProducer();
var topic = session.DeclareTopic(topicName, true);
producer.Send(message, topic);
}
}
.从队列接收消息
Parallel.ForEach(queueList, (item) =>
{
while (true)
{
//接收消息
using (var connection1 = service.CreateConnection())
{
var session1 = connection1.CreateSession(MessageAckMode.IndividualAcknowledge); session1.DeclareQueue(item, true);
var consumer = session1.CreateTopicConsumer(topicName, item, true);
var topic = session1.DeclareTopic(topicName, true);
var receivedmessage = consumer.Receive(topic);
var textMessage = receivedmessage as ITextMessage; Assert.AreEqual(messageText, textMessage.Body);
consumer.Acknowledge(receivedmessage);
}
} });

RabbitMQ 发布订阅持久化的更多相关文章

  1. RabbitMQ 发布订阅

    互联网公司对消息队列是深度使用者,因此需要我们了解消息队列的方方面面,良好的设计及深入的理解,更有利于我们对消息队列的规划. 当前我们使用消息队列中发现一些问题: 1.实际上是异步无返回远程调用,由发 ...

  2. RabbitMQ 发布订阅-实现延时重试队列(参考)

    RabbitMQ消息处理失败,我们会让失败消息进入重试队列等待执行,因为在重试队列距离真正执行还需要定义的时间间隔,因此,我们可以将重试队列设置成延时处理.今天参考网上其他人的实现,简单梳理下消息延时 ...

  3. RabbitMQ发布订阅实战-实现延时重试队列

    RabbitMQ是一款使用Erlang开发的开源消息队列.本文假设读者对RabbitMQ是什么已经有了基本的了解,如果你还不知道它是什么以及可以用来做什么,建议先从官网的 RabbitMQ Tutor ...

  4. RabbitMQ 发布/订阅

    我们会做一些改变,就是把一个消息发给多个消费者,这种模式称之为发布/订阅(类似观察者模式). 为了验证这种模式,我们准备构建一个简单的日志系统.这个系统包含两类程序,一类程序发动日志,另一类程序接收和 ...

  5. MariaDB主从复制,redis发布订阅,持久化,以及主从同步

      一. MariaDB主从复制 mysql基本操作 1 连接数据库 mysql -u root -p -h 127.0.0.1 mysql -u root -p -h 192.168.12.60 2 ...

  6. Linux 安装redis,redis发布订阅,持久化

    安装redis 1.安装redis的方式 -yum (删除这个yum安装的redis,我们只用源码编译安装的) -rpm -源码编译 2.删除原本的redis yum remove redis -y ...

  7. Linux(6)- redis发布订阅/持久化/主从复制/redis-sentinel/redis-cluster、nginx入门

    一.redis发布订阅 Redis 通过 PUBLISH .SUBSCRIBE 等命令实现了订阅与发布模式. 其实从Pub/Sub的机制来看,它更像是一个广播系统,多个Subscriber可以订阅多个 ...

  8. .Net下RabbitMQ发布订阅模式实践

    一.概念AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计.消息中间件主要用于组件之间的解耦,消息的发 ...

  9. 3.rabbitmq 发布/订阅

    1. 发布者 #coding:utf8 import pika import json import sys message = ''.join(sys.argv[1:]) or "hell ...

随机推荐

  1. web开发性能优化---代码优化篇

    1.合理使用缓存使用 提高性能最好最快的办法当然是通过缓存来改善,对于任何一个web开发者都应该善用缓存.Asp.net下的缓存机制十分强大,用好缓存机制可以让我们极大的改善web应用的性能. 1.页 ...

  2. Vue01 Vue介绍、Vue使用、Vue实例的创建、数据绑定、Vue实例的生命周期、差值与表达式、指令与事件、语法糖

    1 Vue介绍 1.1 官方介绍 vue是一个简单小巧的渐进式的技术栈,它提供了Web开发中常用的高级功能:视图和数据的解耦.组件的服用.路由.状态管理.虚拟DOM 说明:简单小巧 -> 压缩后 ...

  3. Linux之23个重要命令

    作为工作几年的Linux运维老司机,总结了Linux命令行的常用的一些用法,希望对您有所收获. 1. 搜索 在vi和vim中如果打开一个很大的文件,不容易找到对应的内容,可以使用自带的搜索关键字进行搜 ...

  4. 让你的微信小程序具有在线支付功能

    前言 最近需要在微信小程序中用到在线支付功能,于是看了一下官方的文档,发现要在小程序里实现微信支付还是很方便的,如果你以前开发过服务号下的微信支付,那么你会发现其实小程序里的微信支付和服务号里的开发过 ...

  5. PortableApps使用入门

    PortableApps使用入门 Software 介绍 添加软件 绿软下载站推荐 介绍 官网:http://portableapps.com/ PortableApps作为一款卓越的绿软管理软件,它 ...

  6. winform自动更新程序实现

    一.问题背景 本地程序在实际项目使用过程中,因为可以操作电脑本地的一些信息,并且对于串口.OPC.并口等数据可以方便的进行收发,虽然现在软件行业看着动不动都是互联网啊啥的,大有Web服务就是高大上的感 ...

  7. 简要分析javascript的选项卡和轮播图

    选项卡 思路 1.按钮和展示的页面要对应:分别遍历,记住当前按钮的索引,让其成为展示页面的索引 2.只出现所对应的页面:所有的页面隐藏,只展示想要的页面 只展示js代码 for(var i=0;i&l ...

  8. 【BZOJ4555】求和(第二类斯特林数,组合数学,NTT)

    [BZOJ4555]求和(第二类斯特林数,组合数学,NTT) 题面 BZOJ 题解 推推柿子 \[\sum_{i=0}^n\sum_{j=0}^iS(i,j)·j!·2^j\] \[=\sum_{i= ...

  9. 【BZOJ4237】稻草人(CDQ分治,单调栈)

    [BZOJ4237]稻草人(CDQ分治,单调栈) 题面 BZOJ 题解 \(CDQ\)分治好题呀 假设固定一个左下角的点 那么,我们可以找到的右下角长什么样子??? 发现什么? 在右侧是一个单调递减的 ...

  10. 对网易云音乐参数(params,encSecKey)的分析

    我们如果对网易云音乐进行爬虫的话,我们会发现,提交的参数是(params,encSecKey),然而这两个参数是一串很长的东西 我们要对网易云进行爬虫,那么就一定要将这两个参数弄明白,然后才可以进行爬 ...