Docker中部署RabbitMQ並使用Python3.x操作全書(Python操作RabbitMQ看這一篇就夠了)

一 、說明

使用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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章