Python RabbitMQ 持久化

RabbitMQ 持久化:

  消息的可靠性是RabbitMQ的一大特色,那么RabbitMQ是如何保证消息可靠性的呢——消息持久化。 为了保证RabbitMQ在退出、异常情况下数据没有丢失,需要将queue,exchange和Message都持久化。

  queue持久化是要再队列声明的时候设置durable = True;需要注意的是,如果RabbitMQ Server中已经声明一个名叫'hello'的话,那这句话是不会修改原'hello'队列的属性,所以,需要Producer和Consumer都要声明这个队列。

1 channel.queue_declare(queue = 'hello', durable=True)

  Message的持久化是要在数据Publish的时候,添加一个Properties参数:

1 channel.basic_publish(exchange = '',
2                       routing_key = 'hello',
3                       body = stdin_str,
4                       properties = pika.BasicProperties(delivery_mode=2))

关于持久化的进一步讨论:

    为了数据不丢失,我们采用了:

  1. 在数据处理结束后发送ack,这样RabbitMQ Server会认为Message Deliver 成功。
  2. 持久化queue,可以防止RabbitMQ Server 重启或者crash引起的数据丢失。
  3. 持久化Message,理由同上。

  但是这样能保证数据100%不丢失吗?

  答案是否定的。问题就在与RabbitMQ需要时间去把这些信息存到磁盘上,这个time window虽然短,但是它的确还是有。在这个时间窗口内如果数据没有保存,数据还会丢失。还有另一个原因就是RabbitMQ并不是为每个Message都做fsync:它可能仅仅是把它保存到Cache里,还没来得及保存到物理磁盘上。

  因此这个持久化还是有问题。但是对于大多数应用来说,这已经足够了。当然为了保持一致性,你可以把每次的publish放到一个transaction中。这个transaction的实现需要user defined codes。

  那么商业系统会做什么呢?

  一种可能的方案是在系统panic时或者异常重启时或者断电时,应该给各个应用留出时间去flash cache,保证每个应用都能exit gracefully。

2.消息什么时候刷到磁盘? 

  写入文件前会有一个Buffer,大小为1M,数据在写入文件时,首先会写入到这个Buffer,如果Buffer已满,则会将Buffer写入到文件(未必刷到磁盘)。 有个固定的刷盘时间:25ms,也就是不管Buffer满不满,每个25ms,Buffer里的数据及未刷新到磁盘的文件内容必定会刷到磁盘。 每次消息写入后,如果没有后续写入请求,则会直接将已写入的消息刷到磁盘:使用Erlang的receive x after 0实现,只要进程的信箱里没有消息,则产生一个timeout消息,而timeout会触发刷盘操作。

Producer :

import pika
# 创建一个connection
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
# 创建一个管道
channel = connection.channel()
# 声明一个Queue,名字为'hello'
channel.queue_declare(queue = 'hello',durable=True)

while True:
    stdin_str = input('>>>')
    channel.basic_publish(exchange = '',
                          routing_key = 'hello',
                          body = stdin_str,
                          properties = pika.BasicProperties(delivery_mode=2))
    print(" [x] Sent '{}'".format(stdin_str))
connection.close()

Consumer :

import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
#创建一个管道
channel = connection.channel()
#声明QUEUE
channel.queue_declare(queue='hello',durable= True)
#回调函数
def callback(ch,method,properties,body):
    print(" [x] Received %r" % body.decode())

channel.basic_consume(callback,
                      queue = 'hello',
                      no_ack = False) #这里讲no_ack设置为有应答,以保护数据不被丢失
channel.basic_qos(prefetch_count=1) #让 Consumer 谁执行完谁去Queue中领任务继续执行,以达到资源合理分配。
print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming()

 

posted @ 2017-07-18 19:16  LeeeetMe  阅读(290)  评论(0编辑  收藏  举报