openstack nova基础知识——RabbitMQ

来源:互联网 发布:西安 国际程序员节 编辑:程序博客网 时间:2024/06/11 12:29

http://www.verydemo.com/demo_c92_i156827.html

nova中各个组件之间的交互是通过“消息队列”来实现的,其中一种实现方法就是使用RabbitMQ,对RabbitMQ的使用,官方文档上有一个非常好的Get Started,由浅及深,结合例子,很容易理解。现在对RabbitMQ的理解,就是利用它可以非常灵活的定制自己想要实现的消息收发机制。

其中,有这样几个角色:producer, consumer, exchange, queue

producer是消息发送者,consumer是消息接受者,中间要通过exchange和queue。producer将消息发送给exchange,exchange决定消息的路由,即决定要将消息发送给哪个queue,然后consumer从queue中取出消息,进行处理,大致流程如下图:


这几个角色当中,我觉得最关键的是这个exchange,它有3种类型:direct, topic, fanout。其中,功能最强大的就是topic,用它完全可以实现direct和fanout的功能。

direct是单条件的路由,即在exchange判断要将消息发送给哪个queue时,判断的依据只能是一个条件;

fanout是广播式的路由,即将消息发送给所有的queue;

topic是多条件的路由,转发消息时,依据的条件是多个,所以只使用topic就可以实现direct和fanout的功能。


上面所说的“条件”,反映到程序中,就是routing_key,这个routing_key出现在两个地方:

    1. 每一个发送的消息都有一个routing_key,表示发送的是一个什么样的消息;

    2. 每一个queue要和exchange绑定,绑定的时候要提供一个routing_key,表示这个queue想要接收什么样的消息。

这样,exchange就可以根据routing_key,来将消息发送到合适的queue中。


基本的思路就这些吧,下面来看一下官方文档上的那由浅及深的六个例子:

(我很喜欢这种风格的文档,整体由浅及深,适合初学者,其次文章没有大量的生僻词汇,而且例子+图片,比较容易懂,更好的是文章还带点小小的幽默,不由得让人汇心一笑,感觉老外做事就是认真细腻,希望自己也能养成这样的风格)


1. Hello World

最简单的情况,发一个消息,接收,打印出来这个消息。


send.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3.   
  4. # 1. Establish a connection with RabbitMQ server.  
  5. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  6.                'localhost'))  
  7. channel = connection.channel()  
  8.   
  9. # 2. Create a queue to which the message will be delivered, let's name it 'hello'  
  10. channel.queue_declare(queue='hello')  
  11.   
  12. # 3. Use a default exchange identified by an empty string, which allows us to specify  
  13. #    exactly to which queue the message should go. The queue name needs to be specified  
  14. #    in the routing_key parameter:  
  15. channel.basic_publish(exchange='',  
  16.                       routing_key='hello',  
  17.                       body='Hello World!')  
  18. print " [x] Sent 'Hello World!'"  
  19.   
  20. # 4. Close the connection  
  21. connection.close()  

recv.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3.   
  4. # 1. Establish a connection with RabbitMQ server  
  5. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  6.         host='localhost'))  
  7. channel = connection.channel()  
  8.   
  9. # 2. Make sure that the queue exists,run the command as many times as we like, and only one will be created.  
  10. channel.queue_declare(queue='hello')  
  11.   
  12. print ' [*] Waiting for messages. To exit press CTRL+C'  
  13.   
  14. # 3. Define a callback function.Whenever we receive a message,   
  15. #    this callback function is called by the Pika library.  
  16. def callback(ch, method, properties, body):  
  17.     print " [x] Received %r" % (body,)  
  18.   
  19. # 4. Subscribe the callback function to a queue.  
  20. #    Tell RabbitMQ that this particular callback function should receive messages from our hello queue.  
  21. channel.basic_consume(callback,  
  22.                       queue='hello',  
  23.                       no_ack=True)  
  24.   
  25. # 5. Enter a never-ending loop that waits for data and runs callbacks whenever necessary.  
  26. channel.start_consuming()  

2. 多个consumer

这个例子跟第一个例子基本上一样,只是启动了多个consumer,并且模拟真实情况,即发送的消息使得consumer在短时间内不能完成工作。在这种情况下,多个consumer是如何协调工作的呢?其实,这些都是可以在程序中进行控制的。


