RabbitMQ

RabbitMQ

  • RabbitMQ是由LShift提供的一個Advanced Message Queuing Protocol(AMQP)的開源實現,由以高性能、健壯以及可伸縮性出名的Erlang寫成,因此也是繼承了這些優點。

安裝

yum -y install erlang-21.3.8.6-1.el7.x86_64.rpm rabbitmq-server-3.7.16-1.el7.noarch.rpm

  • 查看安裝的文件
[root@xdd ~]# rpm -ql rabbitmq-server 
/etc/logrotate.d/rabbitmq-server
/etc/profile.d/rabbitmqctl-autocomplete.sh
/etc/rabbitmq
/usr/lib/ocf/resource.d/rabbitmq/rabbitmq-server
/usr/lib/ocf/resource.d/rabbitmq/rabbitmq-server-ha
/usr/lib/rabbitmq/autocomplete/bash_autocomplete.sh
/usr/lib/rabbitmq/autocomplete/zsh_autocomplete.sh
/usr/lib/rabbitmq/bin/cuttlefish
/usr/lib/rabbitmq/bin/rabbitmq-defaults

配置

環境配置

  • 使用系統環境變量,如果沒有使用rabbitmq-env.conf中定義環境變量,否則使用缺省值
RABBITMQ_NODE_IP_ADDRESS the empty string, meaning that it should bind to all network interfaces.  
RABBITMQ_NODE_PORT 5672  
RABBITMQ_DIST_PORT RABBITMQ_NODE_PORT + 20000  #內部節點和客戶端工具通信用  
RABBITMQ_CONFIG_FILE 配置文件路徑默認爲/etc/rabbitmq/rabbitmq  

環境變量文件,可以不配置

工作特性配置文件

  • rabbitmq.config配置文件
  • 3.7支持新舊兩種配置文件格式
  1. erlang配置文件格式,爲了兼容繼續採用

rabbitmq_001
2. sysctl格式,如果不需要兼容,RabbitMQ鼓勵使用。 (這個文件也可以不配置)

rabbitmq_002

插件管理

列出所有可用插件

rabbitmq-plugins list

  • 啓動WEB管理插件,會依賴啓用其他幾個插件。
[root@xdd rabbitmq]$ rabbitmq-plugins enable rabbitmq_management

啓動服務

systemctl start rabbitmq-server

  • 啓動中,可能出現下面的錯誤
    1. Error when reading /var/lib/rabbitmq/.erlang.cookie:eacces這就是這個文件的權限問題,修改屬組、屬組爲rabbitmq即可
      chown rabbitmq.rabbitmq /var/lib/rabbitmq/.erlang.cookie
  • 服務啓動成功
[root@xdd ~]# ss -tanl | grep 5672
LISTEN     0      128          *:25672                    *:*
LISTEN     0      128          *:15672                    *:*
LISTEN     0      128         :::5672                    :::*
[root@xdd ~]#

用戶管理

  • 開始登陸WEB界面,http://192.168.61.108(rabbitmq所在主機的ip):15672
    rabbitmq_003

  • 使用guest/guest只能本地登陸,遠程登錄會報錯

  1. rabbitmqctl命令
    • rabbitmqctl [-n <node>] [-1][-q] <command> [<command options>]
    • General options:
      1. -n node
      2. -q,–quiet
      3. -t,–timeout timeout
      4. -l longnames
    • Commands:
      1. add_user <username> <password> 添加用戶
      2. list_user 列出用戶
      3. delete_user username 刪除用戶
      4. change_password <username> <password> 修改用戶名,密碼
      5. set_user_tags <username> <tag> [...] 設置用戶tag
      6. list_user_permissions 列出用戶權限
  • 添加用戶:rabbitmqctl add_user username password

  • 刪除用戶:rabbitmqctl delete_user username

  • 更改密碼:rabbitmqctl change_password username newpassword

  • 設置權限Tags,其實就是分配組:rabbitmqctl set_user_tags username tag

  • 設置xdd用戶爲管理員tag後登陸

