1. 簡介
上節我們提到交換機,而且我們也使用了rabbitmq內置的一種交換機類型——扇形交換機,是將消息發送給所有可知的消息隊列,而本節我們使用rabbitmq內置的另外一種交換機——直連交換機,本節我們基於上節的日誌系統加以改造。在上節我們是將所有消息都發給隊列,所有的消息都在worker中保存到硬盤上,而我們本節是要精簡,現實生活中我們可能會把一些重要的日誌保存到硬盤上作爲後續的分析,所以這一節我們是要對日誌內容進行分爲info|warning|error三級,並且將warning|error記錄到硬盤上,而info只輸出到屏幕上。
2. 路由(routing)
綁定(bindings)
前面我們已經使用過綁定,綁定(binding)是指交換機(exchange)和隊列(queue)的關係。可以簡單理解爲:這個隊列(queue)對這個交換機(exchange)的消息感興趣。
綁定的時候可以帶上一個額外的routing_key參數。爲了避免與basic_publish的參數混淆,我們把它叫做綁定鍵(binding key)。接着上面的形容,我們可以理解爲,這個隊列最這個交換機的哪些消息感興趣。
綁定鍵的意義取決於交換機(exchange)的類型。我們之前使用過的扇型交換機(fanout exchanges)會忽略這個值。channel.queue_bind(exchange=exchange_name, queue=queue_name, routing_key='black')
直連交換機(direct exchange)
我們本節要的做內容,是對日誌內容進行區分,error及warning寫入磁盤,我們上節使用的扇形交換機不夠靈活,她只能把消息廣播,所以我們要使用直連交換機代替扇形交換機。
路由的算法很簡單 —— 交換機將會對綁定鍵(binding key)和路由鍵(routing key)進行精確匹配,從而確定消息該分發到哪個隊列。此場景可以看下圖:
在這個場景中,我們可以看到直連交換機 X和兩個隊列進行了綁定。第一個隊列使用orange作爲綁定鍵,第二個隊列有兩個綁定,一個使用black作爲綁定鍵,另外一個使用green。
這樣以來,當路由鍵爲orange的消息發佈到交換機,就會被路由到隊列Q1。路由鍵爲black或者green的消息就會路由到Q2。其他的所有消息都將會被丟棄。
多個綁定(multiple bindings)
看到這個圖,是不是感覺很眼熟,沒錯,和扇形交換機基本實現了同樣的功能,將消息廣播給所有隊列。但是區別是,扇形交換機是將所有的消息都廣播給隊列,而這裏的直連交換機,我們只是將帶有black路由鍵的消息廣播給隊列Q1和Q2,這也是直連交換機的一種靈活使用方法
總結
我們從整個流程中來總結一下
生產者
創建連接創建交換機(直連交換機)
發送消息到指定交換機,並將消息中指定routing_key
消費者
創建連接創建交換機(直連交換機)
創建匿名隊列(每個消費者都要創建的獨立的隊列,用於接收路由後消息)
創建綁定(通過路由鍵綁定交換機隊列關係,確定隊列處理交換機中的哪些消息)在指定交換機處理消息
代碼整合
emit_logs_direct.pyrecv_logs_direct.py#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2016-02-28 21:28:17 # @Author : mx ([email protected]) # @Link : http://www.shujutiyu.com/ # @Version : $Id$ import os import pika import sys conn = None severity = sys.argv[1] if len(sys.argv) > 1 else 'info' message = ' '.join(sys.argv[2:]) or 'Hello World!' try: # 獲取連接 conn = pika.BlockingConnection(pika.ConnectionParameters('localhost')) # 獲取通道 channel = conn.channel() # 創建直連交換機 channel.exchange_declare(exchange='direct_logs', type='direct') # 在RabbitMQ中發送消息,指定交換機(exchange),指定routing ret = channel.basic_publish(exchange='direct_logs', routing_key=severity, body=message,) print " [x] Sent '{0}'".format(message) print ret except Exception, e: raise e finally: if conn: conn.close()
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2016-02-29 16:30:21 # @Author : mx ([email protected]) # @Link : http://www.shujutiyu.com/ # @Version : $Id$ import pika import sys conn = None severities = sys.argv[1:] # 指定routing_key >> info|warning|error if not severities: print >> sys.stderr, "Usage: %s [info] [warning] [error]" % \ (sys.argv[0],) sys.exit(1) def callback(ch, method, properties, body): """ @ch: channel 通道,是由外部調用時上送 out body 讀取隊列內容做不同的操作 """ print " [x] method.routing_key {0}".format(method.routing_key) print " [x] Done %r" % (body, ) try: # get connection conn = pika.BlockingConnection(pika.ConnectionParameters( 'localhost') ) # get channel channel = conn.channel() # 聲明直連交換機 channel.exchange_declare(exchange='direct_logs', type='direct') # 聲明臨時隊列 , param exclusive 互斥 tmp_queue = channel.queue_declare(exclusive=True) queue_name = tmp_queue.method.queue # 根據輸入routing_key綁定交換機與隊列 for severity in severities: channel.queue_bind( exchange='direct_logs', queue=queue_name, routing_key=severity ) # 在隊列中讀取信息 channel.basic_consume(callback, queue=queue_name, no_ack=True) print ' [*] Waiting for messages. To exit press CTRL+C' channel.start_consuming() except Exception, e: raise e finally: if conn: conn.close()