send.py

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3. import sys  
  4.   
  5. # 1. Establish a connection with RabbitMQ server.  
  6. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  7.                'localhost'))  
  8. channel = connection.channel()  
  9.   
  10. # 2. Create a queue to which the message will be delivered, let's name it 'hello'  
  11. #    'durable=True' makes the queue persistent  
  12. channel.queue_declare(queue='task_queue',durable=True)  
  13.   
  14. message=' '.join(sys.argv[1:]) or "Hello World!"  
  15.   
  16. # 3. Use a default exchange identified by an empty string, which allows us to specify  
  17. #    exactly to which queue the message should go. The queue name needs to be specified  
  18. #    in the routing_key parameter:  
  19. channel.basic_publish(exchange='',  
  20.                       routing_key='task_queue',  
  21.                       body=message,  
  22.                       properties=pika.BasicProperties(  
  23.                          delivery_mode = 2# make message persistent  
  24.                       ))  
  25. print " [x] Sent %r" % (message,)  
  26.   
  27. # 4. Close the connection  
  28. connection.close()  

recv.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3. import time  
  4.   
  5. # 1. Establish a connection with RabbitMQ server  
  6. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  7.         host='localhost'))  
  8. channel = connection.channel()  
  9.   
  10. # 2. Make sure that the queue exists,run the command as many times as we like, and only one will be created.  
  11. #    'durable=True' makes the queue persistent  
  12. channel.queue_declare(queue='task_queue',durable=True)  
  13.   
  14. print ' [*] Waiting for messages. To exit press CTRL+C'  
  15.   
  16. # 3. Define a callback function.Whenever we receive a message,   
  17. #    this callback function is called by the Pika library.  
  18. #  
  19. #    Send a ack to tell rabbitmq a task is done, then it can release the message.  
  20. #    If a worker dies, rabbitmq fail to receive the ack, it will redeliver the message to another worker.  
  21. #    Remember to write the last line code, or rabbitmq will eat more and more memory.  
  22. def callback(ch, method, properties, body):  
  23.     print " [x] Received %r" % (body,)  
  24.     time.sleep(body.count('.'))  
  25.     print "[x] Done"  
  26.     ch.basic_ack(delivery_tag = method.delivery_tag)   
  27.   
  28. # Fair dispatch: Tell rabbitmq not give a worker more than one messages at a time  
  29. channel.basic_qos(prefetch_count=1)  
  30.   
  31. # 4. Subscribe the callback function to a queue.  
  32. #    Tell RabbitMQ that this particular callback function should receive messages from our hello queue.  
  33. channel.basic_consume(callback,  
  34.                       queue='task_queue',  
  35.                       no_ack=False)# turn on the (ack)onwledgment, default is False  
  36.   
  37. # 5. Enter a never-ending loop that waits for data and runs callbacks whenever necessary.  
  38. channel.start_consuming()  

3. fanout exchange的例子:


send.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3. import sys  
  4.   
  5. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  6.         host='localhost'))  
  7. channel = connection.channel()  
  8.   
  9. # declare a exchange, type is fanout(means broadcast),named 'logs'.  
  10. # exchange is used to receive messages form producer, and send messages to queue.  
  11. # there are four exchange types: direct, topic, headers and fanout  
  12. channel.exchange_declare(exchange='logs',  
  13.                          type='fanout')  
  14.   
  15. message = ' '.join(sys.argv[1:]) or "info: Hello World!"  
  16. channel.basic_publish(exchange='logs',  
  17.                       routing_key=''#routing_key is '', because 'fanout' exchange will ignore its value.  
  18.                       body=message)  
  19. print " [x] Sent %r" % (message,)  
  20. connection.close()  

recv.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3.   
  4. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  5.         host='localhost'))  
  6. channel = connection.channel()  
  7.   
  8. # if a exchange named 'logs' have not declared yet, then declare one,   
  9. # or just use the existed exchange.  
  10. channel.exchange_declare(exchange='logs',  
  11.                          type='fanout')  
  12.   
  13. # declare a temporary queue with a random name  
  14. # 'exclusive=True' flag will delete the queue when the consumer dies.  
  15. result = channel.queue_declare(exclusive=True)  
  16. queue_name = result.method.queue  
  17.   
  18. # bind the queue to the exchange, to tell the exchange to send messages to our queue.  
  19. channel.queue_bind(exchange='logs',  
  20.                    queue=queue_name)  
  21.   
  22. print ' [*] Waiting for logs. To exit press CTRL+C'  
  23.   
  24. def callback(ch, method, properties, body):  
  25.     print " [x] %r" % (body,)  
  26.   
  27. channel.basic_consume(callback,  
  28.                       queue=queue_name,  
  29.                       no_ack=True)  
  30.   
  31. channel.start_consuming()  

