Rabbitmq 概述及代码示例

1、概述


amqp(advanced message queuing protocol)协议是一个高级抽象层消息协议,即高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间价设计。消息中间价主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在。

amqp的特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性和安全性。



2、使用场景


        a、两个系统间需要通过定时任务来同步数据

        b、异构系统的不同进程间相互调用、通讯

        c、生产者消费者模型的使用


3、基本概念


ConnectionFactory、Connection、Channel都是Rabbitmq对外提供的API中最基本的对象。Connection是rabbitmq的socket连接,它封装了socket协议的相关部分逻辑。connectionfactory为Connection的制造工厂。

channel是我们与Rabbitmq打交道的一个借口,我们大部分的业务操作都是在channel这个借口中完成,包括定义queue、发布exchange、绑定queue和exchange、发布消息等。


4、基本消息类型介绍【Queue】


queue是rabbitmq的内部对象,用于存储消息。

rabbitmq中的所有消息都存储在queue中,上图为生产者消费者模型,图一为一个生产者对应一个消费者,图二为一个生产者对应多个(两个)消费者,那么在对应的使用场景下,我们可以应用于这样的场景:A系统和B系统间需要定时通信,如A系统中的一些订单数据需要到B系统中进行实时统计分析,那么将订单数据放入消息队列,B系统从消息队列中读取便实现系统间通讯。场景二:A系统中需要给用户发送打折消息,用户量很大,需要发送时间很长,将需要发送的人员写入消息队列,同时开启多个线程从消息队列中读取人员信息,并发送消息,就是一个生产者对应多个消费者,实现异步处理。
   
   
  1. #!/usr/bin/env python
  2. #生产者端
  3. import pika
  4. import sys
  5. connection = pika.BlockingConnection(pika.ConnectionParameters(
  6. host=\'localhost\'))
  7. channel = connection.channel()
  8. channel.queue_declare(queue=\'task_queue\', durable=True)
  9. message = \' \'.join(sys.argv[1:]) or "Hello World!"
  10. channel.basic_publish(exchange=\'\',
  11. routing_key=\'task_queue\',
  12. body=message,
  13. properties=pika.BasicProperties(
  14. delivery_mode = 2, # make message persistent
  15. ))
  16. print(" [x] Sent %r" % message)
  17. connection.close()
    
    
  1. #!/usr/bin/env python
  2. #消费者端
  3. import pika
  4. import time
  5. connection = pika.BlockingConnection(pika.ConnectionParameters(
  6. host=\'localhost\'))
  7. channel = connection.channel()
  8. channel.queue_declare(queue=\'task_queue\', durable=True)
  9. print(\' [*] Waiting for messages. To exit press CTRL+C\')
  10. def callback(ch, method, properties, body):
  11. print(" [x] Received %r" % body)
  12. time.sleep(body.count(b\'.\'))
  13. print(" [x] Done")
  14. ch.basic_ack(delivery_tag = method.delivery_tag)
  15. channel.basic_qos(prefetch_count=1)
  16. channel.basic_consume(callback,
  17. queue=\'task_queue\')
  18. channel.start_consuming()

5、Message acknowledge
    在应用场景中,我们可能需要掌握消息是否被消费者正确消费,这时消费者消费完后发送一条消息给生产者,队列知道消息被消费了,从队列中移除。
    但是产生另外一个问题,如果开发人员处理完逻辑后,忘了发送回执为rabbitmq,会导致queue中国堆积消息越来越多,消费者重启后消息重复发送给消费者,导致重复执行,因此要注意。

6、message durability

    如果我们希望rabbitmq重启后,消息不丢失,可以设置queue为持久化的,重启后消息依然存在。

7、prefetch count
    有多个消费者时,会出现问题,有的消费者干的活很多,而有的很闲,因此通过设置prefetchCount=1,使得消息公平分发。

8、exchange
    
消息我们需要通过一个交换器,由路由器将消息分发到不同的消费者上,此时消息可能被分发到一个队列或者多个队列。

9、routing key
    生产者将消息发送给exchang的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key 需要与exhcange type 及binding key 联合使用才能生效。
生产者将消息发送给exchange时,通过指定routing key来确定消息流流向。
emit_producer.py
      
      
  1. #!/usr/bin/env python
  2. import pika
  3. import sys
  4. connection = pika.BlockingConnection(pika.ConnectionParameters(
  5. host=\'localhost\'))
  6. channel = connection.channel()
  7. channel.exchange_declare(exchange=\'logs\',
  8. type=\'fanout\')
  9. message = \' \'.join(sys.argv[1:]) or "info: Hello World!"
  10. channel.basic_publish(exchange=\'logs\',
  11. routing_key=\'\',
  12. body=message)
  13. print(" [x] Sent %r" % message)
  14. connection.close()

receive_log.py
       
       
  1. #!/usr/bin/env python
  2. import pika
  3. connection = pika.BlockingConnection(pika.ConnectionParameters(
  4. host=\'localhost\'))
  5. channel = connection.channel()
  6. channel.exchange_declare(exchange=\'logs\',
  7. type=\'fanout\')
  8. result = channel.queue_declare(exclusive=True)
  9. queue_name = result.method.queue
  10. channel.queue_bind(exchange=\'logs\',
  11. queue=queue_name)
  12. print(\' [*] Waiting for logs. To exit press CTRL+C\')
  13. def callback(ch, method, properties, body):
  14. print(" [x] %r" % body)
  15. channel.basic_consume(callback,
  16. queue=queue_name,
  17. no_ack=True)
  18. channel.start_consuming()


10、binding
    rabbitmq通过binding将exchange与queue关联起来,这样rabbitmq就知道如何正确将消息路由到指定的queue了。


11、binding key
    绑定exchange与queue的同时,一般会指定binding key;binding key 与routing key匹配时,消息会被路由到相应的queue。

12、exchange type
    rabbitmq常用的exchange type有fanout、direct、topic、headers四种。

13、fanout
    fanout类型的exchange 路由规则非常简单,他会把所有发送到该exchange的消息路由到所有与他绑定的queue中


14、direct
direct会把消息路由到那些binding key 与routing key 完全匹配的queue中

emit_log_direct.py
         
         
  1. #!/usr/bin/env python
  2. import pika
  3. import sys
  4. connection = pika.BlockingConnection(pika.ConnectionParameters(
  5. host=\'localhost\'))
  6. channel = connection.channel()
  7. channel.exchange_declare(exchange=\'direct_logs\',
  8. type=\'direct\')
  9. severity = sys.argv[1] if len(sys.argv) > 1 else \'info\'
  10. message = \' \'.join(sys.argv[2:]) or \'Hello World!\'
  11. channel.basic_publish(exchange=\'direct_logs\',
  12. routing_key=severity,
  13. body=message)
  14. print(" [x] Sent %r:%r" % (severity, message))
  15. connection.close()
recieve_logs_direct.py
          
          
  1. #!/usr/bin/env python
  2. import pika
  3. import sys
  4. connection = pika.BlockingConnection(pika.ConnectionParameters(
  5. host=\'localhost\'))
  6. channel = connection.channel()
  7. channel.exchange_declare(exchange=\'direct_logs\',
  8. type=\'direct\')
  9. result = channel.queue_declare(exclusive=True)
  10. queue_name = result.method.queue
  11. severities = sys.argv[1:]
  12. if not severities:
  13. sys.stderr.write("Usage: %s [info] [warning] [error]\\n" % sys.argv[0])
  14. sys.exit(1)
  15. for severity in severities:
  16. channel.queue_bind(exchange=\'direct_logs\',
  17. queue=queue_name,
  18. routing_key=severity)
  19. print(\' [*] Waiting for logs. To exit press CTRL+C\')
  20. def callback(ch, method, properties, body):
  21. print(" [x] %r:%r" % (method.routing_key, body))
  22. channel.basic_consume(callback,
  23. queue=queue_name,
  24. no_ack=True)
  25. channel.start_consuming()

15、topic
    前面讲到direct类型的exchange路由规则完全匹配binding key 与routing key,但这种严格的匹配方法在很多情况下不能满足实际需求,可能需要模糊匹配的情况,因此通过topic类型进行模糊匹配
以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。
emit_log_topic.py
         
         
  1. #!/usr/bin/env python
  2. import pika
  3. import sys
  4. connection = pika.BlockingConnection(pika.ConnectionParameters(
  5. host=\'localhost\'))
  6. channel = connection.channel()
  7. channel.exchange_declare(exchange=\'topic_logs\',
  8. type=\'topic\')
  9. routing_key = sys.argv[1] if len(sys.argv) > 1 else \'anonymous.info\'
  10. message = \' \'.join(sys.argv[2:]) or \'Hello World!\'
  11. channel.basic_publish(exchange=\'topic_logs\',
  12. routing_key=routing_key,
  13. body=message)
  14. print(" [x] Sent %r:%r" % (routing_key, message))
  15. connection.close()
client.py
         
         
  1. #!/usr/bin/env python
  2. import pika
  3. import sys
  4. connection = pika.BlockingConnection(pika.ConnectionParameters(
  5. host=\'localhost\'))
  6. channel = connection.channel()
  7. channel.exchange_declare(exchange=\'topic_logs\',
  8. type=\'topic\')
  9. result = channel.queue_declare(exclusive=True)
  10. queue_name = result.method.queue
  11. binding_keys = sys.argv[1:]
  12. if not binding_keys:
  13. sys.stderr.write("Usage: %s [binding_key]...\\n" % sys.argv[0])
  14. sys.exit(1)
  15. for binding_key in binding_keys:
  16. channel.queue_bind(exchange=\'topic_logs\',
  17. queue=queue_name,
  18. routing_key=binding_key)
  19. print(\' [*] Waiting for logs. To exit press CTRL+C\')
  20. def callback(ch, method, properties, body):
  21. print(" [x] %r:%r" % (method.routing_key, body))
  22. channel.basic_consume(callback,
  23. queue=queue_name,
  24. no_ack=True)
  25. channel.start_consuming()


16、headers
    header是根据消息内容中的header属性进行匹配的。

17、RPC
    

  • When the Client starts up, it creates an anonymous exclusive callback queue.
  • For an RPC request, the Client sends a message with two properties: reply_to, which is set to the callback queue and correlation_id, which is set to a unique value for every request.
  • The request is sent to an rpc_queue queue.
  • The RPC worker (aka: server) is waiting for requests on that queue. When a request appears, it does the job and sends a message with the result back to the Client, using the queue from thereply_to field.
  • The client waits for data on the callback queue. When a message appears, it checks thecorrelation_id property. If it matches the value from the request it returns the response to the application.

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

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


附录:
    自己写的一些实验程序
producer.py
       
       
  1. #coding=utf-8
  2. import sys
  3. import pika
  4. import time
  5. import random
  6. """
  7. 在数据处理结束后发送ack,这样RabbitMQ Server会认为Message Deliver 成功。
  8. 持久化queue,可以防止RabbitMQ Server 重启或者crash引起的数据丢失。
  9. 持久化Message,理由同上。
  10. 答案是否定的。问题就在与RabbitMQ需要时间去把这些信息存到磁盘上,这个time window虽然短,但是它的确还是有。在这个时间窗口内如果数据没有保存,数据还会丢失。还有另一个原因就是RabbitMQ并不是为每个Message都做fsync:它可能仅仅是把它保存到Cache里,还没来得及保存到物理磁盘上。
  11. 因此这个持久化还是有问题。但是对于大多数应用来说,这已经足够了。当然为了保持一致性,你可以把每次的publish放到一个transaction中。这个transaction的实现需要user defined codes
  12. 那么商业系统会做什么呢?一种可能的方案是在系统panic时或者异常重启时或者断电时,应该给各个应用留出时间去flash cache,保证每个应用都能exit gracefully
  13. """
  14. class RbMQProducer(object):
  15. def __init__(self):
  16. self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=\'localhost\'))
  17. def send_message(self):
  18. channel = self.connection.channel()
  19. channel.queue_declare(queue=\'hello\')
  20. for i in xrange(10):
  21. time.sleep(1)
  22. message = " ".join(sys.argv[1:]) or "hello world! i am {}".format(i)
  23. channel.basic_publish(exchange=\'\',
  24. routing_key=\'hello\',
  25. body=message)
  26. print(" [x] Sent \'Hello World!\'")
  27. def send_message_durable(self):
  28. channel = self.connection.channel()
  29. channel.queue_declare(queue=\'hello\',durable=True)
  30. for i in xrange(10):
  31. time.sleep(1)
  32. message = " ".join(sys.argv[1:]) or "hello world! i am {}".format(i)
  33. channel.basic_publish(exchange=\'\',
  34. routing_key="hello",
  35. body=message,
  36. properties=pika.BasicProperties(
  37. delivery_mode=2, #make the message persistent
  38. ))
  39. def exchange_message(self):
  40. channel = self.connection.channel()
  41. channel.exchange_declare(exchange=\'logs\',type=\'fanout\')
  42. for i in xrange(10):
  43. message = " ".join(sys.argv[1:]) or "exchange info : Hello world {}!".format(i)
  44. channel.basic_publish(exchange=\'logs\',
  45. routing_key=\'\',
  46. body=message)
  47. print " [{}] send {}".format(i,message)
  48. time.sleep(1.5)
  49. def direct_message(self):
  50. channel = self.connection.channel()
  51. channel.exchange_declare(exchange=\'direct_logs\',
  52. type=\'direct\')
  53. severity = sys.argv[1] if len(sys.argv)>1 else [\'info\',\'warning\',\'error\']
  54. for i in xrange(10):
  55. message = " ".join(sys.argv[2:]) or \'hello world!\'
  56. routing_key_random = severity[random.randint(0,len(severity)-1)]
  57. channel.basic_publish(exchange=\'direct_logs\',
  58. routing_key=routing_key_random,
  59. body=message)
  60. print "[{}]: {}".format(routing_key_random,message)
  61. time.sleep(1.5)
  62. def topic_message(self):
  63. channel = self.connection.channel()
  64. channel.exchange_declare(exchange=\'topic_logs\',
  65. type=\'topic\')
  66. routing_key = sys.argv[1] if len(sys.argv)>1 else ["anonymous.info","lck.info","lck.error"]
  67. for i in xrange(10):
  68. routing_key_value = routing_key[random.randint(0,len(routing_key)-1)]
  69. message = " ".join(sys.argv[2:]) or "hello world! "
  70. channel.basic_publish(exchange=\'topic_logs\',
  71. routing_key=routing_key_value,
  72. body=message)
  73. print "[{}] : {}".format(routing_key_value,message)
  74. time.sleep(1.5)
  75. def rpc_message(self):
  76. channel = self.connection.channel()
  77. channel.queue_declare(queue=\'rpc_queue\')
  78. channel.basic_qos(prefetch_count=1)
  79. channel.basic_consume(self.on_request,queue=\'rpc_queue\')
  80. print "[x] Awaiting rpc requests"
  81. channel.start_consuming()
  82. def on_request(self,ch, method, props, body):
  83. n = int(body)
  84. response = random.randint(100,1000)
  85. print "[.]{}".format(response)
  86. ch.basic_publish(exchange=\'\',
  87. routing_key=props.reply_to,
  88. properties=pika.BasicProperties(correlation_id=props.correlation_id),
  89. body=str(response))
  90. ch.basic_ack(delivery_tag=method.delivery_tag)
  91. def __del__(self):
  92. self.connection.close()
  93. if __name__ == "__main__":
  94. rm_producer = RbMQProducer()
  95. # rm_producer.send_message()
  96. # rm_producer.send_message_durable()
  97. rm_producer.exchange_message()
  98. # rm_producer.direct_message()
  99. # rm_producer.topic_message()
  100. # rm_producer.rpc_message()


receiver.py

      
      
  1. #coding=utf-8
  2. import sys
  3. import pika
  4. import time
  5. import uuid
  6. class RbMQconsume(object):
  7. def __init__(self):
  8. self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=\'localhost\'))
  9. def callback(self, ch, method, properties, body):
  10. print(" [x] Received %r" % body)
  11. time.sleep( body.count(\'.\'))
  12. print(" [x] Done")
  13. def receive_message(self):
  14. channel = self.connection.channel()
  15. channel.queue_declare(queue=\'hello\')
  16. channel.basic_consume(self.callback,
  17. queue=\'hello\',
  18. no_ack=True)
  19. print(\' [*] Waiting for messages. To exit press CTRL+C\')
  20. channel.start_consuming()
  21. def safe_callback(self,ch,method,properties,body):
  22. print " [x] Received %r"%(body)
  23. time.sleep(body.count(\'.\'))
  24. print( " [x] done !")
  25. ch.basic_ack(delivery_tag=method.delivery_tag)
  26. def safe_receive_message(self):
  27. channel = self.connection.channel()
  28. channel.queue_declare(queue=\'hello\',durable=True)
  29. channel.basic_qos(prefetch_count=1)
  30. channel.basic_consume(self.safe_callback,
  31. queue=\'hello\')
  32. print(\' [*] Waiting for messages. To exit press CTRL+C\')
  33. channel.start_consuming()
  34. #exchange方法
  35. def exchange_callback(self,ch,method,properties,body):
  36. print " [ x ] {}".format(body)
  37. def receieve_exchange_message(self):
  38. channel = self.connection.channel()
  39. channel.exchange_declare(exchange=\'logs\',
  40. type=\'fanout\')
  41. result = channel.queue_declare(exclusive=True)
  42. queue_name = result.method.queue
  43. channel.queue_bind(exchange=\'logs\',queue=queue_name)
  44. channel.basic_consume(self.exchange_callback,queue=queue_name,no_ack=True)
  45. channel.start_consuming()
  46. print(\' [*] Waiting for messages. To exit press CTRL+C\')
  47. #direct 方法
  48. def direct_callback(self, ch, method, properties, body):
  49. print " [ {} ] {}".format(method.routing_key,body)
  50. def receieve_direct_message(self):
  51. channel = self.connection.channel()
  52. channel.exchange_declare(exchange=\'direct_logs\',
  53. type =\'direct\')
  54. result = channel.queue_declare(exclusive=True)
  55. queue_name = result.method.queue
  56. severities = [\'warning\', \'error\']
  57. for severity in severities:
  58. channel.queue_bind(exchange=\'direct_logs\',
  59. queue=queue_name,
  60. routing_key=severity)
  61. channel.basic_consume(self.direct_callback,
  62. queue=queue_name,
  63. no_ack=True)
  64. channel.start_consuming()
  65. #topic
  66. def topic_callback(self, ch, method, properties, body):
  67. print " [ {} ] {}".format(method.routing_key, body)
  68. def receieve_topic_message(self):
  69. channel = self.connection.channel()
  70. channel.exchange_declare(exchange=\'topic_logs\',
  71. type=\'topic\')
  72. result = channel.queue_declare(exclusive=True)
  73. queue_name = result.method.queue
  74. binding_keys = ["anonymous.info","lck.info","lck.error"]
  75. binding_key = \'lck.*\'
  76. channel.queue_bind(exchange=\'topic_logs\',
  77. queue=queue_name,
  78. routing_key=binding_key)
  79. channel.basic_consume(self.topic_callback,
  80. queue= queue_name,
  81. no_ack=True)
  82. channel.start_consuming()
  83. #rpc
  84. def rpc_callback(self,ch,method,props,body):
  85. if self.corr_id == props.correlation_id:
  86. self.response = body
  87. def receieve_rpc_message(self,n):
  88. channel = self.connection.channel()
  89. result = channel.queue_declare(exclusive=True)
  90. callback_queue = result.method.queue
  91. channel.basic_consume(self.rpc_callback,
  92. no_ack=True,
  93. queue=callback_queue)
  94. self.response = None
  95. self.corr_id = str(uuid.uuid4())
  96. channel.basic_publish(exchange=\'\',
  97. routing_key=\'rpc_queue\',
  98. properties=pika.BasicProperties(reply_to=callback_queue,correlation_id=self.corr_id),
  99. body=str(n))
  100. while self.response is None:
  101. self.connection.process_data_events()
  102. return int(self.response)
  103. def __del__(self):
  104. self.connection.close()
  105. if __name__=="__main__":
  106. rqconsume = RbMQconsume()
  107. # rqconsume.safe_receive_message()
  108. rqconsume.receieve_exchange_message()
  109. # rqconsume.receieve_direct_message()
  110. # rqconsume.receieve_topic_message()
  111. # print rqconsume.receieve_rpc_message(30)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值