參考資料: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_key
和binding_key
匹配的隊列。不過在binding_key
中有兩種特殊的情況:
*
(star) 替換成一個單詞。#
(hash) 替換成零或者多個單詞,即任意個單詞。
在本次的案例中,所有發送的消息都是用來描述動物的。消息發送時的routing_key
會包含3個字段的信息
- 敏捷性 celerity
- 顏色
- 物種 species
<celerity>.<colour>.<species>
*.orange.*
綁定到隊列Q1上,*.*.rabbit
和lazy.#
綁定到隊列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
,在fanout
和direct
的基礎上,通過模式匹配符號來實現更加靈活的消息和隊列的匹配方式。