# rabbitmqctl add_user gdy gdy  #添加xdd用戶
# rabbitmqctl list_users #查看所有用戶
# rabbitmqctl set_user_tags gdy administrator #設置xdd用戶爲管理員用戶
  • tag的意義如下:

    1. administrator可以管理用戶、權限、虛擬主機。
      rabbitmq_004
  • 基本信息(web管理端口15672,協議端口5672)
    rabbitmq_005

  • 虛擬主機

    1. 缺省虛擬主機,默認只能是guest用戶在本機鏈接,下圖新建的用戶gdy默認無法訪問任何虛擬主機
      rabbitmq_006

Pika庫

  • Pika是純Python實現的支持AMQP協議的庫
    1. pip install pika

RabbitMQ工作原理及應用

工作模式

rabbitmq_007

rabbitmq_008

  • 名詞解釋
名詞 說明
Server 服務器
接受客戶端連接,實現消息隊列及路由功能的進程(服務),也稱爲消息代理
注意:客戶端可用生產者,也可以是消費者,它們都需要連接到Server
Connection 網絡物理連接
Channel 一個連接允許多個客戶端連接
Exchange 交換器。接收生產者發來的消息,決定如何路由給服務器中的隊列。
常用的類型有:
direct(point-to-point)
topic(publish-subscribe)
fanout(multicast)
Message 消息
Message Queue 消息隊列,數據的存儲載體
Bind 綁定
建立消息隊列和交換器之間的關係,也就是說交換器拿到數據,把什麼樣的數據送給哪個隊列
Virtual Host 虛擬主機
一批交換器、消息隊列和相關對象的集合。爲了多用戶互不干擾,使用虛擬主機分組交換機,消息隊列
Topic 主題、話題
Broker 可等價爲Server

1.隊列

  1. 注意:出現如下運行結果
pika.exceptions.ProbableAuthenticationError: (403, 'ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.')
  • 訪問被拒絕,還是權限問題,原因還是guest用戶只能訪問localhost上的缺省虛擬主機
  • 解決辦法
    1. 缺省虛擬主機,默認只能在本機訪問,不要修改爲遠程訪問,是安全的考慮。
    2. 因此,在Admin中Virtual hosts中,新建一個虛擬主機test。
    3. 注意:新建的test虛擬主機的Users是誰,本次是gdy用戶

rabbitmq_010

  • 在ConnectionParameters中沒有用戶名、密碼填寫的參數,它使用參數credentials傳入,這個需要構建一個pika.credentials.Credentials對象。

  • 參照官方例子,寫一個小程序

# send.py
import pika
from pika.adapters.blocking_connection import BlockingChannel

#構建用戶名密碼對象
credential = pika.PlainCredentials("gdy","gdy")
# 配置鏈接參數
params = pika.ConnectionParameters(
    "192.168.61.108",#ip地址
    5672,  #端口
    "test",#虛擬機
    credential #用戶名密碼
)

# # 第二種建立連接方式
# params = pika.URLParameters("amqp://gdy:[email protected]:5672/test")

# 建立連接
connection = pika.BlockingConnection(params)

with connection:
    # 建立通道
    channel:BlockingChannel = connection.channel()

    #創建一個隊列,queue命名爲hello,如果queue不存在,消息將被dropped
    channel.queue_declare(queue="hello")

    channel.basic_publish(
        exchange="",#使用缺省exchange
        routing_key="hello", #routing_key必須指定,這裏要求和目標queue一致
        body="Hello world" #消息
    )
    print("消息發送成功Sent Message OK")
  • 測試通過。去服務管理界面查看Exchanges和Queues。
    rabbitmq_011

  • URLParameters,也可以使用URL創建參數

