消息队列RabbitMQ

https://www.cnblogs.com/pyedu/p/11866829.html

一. 什么是消息队列

MQ全称为Message Queue 消息队列(MQ)是一种应用程序对应用程序的通信方法。MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中的消息。这样发布者和使用者都不用知道对方的存在。

1. 队列回顾

import queue


q=queue.Queue(maxsize=10) # FIFO

q.put(111)
q.put(222)
q.put(333)

print(q.get())
print(q.get())
print(q.get())
print(q.get())

 

 如果队列空了, 再取值, 则不会报错, 会阻塞住.

 

 这种情况下, 如果队列空了再取值, 则会报错.

二. 为什么要用消息队列

消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。

接下来利用一个外卖系统的消息推送给大家解释下MQ的意义。

消息队列图:

A将信息放到队列中, 如果B, C, D监听了队列, 则可以各取一份. 如果不想取信息了, 可以解绑.

 三. RabbitMQ

RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。

rabbitMQ是一款基于AMQP协议的消息中间件,它能够在应用之间提供可靠的消息传输。在易用性,扩展性,高可用性上表现优秀。使用消息中间件利于应用之间的解耦,生产者(客户端)无需知道消费者(服务端)的存在。而且两端可以使用不同的语言编写,大大提供了灵活性。

3.1 安装

windows下安装需要下载erlang, 有点麻烦.

建议在linux下安装

3.2 工作模型

rabbitmq:


    一 简单模式(广泛应用)
        生产者:
            1 链接rabbitmq
            2 创建队列
            3 向指定的队列插入数据

        消费者
            1 链接rabbitmq
            2 监听模式
            3 确定回调函数

    二 参数使用

        1 应答参数
          channel.basic_consume(queue='hello',
                      auto_ack=False, # 默认应答改为手动应答
                      on_message_callback=callback)

          def callback(ch, method, properties, body):

               print(" [x] Received %r" % body)
               ch.basic_ack(delivery_tag=method.delivery_tag)

        2 持久化参数

           #声明queue
           channel.queue_declare(queue='hello2', durable=True)  # 若声明过,则换一个名字

           channel.basic_publish(exchange='',
                      routing_key='hello2',
                      body='Hello World!',
                      properties=pika.BasicProperties(
                          delivery_mode=2,  # make message persistent
                          )
                      )

    三 交换机模式
         --- 发布订阅

         --- 关键字模式

         --- 通配符模式

 

3.2.1 简单模式

生产者


import pika
# pika是python用于连接RabbitMQ的模块, 需要pip install下载, 且在连接之前需要启动rabbitmq(启动命令: rabbitmq-server, 终止命令: rabbitmqctl)
# 1 链接rabbitmq connection = pika.BlockingConnection(pika.ConnectionParameters('127.0.0.1')) channel = connection.channel() # 2 创建队列 channel.queue_declare(queue='hello') # 3 向指定队列插入数据 channel.basic_publish(exchange='', # 简单模式 routing_key='hello', # 指定队列 body='Hello Yuan!') # 数据 print(" [x] Sent 'Hello Yuan!'") 消费者 import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 创建队列(如果生产者已创建则不会执行, 如果没有创建则会创建队列) channel.queue_declare(queue='hello') # 确定回调函数 def callback(ch, method, properties, body): print(" [x] Received %r" % body) # 确定监听队列参数 channel.basic_consume(queue='hello', # 监听hello队列, 有数据就取出来, 没有数据就blocking auto_ack=True, # 默认应答 on_message_callback=callback) print(' [*] Waiting for messages. To exit press CTRL+C') # 正式监听 channel.start_consuming()

3.2.2 参数

3.2.2.1 应答参数

场景: 生产者将数据放到队列中后, 消费者从队列中取出数据后, 消费者端出现了错误, 导致数据丢失.

生产者


import pika

# 1 链接rabbitmq
connection = pika.BlockingConnection(pika.ConnectionParameters('127.0.0.1'))
channel = connection.channel()

# 2 创建队列
channel.queue_declare(queue='hello')

# 3 向指定队列插入数据
channel.basic_publish(exchange='', # 简单模式
                      routing_key='hello', # 指定队列
                      body='Hello Yuan!')

print(" [x] Sent 'Hello Yuan!'")

消费者
import pika


connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

#  创建队列
channel.queue_declare(queue='hello')


