RabbitMQ发布订阅持久化及持久化方式

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

RabbitMQ发布订阅持久化及持久化⽅式
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重启之后,消息是否仍然存在。

请⾃⾏查找分析,下次分析该问题。

1var sf = new ConnectionFactory();
2using (IConnection conn = cf.CreateConnection())
3 {
4 IModel ch = conn.CreateModel();
Model = Conn.CreateModel();
Model.QueueDeclare(queueName, true, false, false, null); 
string message = "Hello C# SSL Client World"; 11byte[] msgBytes = System.Text.Encoding.UTF8.GetBytes(message);
//发送消息
12 ch.BasicPublish("", queueName, null, msgBytes);
13
14bool noAck = false;
15 BasicGetResult result = ch.BasicGet(qName, noAck);
16byte[] body = result.Body;
17string resultMessage = System.Text.Encoding.UTF8.GetString(body);
18
19 Assert.AreEqual(message, resultMessage);
20 }
通过RabbitMQ SDK发送消息⾄MQ⾮常简单,通过BasicPublish即可。

BasicPublish 的定义:
1///<summary>
2/// (Spec method) Convenience overload of BasicPublish.
3///</summary>
4///<remarks>
5/// The publication occurs with mandatory=false
6///</remarks>
7 [AmqpMethodDoNotImplement(null)]
8void 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也不会存在。

1///<summary>(Spec method) Declare an exchange.</summary>
2///<remarks>
3/// The exchange is declared non-passive and non-internal.
4/// The "nowait" option is not exercised.
5///</remarks>
6 [AmqpMethodDoNotImplement(null)]
7void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete,
8 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发布订阅持久化具体实现⽅式,参考代码:
1 MQ SDK新增接⼝:
2 IMQSession新增⽅法:
3///<summary>
4///创建消息消费者
5///</summary>
6///<param name="topicName">主题名称</param>
7///<param name="customTopicQueueName">⾃定义Topic关联队列名称</param>
8///<param name="isPersistence">是否持久化</param>
9///<returns>消息消费者</returns>
10 IMessageConsumer CreateTopicConsumer(string topicName, string customTopicQueueName, bool isPersistence = false);
11调⽤⽅式:消费端需要明确指定需要消费的发布订阅关联队列。

例如配置中⼼热部署,每个配置中⼼实例都需要指定唯⼀的关联队列名。

12这样就可以和正常的MAC队列消费⼀样,消费指定队列消息。

13
14实现⽅式,四个步骤:
151.创建持久化Topic(即持久化Exchange):
16var service = MQServiceProvider.GetDefaultMQService();
17var messageText = "abc";
18///创建Topic
19using (var connection = service.CreateConnection())
20 {
21var session = connection.CreateSession(MessageAckMode.IndividualAcknowledge);
22var messageCreator = service.GetMessageCreator();
23var message = messageCreator.CreateMessage(messageText);
24 message.IsPersistent = true;
25var producer = session.CreateProducer();
26var topic = session.DeclareTopic(topicName, true);
27 }
282.定义消费者Consumer:
29 List<string> queueList = new List<string>() {
30"guozhiqi1",
31"guozhiqi2",
32"guozhiqi3",
33"guozhiqi4",
34"guozhiqi5",
35"guozhiqi6",
36"guozhiqi7",
37"guozhiqi8",
38"guozhiqi9",
39 };
40//var service = MQServiceProvider.GetDefaultMQService();
41//var messageText = "abc" + DateTime.Now.ToShortTimeString();
42//定义消费者
43using (var connection1 = service.CreateConnection())
44 {
45var session1 = connection1.CreateSession(MessageAckMode.IndividualAcknowledge);
46foreach (var item in queueList)
47 {
48 session1.DeclareQueue(item, true);
49var consumer = session1.CreateTopicConsumer(topicName, item, true);
50 }
51 }
523.发送消息到Topic
53//发送消息
54for (int i = 0; i <= 100; i++)
55 {
56using (var connection = service.CreateConnection())
57 {
58var session = connection.CreateSession(MessageAckMode.IndividualAcknowledge);
59var messageCreator = service.GetMessageCreator();
60var message = messageCreator.CreateMessage(messageText);
61 message.IsPersistent = true;//设置持久化
62 message.TimeToLive = TimeSpan.FromSeconds(30);//设置过期时间
63var producer = session.CreateProducer();
64var topic = session.DeclareTopic(topicName, true);
65 producer.Send(message, topic);
66 }
67 }
684.从队列接收消息
69 Parallel.ForEach(queueList, (item) =>
70 {
71while (true)
72 {
73//接收消息
74using (var connection1 = service.CreateConnection())
75 {
76var session1 = connection1.CreateSession(MessageAckMode.IndividualAcknowledge); 77
78 session1.DeclareQueue(item, true);
79var consumer = session1.CreateTopicConsumer(topicName, item, true);
80var topic = session1.DeclareTopic(topicName, true);
81var receivedmessage = consumer.Receive(topic);
82var textMessage = receivedmessage as ITextMessage;
83
84 Assert.AreEqual(messageText, textMessage.Body);
85 consumer.Acknowledge(receivedmessage);
86 }
87 }
88
89 });
View Code。

相关文档
最新文档