4. direct exchange的例子:

需要注意,一个queue是可以和同一个exchange多次绑定的,每次绑定要用不同的routing_key


send.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3. import sys  
  4.   
  5. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  6.         host='localhost'))  
  7. channel = connection.channel()  
  8.   
  9. # declare a exchange, type is direct, named 'logs'.  
  10. channel.exchange_declare(exchange='direct_logs',  
  11.                          type='direct')  
  12.   
  13. severity = sys.argv[1if len(sys.argv) > 1 else 'info'  
  14. message = ' '.join(sys.argv[2:]) or 'Hello World!'  
  15.   
  16. # a message is sent to the direct exchange with a routing_key.  
  17. # a message is identified by the routing_key.  
  18. channel.basic_publish(exchange='direct_logs',  
  19.                       routing_key=severity,  
  20.                       body=message)  
  21. print " [x] Sent %r:%r" % (severity, message)  
  22. connection.close()  

recv.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3. import sys  
  4.   
  5. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  6.         host='localhost'))  
  7. channel = connection.channel()  
  8.   
  9. # declare a direct exchange named 'direct_logs'  
  10. channel.exchange_declare(exchange='direct_logs',  
  11.                          type='direct')  
  12.   
  13. result = channel.queue_declare(exclusive=True)  
  14. queue_name = result.method.queue  
  15.   
  16. severities = sys.argv[1:]  
  17. if not severities:  
  18.     print >> sys.stderr, "Usage: %s [info] [warning] [error]" % \  
  19.                          (sys.argv[0],)  
  20.     sys.exit(1)  
  21.   
  22. # Bind the queue to the direct exchange,  
  23. # 'routing_key' flag tells the direct exchange which kind of message it wants to receive.  
  24. # A queue can bind multiple times to the same direct exchange with different routing_keys,  
  25. # which means it wants to receive several kinds of messages.  
  26. for severity in severities:  
  27.     channel.queue_bind(exchange='direct_logs',  
  28.                        queue=queue_name,  
  29.                        routing_key=severity)  
  30.   
  31. print ' [*] Waiting for logs. To exit press CTRL+C'  
  32.   
  33. def callback(ch, method, properties, body):  
  34.     print " [x] %r:%r" % (method.routing_key, body,)  
  35.   
  36. channel.basic_consume(callback,  
  37.                       queue=queue_name,  
  38.                       no_ack=True)  
  39.   
  40. channel.start_consuming()  

5. topic exchange的例子

这里的routing_key可以使用一种类似正则表达式的形式,但是特殊字符只能是“*”和“#”,“*”代表一个单词,“#”代表0个或是多个单词。这样发送过来的消息如果符合某个queue的routing_key定义的规则,那么就会转发给这个queue。如下图示例:


send.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3. import sys  
  4.   
  5. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  6.         host='localhost'))  
  7. channel = connection.channel()  
  8.   
  9. # declare a exchange, type is topic, named 'topic_logs'.  
  10. # topic exchange allows to do routing based on multiple criteria.  
  11. channel.exchange_declare(exchange='topic_logs',  
  12.                          type='topic')  
  13.   
  14. severity = sys.argv[1if len(sys.argv) > 1 else 'anonymous.info'  
  15. message = ' '.join(sys.argv[2:]) or 'Hello World!'  
  16.   
  17. # a message is sent to the topic exchange with a routing_key.  
  18. # a message is identified by the routing_key.  
  19. # the topic routing_key can be like 'topic.host','topic.topic1.topic3', etc  
  20. # also can use '*'(one word) and '#'(zero or more words) to substitute word(s).  
  21. channel.basic_publish(exchange='topic_logs',  
  22.                       routing_key=severity,  
  23.                       body=message)  
  24. print " [x] Sent %r:%r" % (severity, message)  
  25. connection.close()  

recv.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3. import sys  
  4.   
  5. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  6.         host='localhost'))  
  7. channel = connection.channel()  
  8.   
  9. # declare a topic exchange named 'topic_logs'  
  10. channel.exchange_declare(exchange='topic_logs',  
  11.                          type='topic')  
  12.   
  13. result = channel.queue_declare(exclusive=True)  
  14. queue_name = result.method.queue  
  15.   
  16. binding_keys = sys.argv[1:]  
  17.   
  18. if not binding_keys:  
  19.     print >> sys.stderr, "Usage: %s [binding_key]..." % (sys.argv[0],)  
  20.     sys.exit(1)  
  21.   
  22. # Bind the queue to the topic exchange,  
  23. # 'routing_key' flag tells the topic exchange which kind of message it wants to receive.  
  24. # A queue can bind multiple times to the same direct exchange with different routing_keys,  
  25. # which means it wants to receive several kinds of messages.  
  26. for binding_key in binding_keys:  
  27.     channel.queue_bind(exchange='topic_logs',  
  28.                        queue=queue_name,  
  29.                        routing_key=binding_key)  
  30.   
  31. print ' [*] Waiting for logs. To exit press CTRL+C'  
  32.   
  33. def callback(ch, method, properties, body):  
  34.     print " [x] %r:%r" % (method.routing_key, body,)  
  35.   
  36. channel.basic_consume(callback,  
  37.                       queue=queue_name,  
  38.                       no_ack=True)  
  39.   
  40. channel.start_consuming()  

