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_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的基础上,通过模式匹配符号来实现更加灵活的消息和队列的匹配方式。

浙公网安备 33010602011771号