Rabbitmq 消息隊列

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

enter description here
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!

enter description here
消費者(接收端)這邊看到已經卡主了

enter description here
如果此時單獨在運行一下生產者(發送端),直接可以從消費者看到新收到的消息


rabbitmq 消息分發輪詢

enter description here
重新開啓rabbitmq

enter description here
運行三個接收者(消費者)

enter description here
運行發送者,可以看到被第一個接收者給收到信息了

enter description here
第二次運行發送者,第二個接收者收到信息了

enter description here
第三次運行發送者,第三個接收者收到信息了

上面幾次運行說明了,依次的將信息發送每一個接收者


接收端 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()  # 

enter description here
在centos中重新執行rabbitmq-server start來清空隊列裏的消息
然後在pycharm開啓三個comsumer,在去運行等待接收消息
再去執行producer來發送消息,執行producer後,立即關閉第一個comsumer,這樣消息就會因爲第一個comsumer沒接收成功跑到第二個comsumer去,以此類推。

enter description here
關閉第二個comsumer,第三個comsumer收到信息

enter description here
這張圖是將三個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()  # 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章