6. PRC(Remote Procedure Call,远程过程调用)

目前对这个的理解就是发送一个消息,然后还要得到一个结果,即消息要走一个来回。如下图所示:


client.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3. import uuid  
  4.   
  5. class FibonacciRpcClient(object):  
  6.     def __init__(self):  
  7.         self.connection = pika.BlockingConnection(pika.ConnectionParameters(  
  8.                 host='localhost'))  
  9.   
  10.         self.channel = self.connection.channel()  
  11.   
  12.         result = self.channel.queue_declare(exclusive=True)  
  13.         self.callback_queue = result.method.queue  
  14.   
  15.         self.channel.basic_consume(self.on_response, no_ack=True,  
  16.                                    queue=self.callback_queue)  
  17.   
  18.     def on_response(self, ch, method, props, body):  
  19.         if self.corr_id == props.correlation_id:  
  20.             self.response = body  
  21.   
  22.     def call(self, n):  
  23.         self.response = None  
  24.         self.corr_id = str(uuid.uuid4())  
  25.         self.channel.basic_publish(exchange='',  
  26.                                    routing_key='rpc_queue',  
  27.                                    properties=pika.BasicProperties(  
  28.                                          reply_to = self.callback_queue,  
  29.                                          correlation_id = self.corr_id,  
  30.                                          ),  
  31.                                    body=str(n))  
  32.         while self.response is None:  
  33.             self.connection.process_data_events()  
  34.         return int(self.response)  
  35.   
  36. fibonacci_rpc = FibonacciRpcClient()  
  37.   
  38. print " [x] Requesting fib(30)"  
  39. response = fibonacci_rpc.call(30)  
  40. print " [.] Got %r" % (response,)  

server.py:

查看文本打印?
  1. #!/usr/bin/env python  
  2. import pika  
  3.   
  4. connection = pika.BlockingConnection(pika.ConnectionParameters(  
  5.         host='localhost'))  
  6.   
  7. channel = connection.channel()  
  8.   
  9. channel.queue_declare(queue='rpc_queue')  
  10.   
  11. def fib(n):  
  12.     if n == 0:  
  13.         return 0  
  14.     elif n == 1:  
  15.         return 1  
  16.     else:  
  17.         return fib(n-1) + fib(n-2)  
  18.   
  19. def on_request(ch, method, props, body):  
  20.     n = int(body)  
  21.   
  22.     print " [.] fib(%s)"  % (n,)  
  23.     response = fib(n)  
  24.   
  25.     ch.basic_publish(exchange='',  
  26.                      routing_key=props.reply_to,  
  27.                      properties=pika.BasicProperties(correlation_id = \  
  28.                                                      props.correlation_id),  
  29.                      body=str(response))  
  30.     ch.basic_ack(delivery_tag = method.delivery_tag)  
  31.   
  32. channel.basic_qos(prefetch_count=1)  
  33. channel.basic_consume(on_request, queue='rpc_queue')  
  34.   
  35. print " [x] Awaiting RPC requests"  
  36. channel.start_consuming()  


几个RabbitMQ相关的命令:

查看文本打印?
  1. 1. 查看RabbitMQ中有多少个queue,以及每个queue中有多少个消息:  
  2. $ sudo rabbitmqctl list_queues  
  3.   
  4. 2. 查看RabbitMQ中exchange的情况:  
  5. $ sudo rabbitmqctl list_exchanges  
  6.   
  7. 3. 查看RabbitMQ中exchange和queue绑定情况:  
  8. $  sudo   rabbitmqctl list_bindings  
  9.   
  10. 4. 启动/停止RabbitMQ:  
  11. $  sudo   invoke-rc.d rabbitmq-server stop/start/etc.