問題
python 腳本進行 MQ 消費過慢, rabbitmq 服務端開始累積隊列的時候, 會發現 python 腳本跑滿 CPU
假如消費隊列中, 消息具有前後依賴關係,那麼多線程併發 python 腳本可以保持這個依賴關係嗎例如
消息 ID 1 openstack NOVA 發送消息我要創建雲主機
消息 ID 2 openstack CINDER 發送信息,我要創建一個 10GB 的雲盤
消息 ID 3 把創建的雲盤掛載到雲主機中上述問題純屬假設, 我們不考慮程序的重試機制, 超時機制, 回源機制
那麼就是說,假如如果不順序執行 MQ 任務, 可能會導致錯誤發生
說明
本文只針對 customer 部分進行單進程, 多進程測試
pika 說明
當使用 centos7 進行程序開發時, 可以通過兩種方法安裝 pika 模塊
yum install -y python2-pika-0.10.0-9.el7.noarch (安裝 pika-0.9 版本)
pip install pika (安裝了 pika 1.1.0 版本)
區別
pika 0.9.0
進行 rabbitmq 消費時, 經常會遇到服務器中有消費堆積現象發生
當服務器端有消費堆積時候, 運行 customer python 進程 CPU 消耗會達到 100%
注意使用時參數卻別
result = channel.queue_declare(exclusive=True) 可以不指定 QUEUE 參數
channel.basic_consume(callback,
queue_name='fanout_cmdb3_mq_exchange',
no_ack=False)
pika 1.1.0
解決了對服務端消費堆積問題
沒有再發現 python 腳本 CPU 消耗 100% 問題
注意使用時參數卻別
result = channel.queue_declare(exclusive=True, queue='fanout_cmdb3_mq_exchange') 新版需要指定 queue
channel.basic_consume(queue_name, 參考源碼後發現新版參數位置改變, 另外直接放置 queue_name, 不需要參數, no_ack 參數要替換爲 auto_ack
callback,
auto_ack=False)
python 腳本
rabbitmq 入隊 python 腳本
目的
隊列需要消費者返回消費確認信息
每次執行腳本都會隨機生成 600 條以內的信息, 並向 RABBITMQ 入隊
隊列信息內容 {“id”: 0, “time”: “2020-07-02_13:35:44”}
參考腳本
#!/usr/bin/python
# -*- coding:utf-8 -*-
import pika
import json
import time
import random
parameters = pika.ConnectionParameters(host='rabbitmqserver',
credentials=pika.PlainCredentials('mquser', 'mqpassword'))
connection = pika.BlockingConnection(parameters=parameters)
channel = connection.channel()
# 'win-test' 爲消息隊列
# durable = true 用於持久性保留消息, 只要消費確認後纔會刪除消息
channel.queue_declare(queue='win-test' , durable=True)
now = time.strftime("%Y-%m-%d_%H:%M:%S",time.localtime(time.time()))
randomNum = random.randint(1,600)
for i in range(10):
data = {}
data["id"] = i
data["time"] = now
message=json.dumps(data)
channel.basic_publish(exchange = '',routing_key = 'win-test',body = message,
properties=pika.BasicProperties(delivery_mode = 2)) # 必須確認消費
print(message)
connection.close()
rabbitmq python 消費腳本(單線程)
目的
從 rabbitmq server 中獲取消費隊列
消費並完成腳本操作後進行消息確認
吧消費信息依次寫入到本地文本中, 用於隊列消費順序確認
參考腳本
#!/usr/bin/python
# -*- coding:utf-8 -*-
import pika
import sys
import json
def callback(ch, method, properties, body):
try:
jsonObject = json.loads(body)
except :
print("not json info")
if len(jsonObject) > 0:
writeFile(jsonObject)
ch.basic_ack(delivery_tag = method.delivery_tag) <- 確認消費完成
def writeFile(data):
with open("/tmp/test.txt", 'a') as fileObj:
json.dump(data ,fileObj)
fileObj.write("\n")
if __name__ == '__main__':
parameters = pika.ConnectionParameters(host='rabbitmq-server',
credentials=pika.PlainCredentials('admin', 'admin'))
connection = pika.BlockingConnection(parameters=parameters)
channel = connection.channel()
# fanout 使用的是廣播模式
channel.exchange_declare(exchange='win-test',exchange_type='fanout')
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
channel.queue_bind(exchange='',
queue='win-test')
channel.basic_consume(callback,
queue='win-test',
no_ack=False) # no_ack=False 確保需要 ACK 迴應再確認消費
print(' [*] Waiting for logs. To exit press CTRL+C')
channel.start_consuming()
rabbitmq python 消費腳本(多線程)
說明
腳本工作原理跟單線程一樣
但使用了 5 個進程進行併發消費 用於驗證消費過程中無法按照 queue 中隊列順序進行處理
單線程使用了 pika 0.9 模板, 多線程使用了 pika 1.1.0 模塊,因此登錄方式,callback 方法不一樣
參考腳本
#!/usr/bin/python
# -*- coding:utf-8 -*-
import multiprocessing
import time
import pika
import json
def callback(ch, method, properties, body):
print " [x] %r received %r" % (multiprocessing.current_process(), body,)
try:
jsonObject = json.loads(body)
except :
print("not json info")
if len(jsonObject) > 0:
writeFile(jsonObject)
ch.basic_ack(delivery_tag = method.delivery_tag)
def writeFile(data):
with open("/tmp/test.txt", 'a') as fileObj:
json.dump(data ,fileObj)
fileObj.write("\n")
def consume():
credentials = pika.PlainCredentials('admin', 'admin')
parameters = pika.ConnectionParameters('mqserver', credentials=credentials, heartbeat=5)
connection = pika.BlockingConnection(parameters)
channel = connection.channel()
channel.exchange_declare(exchange='win-test',exchange_type='fanout')
channel.queue_declare(queue='win-test', durable=True)
channel.basic_consume("win-test",
callback,
auto_ack=False)
print ' [*] Waiting for messages. To exit press CTRL+C'
try:
channel.start_consuming()
except KeyboardInterrupt:
pass
workers = 5
pool = multiprocessing.Pool(processes=workers)
for i in xrange(0, workers):
pool.apply_async(consume)
try:
while True:
continue
except KeyboardInterrupt:
print ' [*] Exiting...'
pool.terminate()
pool.join()
結論
參考輸出文件可以知道, 當使用多進行連接 MQ , 無法保證隊列獲取先後順序
{"id": 292, "time": "2020-07-01_17:47:57"}
{"id": 297, "time": "2020-07-01_17:48:33"}
{"id": 298, "time": "2020-07-01_17:48:33"}
{"id": 293, "time": "2020-07-01_17:47:57"}
{"id": 299, "time": "2020-07-01_17:48:33"}
{"id": 294, "time": "2020-07-01_17:47:57"}
{"id": 300, "time": "2020-07-01_17:48:33"}
{"id": 278, "time": "2020-07-01_17:47:59"}
{"id": 279, "time": "2020-07-01_17:47:59"}
{"id": 301, "time": "2020-07-01_17:48:33"}
{"id": 280, "time": "2020-07-01_17:47:59"}
{"id": 302, "time": "2020-07-01_17:48:33"}
{"id": 281, "time": "2020-07-01_17:47:59"}
{"id": 303, "time": "2020-07-01_17:48:33"}
{"id": 283, "time": "2020-07-01_17:50:01"}
{"id": 304, "time": "2020-07-01_17:48:33"}
{"id": 284, "time": "2020-07-01_17:50:01"}
{"id": 305, "time": "2020-07-01_17:48:33"}
{"id": 285, "time": "2020-07-01_17:50:01"}