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))
关于持久化的进一步讨论:
为了数据不丢失,我们采用了:
- 在数据处理结束后发送ack,这样RabbitMQ Server会认为Message Deliver 成功。
- 持久化queue,可以防止RabbitMQ Server 重启或者crash引起的数据丢失。
- 持久化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()