# 确定回调函数
def callback(ch, method, properties, body):

    print(" [x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag) # 拿到确认信号, 让rabbitmq删除队列中的数据.

# 确定监听队列参数
channel.basic_consume(queue='hello',
                      auto_ack=False, # 默认应答改为手动应答, 防止消费者端出错, 造成数据丢失.
                      on_message_callback=callback)


print(' [*] Waiting for messages. To exit press CTRL+C')
# 正式监听
channel.start_consuming()

注意: 
auto_ack=False
ch.basic_ack(delivery_tag=method.delivery_tag)

手动应答会降低效率, 如果追求数据安全则采用应答参数, 如果追求效率则采用自动应答.

3.2.2.2 持久化参数

场景: 生产者将数据放到队列中后, rabbitmq服务器崩掉了, 此时消费者还未获取到数据. 为防止数据丢失, 需要进行持久化存储, 就将数据存储在磁盘上.

生产者


import pika

# 1 链接rabbitmq
connection = pika.BlockingConnection(pika.ConnectionParameters('127.0.0.1'))
channel = connection.channel()

# 2 创可持久化队列
channel.queue_declare(queue='hello3',durable=True)

# 3 向指定队列插入数据
channel.basic_publish(exchange='', # 简单模式
                      routing_key='hello3', # 指定队列
                      body='Hello ALEX!',
                      properties=pika.BasicProperties(
                          delivery_mode=2,  # make message persistent
                      )
                      )

print(" [x] Sent 'Hello ALEX!'")

消费者

import pika


connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

#  创建队列
channel.queue_declare(queue='hello3',durable=True)


# 确定回调函数
def callback(ch, method, properties, body):

    print(" [x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)

# 确定监听队列参数
channel.basic_consume(queue='hello3',
                      auto_ack=False, # 默认应答改为手动应答
                      on_message_callback=callback)


print(' [*] Waiting for messages. To exit press CTRL+C')
# 正式监听
channel.start_consuming()



注意:
#声明queue
channel.queue_declare(queue='hello2', durable=True)  # 若声明过,则换一个名字
 
channel.basic_publish(exchange='',
           routing_key='hello2',
           body='Hello World!',
           properties=pika.BasicProperties(
                          delivery_mode=2,  # make message persistent
                          )
                      )

 

3.2.2.3 分发参数
producer.py

import pika

# 1 链接rabbitmq
connection = pika.BlockingConnection(pika.ConnectionParameters('127.0.0.1'))
channel = connection.channel()

# 2 创可持久化队列
channel.queue_declare(queue='hello4')

# 3 向指定队列插入数据
channel.basic_publish(exchange='', # 简单模式
                      routing_key='hello4', # 指定队列
                      body='Hello 999!',
                      )


customer.py

import pika


connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

#  创建队列
channel.queue_declare(queue='hello4')


# 确定回调函数
def callback(ch, method, properties, body):
    import time
    time.sleep(5)
    print(" [x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)

#  公平分发(谁快谁接收, 而不是轮巡)
channel.basic_qos(prefetch_count=1)

# 确定监听队列参数
channel.basic_consume(queue='hello4',
                      auto_ack=False, # 默认应答改为手动应答
                      on_message_callback=callback)


print(' [*] Waiting for messages. To exit press CTRL+C')
# 正式监听
channel.start_consuming()

注意: 以上模式并不会为每个消费者各发一份, 而是按照顺序, 或者谁快就发给谁, 发完一个再发下一个, 同一份数据不会发给多个消费者.

3.2.3 交换机模式

3.2.3.1 发布订阅模式

 

 生产者执行的步骤:

1. 连接rabbitmq

2. 创建交换机

3. 向交换机中插入数据

消费者执行的步骤:

1. 连接rabbitmq

2. 创建队列

3. 将队列绑定交换机

4. 回调函数等等

注意: 交换机接收到数据后, 会为每一个绑定的队列分发一份数据.

 

生产者(producer.py)

import pika

# 链接rabbitmq
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()


# 声明一个名为logs类型为fanout的交换机
channel.exchange_declare(exchange='logs', # 创建交换机
                         exchange_type='fanout') # fanout:发布订阅模式参数

# 向logs交换机插入数据"info: Hello World!"
message = "info: Hello World!"
channel.basic_publish(exchange='logs',
                      routing_key='',
                      body=message)
print(" [x] Sent %r" % message)
connection.close()

消费者(customer.py)

import pika

# 链接rabbitmq
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# 声明一个名为logs类型为fanout的交换机, 生产者声明了则不执行, 如果生产者没有声明则执行.
channel.exchange_declare(exchange='logs',
                         exchange_type='fanout')

# 创建队列
result = channel.queue_declare("",exclusive=True) # 让系统自动起名字
queue_name = result.method.queue # 拿到队列的名字
print(queue_name)

# 将指定队列绑定到交换机上
channel.queue_bind(exchange='logs',
                   queue=queue_name)

print(' [*] Waiting for logs. To exit press CTRL+C')

def callback(ch, method, properties, body):
    print(" [x] %r" % body)


channel.basic_consume(queue=queue_name,
                      auto_ack=True,
                      on_message_callback=callback)

channel.start_consuming()

 

3.2.3.2 关键字模式

 

 

producer.py

import pika

# 链接rabbitmq
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()


# 声明一个名为logs类型为fanout的交换机
channel.exchange_declare(exchange='logs2',
                         exchange_type='direct') # direct:关键字模式参数

# 向logs交换机插入数据"info: Hello World!"
message = "error: Hello World!"
channel.basic_publish(exchange='logs2',
                      routing_key='error',
                      body=message)
print(" [x] Sent %r" % message)
connection.close()

customer.py

import pika

# 链接rabbitmq
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# 声明一个名为logs类型为fanout的交换机
channel.exchange_declare(exchange='logs2',
                         exchange_type='direct')

# 创建队列
result = channel.queue_declare("",exclusive=True)
queue_name = result.method.queue
print(queue_name)

# 将指定队列绑定到交换机上
channel.queue_bind(exchange='logs2',
                   queue=queue_name,
                   routing_key="info"
                   )
channel.queue_bind(exchange='logs2',
                   queue=queue_name,
                   routing_key="error"
                   )

print(' [*] Waiting for logs. To exit press CTRL+C')

def callback(ch, method, properties, body):
    print(" [x] %r" % body)


channel.basic_consume(queue=queue_name,
                      auto_ack=True,
                      on_message_callback=callback)

channel.start_consuming()

 

3.2.3.3 通配符

"通配符交换机"与之前的路由模式相比,它将信息的传输类型的key更加细化,以“key1.key2.keyN....”的模式来指定信息传输的key的大类型和大类型下面的小类型,让消费者可以更加精细的确认自己想要获取的信息类型。而在消费者一段,不用精确的指定具体到哪一个大类型下的小类型的key,而是可以使用类似正则表达式(但与正则表达式规则完全不同)的通配符在指定一定范围或符合某一个字符串匹配规则的key,来获取想要的信息。

“通配符交换机”(Topic Exchange)将路由键和某模式进行匹配。此时队列需要绑定在一个模式上。符号“#”匹配一个或多个词,符号“*”仅匹配一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*”只会匹配到“audit.irs”。(这里与我们一般的正则表达式的“*”和“#”刚好相反,这里我们需要注意一下。)
下面是一个解释通配符模式交换机工作的一个样例

 

producer.py

import pika

# 链接rabbitmq
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()


# 声明一个名为logs类型为fanout的交换机
channel.exchange_declare(exchange='logs3',
                         exchange_type='topic') # fanout:发布订阅模式参数

# 向logs交换机插入数据"info: Hello World!"
message = "usa.weather......."
channel.basic_publish(exchange='logs3',
                      routing_key='usa.weather',
                      body=message)
print(" [x] Sent %r" % message)
connection.close()

customer.py

import pika

# 链接rabbitmq
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# 声明一个名为logs类型为fanout的交换机
channel.exchange_declare(exchange='logs3',
                         exchange_type='topic')

# 创建队列
result = channel.queue_declare("",exclusive=True)
queue_name = result.method.queue
print(queue_name)

# 将指定队列绑定到交换机上
channel.queue_bind(exchange='logs3',
                   queue=queue_name,
                   routing_key="#.weather"
                   )


print(' [*] Waiting for logs. To exit press CTRL+C')

def callback(ch, method, properties, body):
    print(" [x] %r" % body)


channel.basic_consume(queue=queue_name,
                      auto_ack=True,
                      on_message_callback=callback)

channel.start_consuming()

 

 

posted @ 2021-01-27 18:45  自由者妍  阅读(159)  评论(0)    收藏  举报