# amqp://username:password@host:port/<virtual_host>[?query-string] 
parameters = pika.URLParameters('amqp://guest:guest@rabbit-server1:5672/%2F') 
# %2F指代/,就是缺省虛擬主機
  1. queue_declare聲明一個queue,有必要可以創建。
  2. basic_publish exchange爲空就使用缺省exchange,如果找不到指定的exchange,拋異
  • 使用缺省exchange,就必須指定routing_key,使用它找到queue
  • 修改上面生產者代碼,讓生產者連續發送send Message。在web端查看Queues中Ready的變化
# send.py
import pika
from pika.adapters.blocking_connection import BlockingChannel
import time

# 第二種建立連接方式
params = pika.URLParameters("amqp://gdy:[email protected]:5672/test")
# 建立連接
connection = pika.BlockingConnection(params)

with connection:
    # 建立通道
    channel:BlockingChannel = connection.channel()

    #創建一個隊列,queue命名爲hello,如果queue不存在,消息將被dropped
    channel.queue_declare(queue="hello")

    for i in range(40):

        channel.basic_publish(
            exchange="",#使用缺省exchange
            routing_key="hello", #routing_key必須指定,這裏要求和目標queue一致
            body="data{:02}".format(i) #消息
        )
        time.sleep(0.5)
    print("消息發送成功Sent Message OK")

rabbitmq_012

  • 構建receive.py消費者代碼
  1. 單個消費消息
    • BlockingChannel.basic_get(“queue名稱”,是否阻塞)->(method,props,body)
      • body爲返回的消息
# receie.py
import pika
from pika.adapters.blocking_connection import BlockingChannel

# 建立連接
params = pika.URLParameters("amqp://gdy:[email protected]:5672/test")
connection = pika.BlockingConnection(params)

with connection:
    channel:BlockingChannel = connection.channel()
    msg = channel.basic_get("hello",True) #從名稱爲hello的queue隊列中獲取消息,獲取不到阻塞
    method,props,body = msg #拿不到的消息tuple爲(None,None,None)
    if body:
        print("獲取到了一個消息Get A message = {}".format(body))
    else:
        print("沒有獲取到消息empty")

rabbitmq_013

  • 獲取到消息後msg的結構如下:
(<Basic.GetOk(['delivery_tag=1', 'exchange=', 'message_count=38', 'redelivered=False', 'routing_key=hello'])>, <BasicProperties>, b'data01')  
返回元組:(方法method,屬性properties,消息body)
無數據返回:(None,None,None)
  1. 批量消費消息recieve.py
# receie.py 消費代碼
import pika
from pika.adapters.blocking_connection import BlockingChannel

# 建立連接
params = pika.URLParameters("amqp://gdy:[email protected]:5672/test")
connection = pika.BlockingConnection(params)

def callback(channel,method,properties,body):
    print("Get a message = {}".format(body))

with connection:
    channel:BlockingChannel = connection.channel()
    channel.basic_consume(
        "hello",#隊列名
        callback,#消費回調函數
        True,#不迴應
    )
    print("等待消息,退出按CTRL+C;Waiting for messages. To exit press CTRL+C")
    channel.start_consuming()

rabbitmq_014

2.工作隊列

rabbitmq_015

  • 繼續使用隊列模式的生產者消費者代碼,啓動2個消費者。觀察結果,可以看到,2個消費者是交替拿到不同的消息。
  • 這種工作模式時一種競爭工作方式,對某一個消息來說,只能有一個消費者拿走它。
    1. 從結果知道,使用的是輪詢方式拿走數據的。
    2. 注意:雖然上面的圖中沒有畫出exchange。用到缺省exchange

3.發佈、訂閱模式(Publish/Subscribe)

