RabbitMQ 消息隊列介紹
RabbitMQ是一種消息隊列,與線程queue和進程QUEUE作用是一樣的。
RabbitMQ是一箇中間程序,可以實現不同進程之間的通信(比如python和Java之間,QQ和Word之間等);
普通情況下A進程與B進程之間通信,兩者之間需要建立很多連接和單獨寫一些代碼,但是使用RabbitMQ的話就可以實現幫助不同進程之間的數據通信。
A進程交給RabbitMQ,RabbitMQ在交給B,同樣B交給RabbitMQ,RabbitMQ在交給A,RabbitMQ可以實現A與B進程之間的連接和信息轉換。
使用RabbitMQ可以實現很多個獨立進程之間的交互,所有其他獨立進程都可以用RabbitMQ作爲中間程序。
py 消息隊列:
線程 queue(同一進程下線程之間進行交互)
進程 Queue(父子進程進行交互 或者 同屬於同一進程下的多個子進程進行交互)
如果是兩個完全獨立的python程序,也是不能用上面兩個queue進行交互的,或者和其他語言交互有哪些實現方式呢。
【Disk、Socket、其他中間件】這裏中間件不僅可以支持兩個程序之間交互,可以支持多個程序,可以維護好多個程序的隊列。
雖然可以通過硬盤的方式實現多個獨立進程交互,但是硬盤速度比較慢,而RabbitMQ則能夠很好的、快速的幫助兩個獨立進程實現交互。
像這種公共的中間件有好多成熟的產品:
RabbitMQ
ZeroMQ
ActiveMQ
……
RabbitMQ:erlang語言 開發的。
Python中連接RabbitMQ的模塊:pika 、Celery(分佈式任務隊列) 、haigha
可以維護很多的隊列
其中pika是RabbitMQ常用的模塊
RabbitMQ 教程官網:http://www.rabbitmq.com/getstarted.html
幾個概念說明:
Broker:簡單來說就是消息隊列服務器實體。
Exchange:消息交換機,它指定消息按什麼規則,路由到哪個隊列。
Queue:消息隊列載體,每個消息都會被投入到一個或多個隊列。
Binding:綁定,它的作用就是把exchange和queue按照路由規則綁定起來。
Routing Key:路由關鍵字,exchange根據這個關鍵字進行消息投遞。
vhost:虛擬主機,一個broker裏可以開設多個vhost,用作不同用戶的權限分離。
producer:消息生產者,就是投遞消息的程序。
consumer:消息消費者,就是接受消息的程序。
channel:消息通道,在客戶端的每個連接裏,可建立多個channel,每個channel代表一個會話任務
![image_1cfq381mj7341i1b4lnitk1h3t9.png-178.4kB][1]
RabbitMQ不像之前學的python Queue都在一個隊列裏實現交互,RabbitMQ有多個隊列(圖中紅色部分代表隊列),每個隊列都可以將消息發給多個接收端(C是接收端,P是生產消息端)
RabbitMQ基本示例.
1、Rabbitmq 安裝
Windos系統
pip install pika
ubuntu系統
install rabbitmq-server # 直接搞定
以下centos系統
1)Install Erlang
# For EL5:
rpm -Uvh http://download.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.rpm
# For EL6:
rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
# For EL7:
rpm -Uvh http://download.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-8.noarch.rpm
yum install erlang
2)Install RabbitMQ Server
rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc
yum install rabbitmq-server-3.6.5-1.noarch.rpm
3)use RabbitMQ Server
chkconfig rabbitmq-server on
service rabbitmq-server stop/start
或者
rabbitmq-server start
rabbitmq已經開啓,等待傳輸
2、基本示例
發送端 producer
import pika
# 建立一個實例;相當於建立一個socket。
#通過ctrl+ConnectionParameters可以看到能傳很多參數,如果遠程還可以傳用戶名密碼。
connection = pika.BlockingConnection(
pika.ConnectionParameters('localhost',5672) # 默認端口5672,可不寫
)
# 聲明一個管道,在管道里發消息
channel = connection.channel()
# 在管道里聲明queue
channel.queue_declare(queue='hello')
# RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange='',
routing_key='hello', # queue名字,將消息發給hello這個queue
body='Hello World!') # 消息內容
print(" [x] Sent 'Hello World!'")
connection.close() # 發完消息後關閉隊列
執行結果:
[x] Sent 'Hello World!'
注意一定要開啓rabbitmq,否則會報錯
接收端 consumer
import pika
import time
# 建立實例
connection = pika.BlockingConnection(pika.ConnectionParameters(
'localhost'))
# 聲明管道
channel = connection.channel()
# 爲什麼又聲明瞭一個‘hello’隊列?
# 如果這個queue確定已經聲明瞭,可以不聲明。但是你不知道是生產者還是消費者先運行,所以要聲明兩次。如果消費者沒聲明,且消費者先運行的話,就會報錯。
# 生產者先聲明queue,消費者不聲明,但是消費者後運行就不會報錯。
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body): # 四個參數爲標準格式
print(ch, method, properties) # 打印看一下是什麼
# ch是管道內存對象地址;method是內容相關信息 properties後面講 body消息內容
print(" [x] Received %r" % body)
#time.sleep(15)
#ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume( # 消費消息
'hello', # 你要從哪個隊列裏收消息
callback, # 如果收到消息,就調用callback函數來處理消息 # 注意調用的函數以前模塊是放在形參第一個位置的,後面修改到第二個位置了,如果放錯位置會報錯
# no_ack=True # 寫的話,如果接收消息,機器宕機消息就丟了
# 一般不寫。宕機則生產者檢測到發給其他消費者
)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming() # 開始消費消息(開始接收消息,一直收,如果沒消息就卡主在這裏了)
執行結果:
[*] Waiting for messages. To exit press CTRL+C
<BlockingChannel impl=<Channel number=1 OPEN conn=<SelectConnection OPEN transport=<pika.adapters.utils.io_services_utils._AsyncPlaintextTransport object at 0x7f715d76f128> params=<ConnectionParameters host=localhost port=5672 virtual_host=/ ssl=False>>>> <Basic.Deliver(['consumer_tag=ctag1.b728277178e746118699d5b4302a0314', 'delivery_tag=1', 'exchange=', 'redelivered=False', 'routing_key=hello'])> <BasicProperties>
[x] Received b'Hello World!'
收到了bytes格式的 Hello World!
消費者(接收端)這邊看到已經卡主了
如果此時單獨在運行一下生產者(發送端),直接可以從消費者看到新收到的消息
rabbitmq 消息分發輪詢
重新開啓rabbitmq
運行三個接收者(消費者)
運行發送者,可以看到被第一個接收者給收到信息了
第二次運行發送者,第二個接收者收到信息了
第三次運行發送者,第三個接收者收到信息了
上面幾次運行說明了,依次的將信息發送每一個接收者
接收端 consumer
import pika
import time
# 建立實例
connection = pika.BlockingConnection(pika.ConnectionParameters(
'localhost'))
# 聲明管道
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(ch, method, properties)
print(" [x] Received %r" % body)
# 正常回調函數(callback)執行完成就表示信息接收完成,如果在還沒執行完成時就出現異常就表示信息沒有正常接收,比如斷網、斷電等,會導致信息不能正常接收。
# 下面sleep 60秒,在60秒之前就將該模塊終止執行來模擬異常情況。
time.sleep(60)
#ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume(
'hello',
callback,
# no_ack=True 表示不管消息是否接收(處理)完成,都不會回覆確認消息
# 如果producer不關心 comsumer是否處理完,可以使用該參數
# 但是一般都不會使用它
# no_ack=True
)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming() #
在centos中重新執行rabbitmq-server start來清空隊列裏的消息
然後在pycharm開啓三個comsumer,在去運行等待接收消息
再去執行producer來發送消息,執行producer後,立即關閉第一個comsumer,這樣消息就會因爲第一個comsumer沒接收成功跑到第二個comsumer去,以此類推。
關閉第二個comsumer,第三個comsumer收到信息
這張圖是將三個comsumer同時都關閉了,這樣三個comsumer都收不到消息,說明producer的消息沒有被接收,此時再去開啓第一個comsumer,這時第一個comsumer會將消息給接收過來。
我們將sleep註釋掉,也是這種現象,這是因爲comsumer並沒有發送確認消息給producer
import pika
import time
# 建立實例
connection = pika.BlockingConnection(pika.ConnectionParameters(
'localhost'))
# 聲明管道
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(ch, method, properties)
print(" [x] Received %r" % body)
time.sleep(30)
ch.basic_ack(delivery_tag = method.delivery_tag) # 告訴生成者,消息處理完成
channel.basic_consume(
'hello',
callback,
# no_ack=True
)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming() #
此時的代碼:當其中一個comsumer執行完成,併發送確認消息後再去中斷,下一個comsumer就不會收到消息;反之,如果還沒發送確認消息就中斷了,那麼消息就會被下一個comsumer接收到。
rabbitmq 消息持久化
如果producer端宕機,那麼隊列的數據也會消失;這樣就需要讓隊列消息持久化
# durable=True 該代碼只是將生成的隊列持久化(不是消息),如果producer宕機,隊列會存在,單消息會丟
# 要注意需要在producer端和 comsumer端都要 寫durable=True
channel.queue_declare(queue='hello',durable=True)
在centos重新開啓 rabbitmq-server start
在producer端
將producer代碼執行三次,將三個消息放入隊列
import pika
。
connection = pika.BlockingConnection(
pika.ConnectionParameters('localhost',5672)
)
channel = connection.channel()
channel.queue_declare(queue='hello',durable=True)
channel.basic_publish(exchange='',
routing_key='hello',
body='Hello World!',
# 下面的代碼是讓消息持久化
properties = pika.BasicProperties(delivery_mode=2)
)
print(" [x] Sent 'Hello World!'")
connection.close()
將producer代碼執行三次,將三個消息放入隊列
import pika
import time
connection = pika.BlockingConnection(pika.ConnectionParameters(
'localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello',durable=True)
def callback(ch, method, properties, body):
print(ch, method, properties)
print(" [x] Received %r" % body)
# time.sleep(30) #註釋掉
ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume(
'hello',
callback
)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming() #