092-生产者和消费者模型的小案例(RabbitMQ的简单使用)
RabbitMQ的简单使用案例
一:消费者和生产者模型的理解
01:生产者消费者模型的优点
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体
处理数据的速度。
02:为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,
那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题
于是引入了生产者和消费者模式。
03:什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生
产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和
消费者的处理能力,并且我可以根据生产速度和消费速度来均衡一下多少个生产者可以为多少个消费者提供足够的服务,就可以开多进程等等,而这些进程都是
到阻塞队列或者说是缓冲区中去获取或者添加数据。
二:RabbitMQ的应用:(生产者和消费者之间的4种模式)
ps:为了为了方便生产者和消费之间得联系,当生产者产生数据后,不直接给消费者,而是将消息放到一个队列中,这个队列现在暂时理解为一个服务器RabbitMQ。生产者产生消息,放入队列中,然后消费者持续监听者这个队列,一旦有队列中有消息,则进行获取。
01:简单模式
消费者端代码:
import pika import time # 1 连接rabbitmq connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 2 创建队列 channel.queue_declare(queue='hello') # 3 定义一个回调函数 def callback(ch, method, properties, body): print(" [x] Received %r" % body) # 确定监听事件 channel.basic_consume(callback, queue='hello', no_ack=True ) # 开始监听 channel.start_consuming()
生产者端代码:
import pika # 导入模块,没有,需要安装 pip3 install pika # 第一步:连接到rabbitMQ connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() # 第二步:创建一个队列 channel.queue_declare(queue='hello') # 队列的名字是hello # 第三步:往队列里面填值 ''' exchange="", exchange 为空则代表为简单模式 exchange_type="fanout" 则代表为发布订阅模式 exchange_type="topic" 则代表为关键字模式 exchange_type=direct 则代表为模糊匹配模式 ''' channel.basic_publish(exchange='', routing_key='hello', # routinde_key的值必须要与队列的名字一样 body='Hello Tom') # 这个是生产者产生的数据,会放到rabbitmq里面,然后消费者取得数据(就是生产者要发给消费者的数据) # 第四步:关闭连接 connection.close()
ps:说明:先启动消费者,让消费者持续监听着队列,可以多次执行消费者代码,这样就产生多个消费者(比如执行三次,产生三个消费者持续监听),此时先执行一次生产者代码,通过打印结果
可以看到,只有第一个消费者获得了数据。但是如果再执行2次生产者,则剩下的两个消费者可以依次获得数据。第一个消费者不会再获取数据。类似于队列queue中每次获取一个数据。直到
队列中没有数据了。消费者仍然会持续监听着队列。
特别说明:如果消费者在持续监听着服务端的时候,消费者端发生错误(在回掉函数出现错误),出现bug,那么生产者产生了一个数据,发送到了RbbitMQ服务器,但是消费者没有接收到,
那么就算
消费者端修改了bug,正常运行了,那这个数据也不会被接受了。说明这个数据已经被浪费掉了
------no-ack=False:rabbitmq消费者连接断了 消息不丢失----
解决方法:修改 no_ack=False,
no_ack=False 为有应答模式,
为no_ack=True, 表示没有应答模式.
no_acl=False的时候,如果消费者去拿值的时候, 队列在消费者拿完值后,会先备份,不会立即删除掉。在消费者拿着这个值去自己的代码里面运行的时候,如果没有出现bug
,给队列一个应答信号
这边已经处理完成,队列你可以删除对应的数据了。我已经不再需要了。 同理,no_ack=True 则同理去理解。
02:exchange模式中的三种模式
001:发布订阅模式(就是生产者发送一次信息,所对应的消费者人手一份数据)
消费者代码:
import pika connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel() # 声明交换机 channel.exchange_declare(exchange='logs',exchange_type='fanout') # 声明队列 result = channel.queue_declare(exclusive=True) queue_name = result.method.queue print("queue_name",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(callback, queue=queue_name, no_ack=True) channel.start_consuming()
生产者代码
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel() # 声明一个交换机 channel.exchange_declare(exchange='logs',exchange_type="fanout") message ="info: Hello World!" channel.basic_publish(exchange='logs', routing_key='', body=message) print(" [x] Sent %r" % message) connection.close()
先运行多个消费者,然后运行一次生产者。通过结果可以看到。每个消费者都可以接受到信息
002:关键字模式(# 代表0个或则多个任意单词,单词以 点号 . 分割,一个单词分割一个单词,* 则代表任意一个单词 )
消费者代码:
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel() channel.exchange_declare(exchange='topic_logs', exchange_type='topic') result = channel.queue_declare(exclusive=True) queue_name = result.method.queue channel.queue_bind(exchange='topic_logs', queue=queue_name, routing_key="*.apple.#") #发送到生产者的匹配关键字,如果关键字匹配则从队列中拿取数据,否则不会拿到数据数据 print(' [*] Waiting for logs. To exit press CTRL+C') def callback(ch, method, properties, body): print(" [x] %r:%r" % (method.routing_key, body)) channel.basic_consume(callback, queue=queue_name, no_ack=True) channel.start_consuming()
生产者代码
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel() # 声明一个交换机 channel.exchange_declare(exchange='topic_logs', exchange_type="topic") message = "Hello World!" channel.basic_publish(exchange='topic_logs', routing_key='banana.apple.xigua.juzi', body=message) print(" [x] Sent %r" % message) connection.close()
先运行消费者监听生产者,如果消费者代码中 routing_key的匹配规则和 生产者中的 正则 规则匹配,则消费者可以从队列中拿到数据。反则拿不到数据。生产者会一直监听着等到匹配数据出现!
003:模糊匹配模式
消费者代码
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel() channel.exchange_declare(exchange='direct_logs', exchange_type='direct') result = channel.queue_declare(exclusive=True) queue_name = result.method.queue channel.queue_bind(exchange='direct_logs', queue=queue_name, routing_key="error") print(' [*] Waiting for logs. To exit press CTRL+C') def callback(ch, method, properties, body): print(" [x] %r:%r" % (method.routing_key, body)) channel.basic_consume(callback, queue=queue_name, no_ack=True) channel.start_consuming()
生产者代码
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel() # 声明一个交换机 channel.exchange_declare(exchange='direct_logs',exchange_type="direct") message ="error: Hello World!" channel.basic_publish(exchange='direct_logs', # routing_key='warning', routing_key='error', body=message) print(" [x] Sent %r" % message) connection.close()
先运行消费者,然后再运行生产者,可以得到: 消费者:[x] 'error':b'error: Hello World!',通过修改生产者代码中 message ="error: Hello World!" 和 routing_key='error',注意前面的error 和后面的 routing_key="error"中的error必须一样, 同理,消费者代码中的routing_key="error" 中的 error 也必须和生产者中的一样。当然 error也可以修改为其他的数据。这个就是模糊匹配。
普通人!