rabbitmq_016

  • Publish/Subscribe發佈訂閱,想象一下訂閱者(消費者)訂閱這個報紙(消息),都應該拿到一份同樣內容的報紙。
  • 訂閱者和消費者之間還有一個exchange,可以想象成郵局,消費者去郵局訂閱報紙,報社發報紙到郵局,郵局決定如何投遞到消費者手中。
  • 上例子中工作隊列模式的使用,相當於,每個人只能拿到不同的報紙。所以不適合發佈訂閱模式。
  1. 當模式的exchange的type是fanout,就是一對多,即廣播模式。
  2. 注意,同一個queue的消息只能被消費一次,所以,這裏使用了多個queue,相當於爲了保證不同的消費者拿到同樣的數據,每一個消費者都應該有自己的queue。
# 生成一個交換機
channel.exchange_declare(
    exchange="logs", #新交換機
    exchange_type="fanout" #廣播
)
  • 生產者使用廣播模式。在test虛擬主機中構建了一個logs交換機
  1. 至於queue,可以由生產者創建,也可以由消費者創建。
  2. 本次採用使用消費者端創建,生產者把數據都發往交換機logs,採用了fanout,然後將數據通過交換機發往已經綁定到此交換機的所有queue。
    rabbitmq_017
  • 綁定Bingding,建立exchange和queue之間的聯繫
# 消費者端
result =channel.queue_declare(queue="") #生成一個隨機名稱的queue
resutl = channel.queue_declare(queue="",exclusive=True) #生成一個隨機名稱的queue,並在斷開鏈接時刪除queue

# 生成queue
q1:Method = channel.queue_declare(queue="",exclusive=True)
q2:Method = channel.queue_declare(queue="",exclusive=True)
q1name = q1.method.queue #可以通過result.method.queue 查看隨機名稱
q2name = q2.method.queue

print(q1name,q2name)

#綁定
channel.queue_bind(exchange="logs",queue=q1name)
channel.queue_bind(exchange="logs",queue=q2name)
  • 生成者代碼
    1. 注意觀察 交換機和隊列
# send.py 生產者代碼
import pika
from pika.adapters.blocking_connection import BlockingChannel
import time

# 建立連接
params = pika.URLParameters("amqp://gdy:[email protected]:5672/test")
connection = pika.BlockingConnection(params)
channel:BlockingChannel = connection.channel()

with connection:
    #指定交換機和模式
    channel.exchange_declare(
        exchange="logs",#新交換機
        exchange_type="fanout" #扇出,廣播
    )

    for i in range(40):
        channel.basic_publish(
            exchange="logs",#使用指定的exhcange
            routing_key="", #廣播模式,不指定routing_key
            body = "data-{:02}".format(i) #消息
        )
        time.sleep(0.01)
    print("消息發送完成")
  1. 特別注意:如果先開啓生產者,由於沒有隊列queue,請觀察數據
  • 消費者代碼
    1. 構建queue並綁定到test虛擬機的logs交換機上
# receie.py 消費者代碼

import time
import pika
from pika.adapters.blocking_connection import BlockingConnection
from pika.adapters.blocking_connection import BlockingChannel

connection:BlockingConnection = pika.BlockingConnection(pika.URLParameters("amqp://gdy:[email protected]:5672/test"))
channel:BlockingChannel = connection.channel()
# 指定交換機
channel.exchange_declare(exchange="logs",exchange_type="fanout")

q1 = channel.queue_declare(queue="",exclusive=True)
q2 = channel.queue_declare(queue="",exclusive=True)
name1 = q1.method.queue #隊列名
name2 = q2.method.queue

#爲交換機綁定queue
channel.queue_bind(exchange="logs",queue=name1)
channel.queue_bind(exchange="logs",queue=name2)

def callback(channel,method,properties,body):
    print("{}\n{}".format(channel,method))
    print("獲取了一個消息 Get a message = {}".format(body))

with connection:
    #爲第一個隊列綁定消費者函數
    channel.basic_consume(
        name1,#隊列名
        callback, #消費者回調函數
        True #不迴應
    )
    #爲第二個隊列綁定消費者函數
    channel.basic_consume(name2,callback,True)

    print("等待消息,退出按CTRL+C;Waiting for messages. To exit press CTRL+C")
    channel.start_consuming()
  • 先啓動消費者receie.py可以看到已經創建了exchange

