RabbitMQ學習筆記06:Topics

參考資料:RabbitMQ tutorial - Topics — RabbitMQ 

 

前言

在上一篇博文中我們使用direct類型的exchange改善了我們的日誌系統,但是它仍然有一定的限制,它沒有辦法基於多個條件路由消息。

我們可能不僅僅希望基於日誌級別(嚴重性)來訂閱日誌,也希望可以基於比如說日誌的來源。比如syslog這個unix工具就是這麼工作的,它會基於severity (info, warn, crit...) 和facility (auth, cron, kern...) 來路由。

這樣就比較靈活了,比如我們可能希望獲取crit級別的cron類型日誌同時希望獲取所有級別的kern類型日誌。

想要實現的話,我們需要一個更復雜的exchange,叫做topic

 

Topic exchange

消息發送給topic類型的exchange的時候,不能使用隨意的routing_key,它必須得是一個使用小數點分隔的單詞列表。單詞可以是隨意的不過一般都是會和消息有關係。以下是一些有效的routing_key示例:"stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit"routing_key的大小限制是255字節。

binding_key也必須是同樣的格式。topic exchange背後的邏輯其實和direct exchange是類似的,也是將消息發送給routing_keybinding_key匹配的隊列。不過在binding_key中有兩種特殊的情況:

  • * (star) 替換成一個單詞。
  • # (hash) 替換成零或者多個單詞,即任意個單詞。

在本次的案例中,所有發送的消息都是用來描述動物的。消息發送時的routing_key會包含3個字段的信息

  • 敏捷性 celerity
  • 顏色
  • 物種 species

<celerity>.<colour>.<species>

*.orange.*綁定到隊列Q1上,*.*.rabbitlazy.#綁定到隊列Q2上。

通過剛纔對routing_key的字段的解釋以及綁定的關係,我們可以知道:

  • 隊列Q1對於橙色的動物比較感興趣。
  • 隊列Q2對於兔子或者懶洋洋的動物比較感興趣。

接下來我們舉例說明一些帶有具體的routing_key會進入的隊列:

  • quick.orange.rabbit兩個隊列都會進入。
  • lazy.orange.elephant兩個隊列都會進入。
  • quick.orange.fox只會進入隊列Q1。
  • lazy.brown.fox只會進入隊列Q2。
  • lazy.pink.rabbit雖然它匹配了2個binding_key,但是它只會進入隊列Q2一次。
  • quick.brown.fox沒有任何的binding_key和它匹配,因此這條消息會被丟棄。

雖然上面我們說routing_key只有3個字段,但是由於lazy.#的存在,因此任意字段數都是可以的

  • orange被丟棄。
  • quick.orange.new.rabbit被丟棄。
  • lazy.orange.new.rabbit只會進入隊列Q2。

topic exchange很強大,在某些條件下它可以等同於其他類型的exchange

  • 當僅使用#來綁定隊列時,它可以接收任意的routing_key的消息,即忽略了routing_key,此時就等同於fanout
  • 如果#*都沒有使用而僅使用字符串常量的時候,此時就等同於direct

 

Putting it all together

假設我們的routing_key的形式是<facility>.<severity>

emit_log_topic.py

#!/usr/bin/env python
import pika
import sys

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='topic_logs', exchange_type='topic')

routing_key = sys.argv[1] if len(sys.argv) > 2 else 'anonymous.info'
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()

receive_logs_topic.py

#!/usr/bin/env python
import pika, sys, os

def main():
    connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()

    channel.exchange_declare(exchange='topic_logs', exchange_type='topic')

    result = channel.queue_declare(queue='', exclusive=True)
    queue_name = result.method.queue

    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.decode()))


    channel.basic_consume(
        queue=queue_name, on_message_callback=callback, auto_ack=True)

    channel.start_consuming()


if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('Interrupted')
        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)

測試的方式和以往類似,開啓多個終端來啓動消費者進程,使用不同的binding_key,監聽消息。

python receive_logs_topic.py "#"
python receive_logs_topic.py "kern.*"
python receive_logs_topic.py "*.critical"
python receive_logs_topic.py "kern.*" "*.critical" # 多個binding_key綁定

然後我們發幾條消息測試一下。

python emit_log_topic.py "kern.critical" "A critical kernel error"
python emit_log_topic.py "kern.info" "A critical kernel error"
python emit_log_topic.py "space.critical" "A critical kernel error"
python emit_log_topic.py "log.info" "A critical kernel error"

 

總結

這篇博文介紹了topic類型的exchange,在fanoutdirect的基礎上,通過模式匹配符號來實現更加靈活的消息和隊列的匹配方式。

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