文章目錄
一 、說明
使用Python操作RabbitMQ的書籍以及例子,少之又少。翻遍了網上所有的例子,發現十個有9個半不能運行的,這半個你還得修改。
原因很簡單,要麼例子的Python版本太低了,要麼例子的RabbitMQ的版本太低了。所以造成了一系列文字。
讓我很痛苦,決定下筆寫一篇關於這個的文章。
Python3.x+RabbitMQ+Docker+Centos
二、安裝RabbitMQ
爲了此篇文章只突出Python+RabbitMQ,就單獨寫了一篇文章給大家:
Centos7.x+Docker部署RabbitMQ
三、編寫操作的代碼
6種模式
這裏我們使用pika來操作RabbitMQ
pip install pika
(一)、簡單的RabbitMQ消息隊列(不安全,不能持久化)
發送端
send.py
import pika
#你的RabbitMQ的地址
host = "替換成自己的RabbitMQ服務器的IP"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
#指定隊列的名字
queueName="hello"
#說明使用的隊列,如果沒有會自動創建
channel.queue_declare(queueName)
#發送的msg消息
msg = "Hello TrueDei"
#n 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=queueName, body=msg)
print(" [x] Sent 'Hello TrueDei'")
connection.close()
發送結果:
可以再web上看到,也收到了
接收端
resv.py
import pika
#你的RabbitMQ的地址
host = "替換成自己的IP"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
#指定隊列的名字
queueName="hello"
#說明使用的隊列,如果沒有會自動創建
channel.queue_declare(queueName)
#將ReceivedMessage添加到隊列中,同時替換通道實現。
#返回的結果,會返回到這裏面,如果有興趣可以點開basic_consume方法去看看源代碼
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
#從服務器隊列消費。
# no_ack=True ,是需要是否確定消息的處理了,告訴服務端
# no_ack=False ,默認是False,可以不寫
channel.basic_consume(queueName,callback,False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
Python收消息:
注意:接收到處於死循環,一直在等待接收,發送一個數據,就收到一個數據
(二)、深入理解消息隊列
1、當有1個生產者,n個消費者時
基於上面的代碼不做任何修改
把上面的消費者開N個就是想要的結果。
如下:
運行3個消費者,生產者生成的消息隊列依次被接收者接收
2、處理消息安全問題(缺持久化)
基於上面代碼,如果消費者出問題了,消息發送將無人接收。
即便再次啓動消費者,之前發生的消息將一直存在隊列中
生產者
send_msg_safe.py
import pika
import time
#你的RabbitMQ的地址
host = "替換成自己的IP"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
message = "Hello World! %s" % time.time()
print("產生的消息:" ,message)
channel.basic_publish(exchange='',
routing_key='task_queue', #
body=message,
# properties=pika.BasicProperties(
# delivery_mode=2, # make message persistent
# )
)
print(" [x] Sent %r" % message)
connection.close()
消費者
recv_msg_safe.py
import pika, time
#你的RabbitMQ的地址
host = "替換成自己的IP"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
time.sleep(20)
print(" [x] Done")
print("method.delivery_tag",method.delivery_tag)
ch.basic_ack(delivery_tag=method.delivery_tag) # 再進行手動確認
channel.basic_consume('task_queue',callback,False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
存在問題:
在於消費者:消費者處理好的消息,需要給服務端回信息
# no_ack=True ,是需要是否確定消息的處理了,告訴服務端
# no_ack=False ,默認是False,可以不寫
# callback 函數後面需要添加 ch.basic_ack(delivery_tag=method.delivery_tag) # 再進行手動確認
3、處理消息安全且持久化
基於上面的代碼,如果重啓了rabbitmq,則存在的消息就消失。需要做到消息持久化。(消息安全且持久化)
生產者
send_msg.py
import pika
#你的RabbitMQ的地址
host = "替換成自己的IP"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
# durable=True:在代理重新啓動後仍然存在
channel.queue_declare(queue='hello10',durable=True)
channel.basic_publish(exchange='',
routing_key='hello10',
body='Hello World!',
properties = pika.BasicProperties( #消息持久化
delivery_mode = 2,)
)
print(" [x] Sent 'Hello World!'")
# 關閉隊列
connection.close()
消費者
recv_msg.py
import pika,time
#你的RabbitMQ的地址
host = "替換成自己的IP"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
channel.queue_declare(queue='hello10',durable=True)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
time.sleep(10)
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume('hello10',callback,False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
存在問題:
問題再於生產者的消息需要被持久化
durable=True:功能是,告訴服務,重啓後消息依然存在
channel.queue_declare(queue='hello10',durable=True)
properties = pika.BasicProperties(
delivery_mode = 2,)
)
(三)、消息的發佈、訂閱以及廣播模式
之前的例子都基本都是1對1的消息發送和接收,即消息只能發送到指定的queue裏,
但有些時候你想讓你的消息被所有的Queue收到,類似廣播的效果,這時候就要用到exchange了,
Exchange在定義的時候是有類型的,以決定到底是哪些Queue符合條件,可以接收消息
fanout: 所有bind到此exchange的queue都可以接收消息
direct: 通過routingKey和exchange決定的那個唯一的queue可以接收消息
topic:所有符合routingKey(此時可以是一個表達式)的routingKey所bind的queue可以接收消息
表達式符號說明:#代表一個或多個字符,*代表任何字符
例:#.a會匹配a.a,aa.a,aaa.a等
*.a會匹配a.a,b.a,c.a等
注:使用RoutingKey爲#,Exchange Type爲topic的時候相當於使用fanout
三種最常用的交換機
direct:“直接連接交換機”
topic:“主題路由匹配交換機”
fanout:“無路由交換機”
1、fanout交換類型
fanout類型的Exchange路由規則非常簡單,它會把所有發送到該Exchange的消息路由到所有與它綁定的Queue中。
上圖中,生產者(P)發送到Exchange(X)的所有消息都會路由到圖中的兩個Queue,
並最終被兩個消費者(C1與C2)消費。
2、direct交換類型
direct類型的Exchange路由規則也很簡單,
它會把消息路由到那些binding key與routing key完全匹配的Queue中。
以上圖的配置爲例,我們以routingKey=”error”發送消息到Exchange,則消息會路由到Queue1(amqp.gen-S9b…,這是由RabbitMQ自動生成的Queue名稱)和Queue2(amqp.gen-Agl…);如果我們以routingKey=”info”或routingKey=”warning”來發送消息,則消息只會路由到Queue2。如果我們以其他routingKey發送消息,則消息不會路由到這兩個Queue中。
3、topic交換類型
前面講到direct類型的Exchange路由規則是完全匹配binding key與routing key,
但這種嚴格的匹配方式在很多情況下不能滿足實際業務需求。
topic類型的Exchange在匹配規則上進行了擴展,它與direct類型的Exchage相似,
也是將消息路由到binding key與routing key相匹配的Queue中,但這裏的匹配規則有些不同,它約定:
- routing key爲一個句點號“. ”分隔的字符串(我們將被句點號“. ”分隔開的每一段獨立的字符串稱爲一個單詞),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
- binding key與routing key一樣也是句點號“. ”分隔的字符串
- binding key中可以存在兩種特殊字符“”與“#”,用於做模糊匹配,其中“”用於匹配一個單詞,“#”用於匹配多個單詞(可以是零個)
以上圖中的配置爲例,routingKey=”quick.orange.rabbit”的消息會同時路由到Q1與Q2,routingKey=”lazy.orange.fox”的消息會路由到Q1與Q2,routingKey=”lazy.brown.fox”的消息會路由到Q2,routingKey=”lazy.pink.rabbit”的消息會路由到Q2(只會投遞給Q2一次,雖然這個routingKey與Q2的兩個bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息將會被丟棄,因爲它們沒有匹配任何bindingKey。
1、廣播模式(fanout,直接連接交換機),發送一個消息,無論有多少接收端,只要在,就能收到,不在就不能收到
生產者
send.py
import pika
#你的RabbitMQ的地址
host = "你的RabbitMQ的地址"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
#設置交換器的名字和要使用的交換類型
channel.exchange_declare(exchange='logs',exchange_type='fanout')
#設置消息
message = "info: Hello World!"
channel.basic_publish(exchange='logs',
routing_key='', #不指定,留空
body=message)
print(" [x] Sent %r" % message)
connection.close()
消費者
recv.py
import pika
#你的RabbitMQ的地址
host = "你的RabbitMQ的地址"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
#設置交換器的名字和要使用的交換類型
channel.exchange_declare(exchange='logs',exchange_type='fanout')
# 不指定queue名字,rabbit會隨機分配一個名字,exclusive=True會在使用此queue的消費者斷開後,自動將queue刪除
result = channel.queue_declare(queue='',exclusive=True)
#獲取生成的隊列名字
queue_name = result.method.queue
channel.queue_bind(exchange='logs', queue=queue_name)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r" % body)
channel.basic_consume(queue_name,callback, True)
channel.start_consuming()
2、組播模式(direct,直接連接交換機)。
RabbitMQ還支持根據關鍵字發送,即:隊列綁定關鍵字,發送者將數據根據關鍵字發送到消息exchange,exchange根據 關鍵字 判定應該將數據發送至指定隊列。
send端根據關鍵字指定發送內容
可發送info,warning,error
recv端根據關鍵字指定接收內容
可接收info,warning,error
生產者
send.py
import pika
import sys #sys模塊再運行的時候,可以接受用戶輸入的值
#你的RabbitMQ的地址
host = "你的RabbitMQ的地址"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
channel.exchange_declare(exchange='direct_logs',exchange_type='direct')
#如果運行(python test.py hello) 的話,sys.argv[1]就可以拿到這個hello這個詞
severity = sys.argv[1] if len(sys.argv) > 1 else 'info' #如果運行的時候,後面跟了數據,就替換info這個值,否則默認就是info
# severity = 'info' #如果運行的時候,後面跟了數據,就替換info這個值,否則默認就是info
#消息體
message = ' '.join(sys.argv[2:]) or 'Hello World!' #拿到後面跟的第二個值,默認是Hello World!
#綁定交換的類型,綁定隊列,綁定發送的消息
channel.basic_publish(exchange='direct_logs',routing_key=severity,body=message)
print(" [x] Sent %r:%r" % (severity, message))
connection.close()
消費者
recv.py
import pika
import sys
#你的RabbitMQ的地址
host = "你的RabbitMQ的地址"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
channel.exchange_declare(exchange='direct_logs',
exchange_type='direct')
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
severities = sys.argv[1:]
if not severities:
sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
sys.exit(1)
for severity in severities:
channel.queue_bind( queue_name, 'direct_logs', routing_key=severity)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r:%r" % (method.routing_key, body))
channel.basic_consume( queue_name, callback, True)
channel.start_consuming()
結果:建議放大看
說明:
在上圖中可以看到運行的命令:
send.py
執行示例,需說明發送的內容級別
根據Python代碼可以給出公式:python send.py 模式 發送的消息
python send.py info TestInfo1
python send.py warning TestWarning1
python send.py error TestError1
recv.py
執行示例,需說明接收代碼內容
執行示例,需說明發送的內容級別
根據Python代碼可以給出公式:python recv.py 模式
python recv.py info
python recv.py warning
python recv.py error
3、組播模式之基於組播模式(topic,主題路由匹配交換機),實現更細緻的劃分不同種類的信息。
形象點可以如下圖:
生產者
topic_send.py
import pika
import sys
#你的RabbitMQ的地址
host = "你的RabbitMQ的地址"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
#指定使用的交換類型和交換器
channel.exchange_declare(exchange='topic_logs',exchange_type='topic')
#如果有輸入,就拿到輸入的第一個數據爲隊列,否則默認爲:anonymous.info(匿名的,當然了,可以隨便修改哦)
routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'
#如果輸入的數據,存在第二個,那麼就把第二個當作消息,發送出去。否則默認消息就是:Hello World!
message = ' '.join(sys.argv[2:]) or 'Hello World!'
#綁定交換的類型,綁定隊列,綁定發送的消息
channel.basic_publish(exchange='topic_logs', routing_key=routing_key, body=message)
#提示
print(" [x] Sent %r:%r" % (routing_key, message))
#關閉
connection.close()
# python topic_send.py python.error test 發送了一條python的錯誤信息,錯誤內容爲test
# python topic_send.py mysql.info hello 發送了一條mysql的信息,信息內容爲hello
消費者
topic_recv.py
import pika
import sys
#你的RabbitMQ的地址
host = "你的RabbitMQ的地址"
#RabbitMQ端口號
post = 5672
#創建的賬號,當然了也可以使用默認的guest賬號,密碼也是guest
username = "admin"
#賬號的密碼
password = "123456"
# 創建一個有憑證的新實例
credentials = pika.PlainCredentials(username, password)
# 使用憑證連接RabbitMQ服務器
connection = pika.BlockingConnection(pika.ConnectionParameters(host,post,credentials=credentials))
#聲明一個管道
channel = connection.channel()
#指定使用的交換類型和交換器
channel.exchange_declare(exchange='topic_logs',exchange_type='topic')
#自動產生一個隊列,exclusive=True:自動銷燬
result = channel.queue_declare(queue='',exclusive=True)
#獲取自己產生的隊列名字
queue_name = result.method.queue
#拿到輸入的第一個數爲key,就在這個上面監聽
binding_keys = sys.argv[1:]
if not binding_keys:
sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
sys.exit(1)
for binding_key in binding_keys:
channel.queue_bind(exchange='topic_logs',queue=queue_name,routing_key=binding_key)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r:%r" % (method.routing_key, body))
channel.basic_consume( queue_name,callback,True)
channel.start_consuming()
# python topic_recvive.py # 使用" # "號收所有
# python topic_recvive.py mysql.* 使用"mysql.* "號收來自mysql的信息
# python topic_recvive.py mysql.error.* 使用"mysql.error.* "號收來自mysql的錯誤信息
# python topic_recvive.py *.django.* 使用"*.django.* "號收來自所有Django的信息
測試結果1
測試結果2
還有監聽全部的可以收到
測試結果3
總結:
發送端1
python topic_send.py mysql.info ThisMysqlInfoMsg
[x] Sent 'mysql.info':'ThisMysqlInfoMsg'
python topic_send.py mysql.error.Insert ThisMysqlErrorInsertMsg
[x] Sent 'mysql.error.Insert':'ThisMysqlErrorInsertMsg'
python topic_send.py python.data Pythonaaaa
[x] Sent 'python.data':'Pythonaaaa'
接收端1
python topic_recv.py #
[*] Waiting for logs. To exit press CTRL+C
[x] 'mysql.info':b'ThisMysqlInfoMsg'
[x] 'mysql.error.Insert':b'ThisMysqlErrorInsertMsg'
[x] 'python.data':b'Pythonaaaa'
接收端2
python topic_recv.py mysql.*
[*] Waiting for logs. To exit press CTRL+C
[x] 'mysql.info':b'ThisMysqlInfoMsg'
接收端3
python topic_recv.py mysql.error.*
[*] Waiting for logs. To exit press CTRL+C
[x] 'mysql.error.Insert':b'ThisMysqlErrorInsertMsg'
接收端4
python topic_recv.py python.*
[*] Waiting for logs. To exit press CTRL+C
[x] 'python.data':b'Pythonaaaa'
四、問題集整理以及常見的錯誤
1、錯誤碼403
賬號密碼錯誤
2、錯誤碼404
出現404,大多數就是連接的地址有問題,或者斷網了也會造成
3、錯誤碼405
出現這個405,肯定是有已經在運行的程序了,被佔用了。要先結束掉,纔可以運行這個
4、新版與老版本的常見問題
第一處:關於callback與queue_name的位置
老版本:callback與queue_name的位置換了
channel.basic_consume( callback,queue_name ,True)
新版本:callback與queue_name的位置換了
channel.basic_consume( queue_name,callback,True)
第二處:關於隊列名
老版本:可以不指定隊列,就會自動生成
result = channel.queue_declare(exclusive=True)
新版本:必須指定一個空的就行
result = channel.queue_declare(queue='',exclusive=True)
參考文章
https://blog.csdn.net/fwk19840301/article/details/92986072
https://blog.csdn.net/banzhi8397/article/details/101392965
https://blog.csdn.net/Da___Vinci/article/details/100105451
https://www.cnblogs.com/shenyixin/p/9084249.html