rabbitmq_018

  • 如果exchange是fanout,也就是廣播了,routing_key就不用關心了。

rabbitmq_019

q1 = channel.queue_declare(queue="",exclusive=True)
q2 = channel.queue_declare(queue="",exclusive=True)
  • 注意:演示時要先啓動消費者,再啓動生產。如果先嚐試啓動生產者,在啓動消費者會導致部分數據丟失。因爲:exchange收了數據,沒有queue接受,所以,exchange丟棄了這些數據。

4.路由模式Routing

rabbitmq_020

  • 路由其實就是生產者的數據經過exhange的時候,通過匹配規則,決定數據的去向。
  1. 生產者代碼,交換機類型爲direct,指定路由的key
# send生產者
import time
import pika
import random
from pika.adapters.blocking_connection import BlockingConnection
from pika.adapters.blocking_connection import BlockingChannel

exchangename = "color"
colors = ("orange","black","green")

#建立連接
connection:BlockingConnection = pika.BlockingConnection(pika.URLParameters("amqp://gdy:[email protected]:5672/test"))
channel:BlockingChannel = connection.channel()

with connection:
    channel.exchange_declare(
        exchange=exchangename,#使用指定的exchange
        exchange_type="direct" #路由模式
    )

    for i in range(40):
        rk = random.choice(colors)
        msg = "{}-data-{:02}".format(rk,i)
        channel.basic_publish(
            exchange=exchangename,#
            routing_key=rk,#指定routing_key
            body=msg #消息
        )
        print(msg,"----")
        time.sleep(0.01)
    print("消息發送完成 Sent ok")
  1. 消費者代碼
# receie.py消費者
import time
import pika
import random
from pika.adapters.blocking_connection import BlockingConnection
from pika.adapters.blocking_connection import BlockingChannel

exchangename = "color"
colors = ("orange","black","green")

#建立連接
connection:BlockingConnection = pika.BlockingConnection(pika.URLParameters("amqp://gdy:[email protected]:5672/test"))
channel:BlockingChannel = connection.channel()
channel.exchange_declare(exchange=exchangename,exchange_type="direct")

# 生成隊列,名稱隨機,exclusive=True斷開刪除該隊列
q1 = channel.queue_declare(queue="",exclusive=True)
q2 = channel.queue_declare(queue="",exclusive=True)
name1 = q1.method.queue #查看隊列名
name2 = q2.method.queue
print(name1,name2)

#綁定到交換機,而且一定要綁定routing_key
channel.queue_bind(exchange=exchangename,queue=name1,routing_key=colors[0])
channel.queue_bind(exchange=exchangename,queue=name2,routing_key=colors[1])
channel.queue_bind(exchange=exchangename,queue=name2,routing_key=colors[2])

def callback(channel,method,properties,body):
    print("{}\n{}".format(channel,method))
    print("獲取了一個消息get a message = {}".format(body))
    print()

with connection:
    channel.basic_consume(
        name1,#隊列名
        callback, #消息回調函數
        True #不迴應
    )
    channel.basic_consume(name2,callback,True)
    print("等待消息,退出按CTRL+C;Waiting for messages. To exit press CTRL+C")
    channel.start_consuming()

rabbitmq_021

  • 注意:如果routing_key設置一樣,綁定的時候指定routing_key=‘black’,如下圖。和fanout就類似了,都是1對多,但是不同。
    rabbitmq_022

    1. 因爲fanout時,exchange不做數據過濾,1個消息,所有綁定的queue都會拿到一個副部。
    2. direct時候,要按照routing_key分配數據,上圖的black有2個queue設置了,就會把1個消息分發給這2個queue。

5.Topic話題

rabbitmq_023

  • Topic就是更加高級的路由,支持模式匹配而已。
  • Topic的routing_key必須使用.點號分割的單詞組成。最多255個字節。
  • 支持使用通配符:
    1. *表示嚴格的一個單詞
    2. #表示0個或多個單詞
  • 如果queue綁定的routing_key只是一個#,這個queue其實可以接收所有的消息。
  • 如果沒有使用任何通配符,效果類似於direct,因爲只能和字符串匹配了。
  1. 生產者代碼
# send.py生產者代碼
import time
import pika
import random
from pika.adapters.blocking_connection import BlockingConnection
from pika.adapters.blocking_connection import BlockingChannel

exchangename = "products"
#產品和顏色搭配
colors = ("orange","black","green")
topics = ("phone.*","*.red") #兩種話題
product_type = ("phone","pc","tv") #3種產品

#建立連接
connection:BlockingConnection = pika.BlockingConnection(pika.URLParameters("amqp://gdy:[email protected]:5672/test"))
channel:BlockingChannel = connection.channel()
#指定交換機爲話題模式
channel.exchange_declare(exchange=exchangename,exchange_type="topic")


with connection:
    for i in range(40):
        rk = "{}.{}".format(random.choice(product_type),random.choice(colors))
        msg = "{}-data-{:02}".format(rk,i)
        channel.basic_publish(
            exchange=exchangename,#使用指定的exchange
            routing_key=rk,#指定routing_key
            body=msg #消息
        )
        print(msg,"-----")
        time.sleep(0.5)
    print("消息發送完成 Sent ok")
  1. 消費者代碼
# recieve.py 消費者代碼
import time
import pika
import random
from pika.adapters.blocking_connection import BlockingConnection
from pika.adapters.blocking_connection import BlockingChannel
# 虛擬機名稱
exchangename = "products"


#建立連接
connection:BlockingConnection = pika.BlockingConnection(pika.URLParameters("amqp://gdy:[email protected]:5672/test"))
channel:BlockingChannel = connection.channel()
#指定虛擬機,交換機爲話題模式
channel.exchange_declare(exchange=exchangename,exchange_type="topic")

# 生成隊列,名稱隨機,exclusive=True斷開刪除該隊列
q1 = channel.queue_declare(queue="",exclusive=True)
q2 = channel.queue_declare(queue="",exclusive=True)
name1 = q1.method.queue #查看隊列名
name2 = q2.method.queue
print(name1,name2)

#綁定到交換機,而且一定綁定routing_key
#q1只收集phone開頭的routing_key的消息,也就是說只管收集類型信息
channel.queue_bind(exchange=exchangename,queue=name1,routing_key="phone.*")
# q2只收集red結尾的routing_key的消息,也就是說只管紅色的信息
channel.queue_bind(exchange=exchangename,queue=name2,routing_key="*.red")

def callback(channel,method,properties,body):
    print("{}\n{}".format(channel,method))
    print("獲取了一個消息Get a message = {}".format(body))
    print()

with connection:
    channel.basic_consume(
        name1,#隊列名
        callback,#消息回調函數
        True #不迴應
    )
    channel.basic_consume(name2,callback,True)

    print("等待消息,退出按CTRL+C;Waiting for messages. To exit press CTRL+C")
    channel.start_consuming()

rabbitmq_024

  • 觀察消費者拿到的數據,注意觀察phone.red的數據出現次數。
    rabbitmq_025

  • 由此,可以知道交換機在路由消息的時候,只要和queue的routing_key匹配,就把消息發給該queue。

RPC遠程過程調用

  • RabbitMQ的RPC的應用場景較少,因爲有更好的RPC通信框架。

消息隊列的作用

  1. 系統間解耦
  2. 解決生產者、消費者速度匹配
  • 由於稍微上規模的項目都會分層、分模塊開發,模塊間或系統間儘量不要直接耦合,需要開放公共接口提供給別的模塊或系統調用,而調用可能觸發併發問題,爲了緩衝和解耦,往往採用中間件技術。
  • RabbitMQ只是消息中間件中的一種應用程序,也是較常用的中間件服務。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章