python操作RabbitMQ

一、安装RabbitMQ

https://blog.csdn.net/yangxiaodong88/article/details/83088569

二 用Python操作 RabbitMQ

RabbitMQ 是一个在AMQP 基础上完成的, 可复用的企业消息系统。他遵循Mozilla Public License开源协议。

2.1 基于queue实现的消费者生成者模型

import Queue
import threading

message = Queue.Queue(10)

def producer(i):
    while True:
        message.put(i)

def consumer(i):
    while True:
        msg = message.get()


for i in range(12):
    t = threading.Thread(target=producer, args=(i,))
    t.start()

for i in range(10):
    t = threading.Thread(target=consumer, args=(i,))
    t.start()

2.2 RabbitMQ 实现消息队列

对于RabbitMQ 来说, 生成者和消费者不再针对内存里的一个queue对象, 而是某个服务器上的RabbitMQ Server 实现的消息队列。 先运行消费者脚本, 让他监听队列消息, 然后运行生产者脚本, 生成者往队列里面发送消息, 消费者从队列里面取消息。

import pika

# ########################### 消费者 ###########################

connection = pika.BlockingConnection(pika.ConnectionParameters(
                host='192.168.137.208'))
channel = connection.channel()

channel.queue_declare(queue='abc')  # 如果队列没有创建,就创建这个队列

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

channel.basic_consume(callback,
                      queue='abc',  # 队列名
                      no_ack=True)  # 不通知已经收到,如果连接中断可能消息丢失

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

生产者

import pika
# ############################## 生产者 ##############################
connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='192.168.137.208'
))
channel = connection.channel()
channel.queue_declare(queue='abc')  # 如果队列没有创建,就创建这个队列
channel.basic_publish(exchange='',
                      routing_key='abc',   # 指定队列的关键字为,这里是队列的名字
                      body='Hello World!')  # 往队列里发的消息内容
print(" [x] Sent 'Hello World!'")
connection.close()

2.3 no-ack=False:rabbitmq消费者连接断了 消息不丢失

RabbitMQ 支持一种应答方式:应答。比如我从消息里拿一条消息,如果全处理完,你就不要帮我记着了。如果没处理完,突然断开了,再连接上的时候,消息队列就会重新发消息。

总结

  • Basic.Ack 发回给 RabbitMQ 以告知,可以将相应 message 从 RabbitMQ 的消息缓存中移除。
  • Basic.Ack 未被 consumer 发回给 RabbitMQ 前出现了异常,RabbitMQ 发现与该 consumer 对应的连接被断开,之后将该 message 以轮询方式发送给其他 consumer (假设存在多个 consumer 订阅同一个 queue)
  • 在 no_ack=true 的情况下,RabbitMQ 认为 message 一旦被 deliver 出去了,就已被确认了,所以会立即将缓存中的 message 删除。所以在 consumer 异常时会导致消息丢失。
  • 来自 consumer 侧的 Basic.Ack 与 发送给 Producer 侧的 Basic.Ack 没有直接关系

注意

1)只有在Consumer(消费者)断开连接时,RabbitMQ才会重新发送未经确认的消息。
2)超时的情况并未考虑:无论Consumer需要处理多长时间,RabbitMQ都不会重发消息。

消息不丢失的关键代码:
1)在接收端的callback最后:

channel.basic_ack(delivery_tag=method.delivery_tag)

ack即acknowledge(承认,告知已收到)
也就是消费者每次收到消息,要通知一声:已经收到,如果消费者连接断了,rabbitmq会重新把消息放到队列里,下次消费者可以连接的时候,就能重新收到丢失消息。

2)除了callback函数,还要在之前设置接收消息时指定no_ack(默认False):

channel.basic_consume(callback, queue='hello', no_ack=False)  

消费者:

import pika
  # ########################### 消费者 ##########################
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='10.211.55.4'))
channel = connection.channel()

channel.queue_declare(queue='hello')

def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
    import time
    time.sleep(10)
    print('ok')
    ch.basic_ack(delivery_tag = method.delivery_tag)

channel.basic_consume(callback,
                      queue='hello',
                      no_ack=False)

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

消费者断掉连接,再次连接,消息还会收到。

2.4 durable:rabbitmq服务端宕机 消息不丢失

发数据的时候,就说了:我这条数据要持久化保存。
如果rabbitmq服务端机器如果挂掉了,会给这台机器做持久化。如果启动机器后,消息队列还在。

生产者.py


import pika
# ############################## 生产者 ##############################

connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.211.55.4'))
channel = connection.channel()

# make message persistent
channel.queue_declare(queue='hello', durable=True)

channel.basic_publish(exchange='',
                      routing_key='hello',
                      body='Hello World!',
                      properties=pika.BasicProperties(
                          delivery_mode=2, # make message persistent
                      ))
print(" [x] Sent 'Hello World!'")
connection.close()

消费者.py

import pika
# ########################### 消费者 ###########################

connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.211.55.4'))
channel = connection.channel()

# make message persistent
channel.queue_declare(queue='hello', durable=True)


def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
    import time
    time.sleep(10)
    print('ok')
    ch.basic_ack(delivery_tag = method.delivery_tag)

channel.basic_consume(callback,
                      queue='hello',
                      no_ack=False)

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

2.5 消息获取顺序

默认消息队列里的数据是按照顺序被消费者拿走,例如:消费者1 去队列中获取 奇数 序列的任务,消费者1去队列中获取 偶数 序列的任务。

因为默认是跳着取得。第一个消费者取得很快,已经执行到20了,但是第二个消费者只取到13,可能消息执行的顺序就有问题了。

如果多个消费者,如果不想跳着取,就按消息的顺序取,而不是按着自己的间隔了。

channel.basic_qos(prefetch_count=1) 表示谁来谁取,不再按照奇偶数排列 , 根据消费能力取

#!/usr/bin/env python
# -*- coding:utf-8 -*-
__author__ = 'Python Yang'
import pika

# ########################### 消费者 ###########################
connection = pika.BlockingConnection(pika.ConnectionParameters(host='192.168.137.208'))
channel = connection.channel()

# make message persistent
channel.queue_declare(queue='hello1')


def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
    import time
    time.sleep(10)
    print('ok')
    ch.basic_ack(delivery_tag = method.delivery_tag)

channel.basic_qos(prefetch_count=1)

channel.basic_consume(callback,
                      queue='hello1',
                      no_ack=False)

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

2.6 发布订阅

发布订阅原理:
1)发布订阅和简单的消息队列区别在于,发布订阅会将消息发送给所有的订阅者,而消息队列中的数据被消费一次便消失。
2)所以,RabbitMQ实现发布和订阅时,会为每一个订阅者创建一个队列,而发布者发布消息时,会将消息放置在所有相关队列中。

3)exchange 可以帮你发消息到多个队列!type设为什么值,就把消息发给哪些队列。

发布订阅应用到监控上:
模板就是写上一段脚本,放在服务器上,

客户端每5分钟,从服务端拿到监控模板,根据模板来取数据,

然后把数据结果发步到服务端的redis频道里。

服务端收到数据,1)处理历史记录 2)报警 3)dashboard显示监控信息

服务端有三处一直来订阅服务端频道(一直来收取客户端监控数据)

2.6.1 发布给所有绑定队列 (fanout)

exchange type = fanout
exchange 可以帮你发消息到多个队列,type = fanout表示:跟exchange绑定的所有队列,都会收到消息。
在这里插入图片描述
发布者:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Python Yang'
import pika
import sys
# ########################### 发布者 ###########################

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='logs',
                         type='fanout')

message = ' '.join(sys.argv[1:]) or "info: Hello World!"
channel.basic_publish(exchange='logs',
                      routing_key='',
                      body=message)
print(" [x] Sent %r" % message)
connection.close()

注意这里是发给所有的订阅者, 所以这里routing_key 的值是空的 , 很容易理解, 是绑定给所有的, 所以这里就内有明确的指定。其他的模式就需要指定啦

订阅者:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Python Yang'

import pika
# ########################### 订阅者 ###########################

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='logs',
                         type='fanout')
# 随机创建队列
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
# 绑定
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()


'''
多次执行这个文件,就会随机生成多个队列。并且exchange都绑定这些队列。
然后发布者只需要给exchange发送消息,然后exchange绑定的多个队列都有这个消息了。订阅者就收到这个消息了。
'''

2.6.2 关键字发送

一个队列还可以绑定多个关键字
在这里插入图片描述

对一个随机队列,绑定三个关键字
再次执行,对另一个随机队列,只绑定一个关键字。
消费者:每执行一次可以生成一个队列。通过使用命令行传参的方式,来传入队列的关键字。

#!/usr/bin/env python
import pika
import sys

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

channel.exchange_declare(exchange='direct_logs',
                         type='direct')

result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue

severities = sys.argv[1:]
if not severities:
    sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
    sys.exit(1)

for severity in severities:
    channel.queue_bind(exchange='direct_logs',
                       queue=queue_name,
                       routing_key=severity)

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()

容易测试的版本:

消费者1:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Python Yang'

import pika
import sys

# ########################### 消费者1 ###########################

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='direct_logs',
                         type='direct')

result = channel.queue_declare(exclusive=True)    # 随机生成队列
queue_name = result.method.queue

severities = ["info", "warning", "error"]
if not severities:
    sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
    sys.exit(1)

for severity in severities:
    channel.queue_bind(exchange='direct_logs',
                       queue=queue_name,
                       routing_key=severity)

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()

消费者2:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Python Yang'

import pika
import sys

# ########################### 消费者2 ###########################

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='direct_logs',
                         type='direct')

result = channel.queue_declare(exclusive=True)    # 随机生成队列
queue_name = result.method.queue

severities = ["error"]


for severity in severities:
    channel.queue_bind(exchange='direct_logs',
                       queue=queue_name,
                       routing_key=severity)

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()

生产者:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Python Yang'

import pika
import sys

# ############################## 生产者 ##############################

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='direct_logs',
                         type='direct')

severity = 'info'
message = 'Hello World!'
channel.basic_publish(exchange='direct_logs',
                      routing_key=severity,
                      body=message)
print(" [x] Sent %r:%r" % (severity, message))
connection.close()

'''
同时运行消费者1,消费者2,然后修改生产者的关键字,运行生产者。
当生产者:severity = 'info',则消费者1收到消息,消费者2没收到消息
当生产者:severity = 'error',则消费者1、消费者2 都收到消息
'''

2.6.3 模糊匹配

在这里插入图片描述

exchange type = topic

在topic类型下,可以让队列绑定几个模糊的关键字,之后发送者将数据发送到exchange,exchange将传入”路由值“和 ”关键字“进行匹配,匹配成功,则将数据发送到指定队列。

  • q # 表示可以匹配 0 个 或 多个 字符

  • q * 表示只能匹配 一个 任意字符

    发送者路由值 队列中
    old.boy.python old.* – 不匹配
    old.boy.python old.# – 匹配

消费者:

#!/usr/bin/env python
import pika
import sys
# ############################## 消费者 ##############################
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='topic_logs',
                         type='topic')

result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue

binding_keys = "*.orange.*"


for binding_key in binding_keys:
    channel.queue_bind(exchange='topic_logs',
                       queue=queue_name,
                       routing_key=binding_key)

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()

生产者:

#!/usr/bin/env python
import pika
import sys
# ############################## 生产者 ##############################
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='topic_logs',
                         type='topic')

# routing_key = 'abc.new.qiaomei.old'
routing_key = 'neworangeold'
message = 'Hello World!'
channel.basic_publish(exchange='topic_logs',
                      routing_key=routing_key,
                      body=message)
print(" [x] Sent %r:%r" % (routing_key, message))
connection.close()

'''
#.orange.#  匹配:new.orange.old  neworangeold
*.orange.*  匹配:neworangeold,不匹配:new.orange.old
'''

2.7 saltstack原理实现

saltstack:zeromq:放到内存里的,会更快,会基于这个做rcp

openstack:大量使用:rabbitmq

saltstack上有master,有三个队列。,让三个客户端每个人取一个队列的任务

saltstack的原理:

1)发一条命令ifconfig,想让所有nginx主机组的机器,都执行。

2)在master我们可以发命令给exchange,nginx总共有10台服务器,创建10个带有nginx关键字的10个队列,

3)master随机生成队列,md5是一个队列的名字,exchange把命令和md5这个消息推送到nginx关键字的队列里。

4)nginx10台服务器从队列中取出消息,执行命令,并且把主机名和执行的结果返回给这个队列里。

5)master变为消费者,取出队列里的主机名和执行结果,并打印到终端上。

服务器1

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Python Yang'

import pika
import sys

# ########################### 消费者1 ###########################

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='direct_logs',
                         type='direct')

result = channel.queue_declare(exclusive=True)  # 随机生成队列
queue_name = result.method.queue

severities = ["nginx", "gfs", "redis"]
if not severities:
    sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
    sys.exit(1)

for severity in severities:
    channel.queue_bind(exchange='direct_logs',
                       queue=queue_name,
                       routing_key=severity)

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

def callback(ch, method, properties, body):
    print(" [x] %r:%r" % (method.routing_key, body))
    queue_md5=body.decode().split(",")[1]
    hostname = 'nginx1'
    channel.queue_declare(queue=queue_md5)  # 如果队列没有创建,就创建这个队列
    channel.basic_publish(exchange='',
                          routing_key=queue_md5,   # 指定队列的关键字为,这里是队列的名字
                          body='%s|cmd_result1' %hostname)  # 往队列里发的消息内容

channel.basic_consume(callback,
                      queue=queue_name,
                      no_ack=True)

channel.start_consuming()

服务器2:

import pika
import sys

# ########################### 消费者2 ###########################

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='direct_logs',
                         type='direct')

result = channel.queue_declare(exclusive=True)  # 随机生成队列
queue_name = result.method.queue

severities = ["nginx"]


for severity in severities:
    channel.queue_bind(exchange='direct_logs',
                       queue=queue_name,
                       routing_key=severity)

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

def callback(ch, method, properties, body):
    print(" [x] %r:%r" % (method.routing_key, body))
    queue_md5=body.decode().split(",")[1]
    hostname = 'nginx2'
    channel.queue_declare(queue=queue_md5)  # 如果队列没有创建,就创建这个队列
    channel.basic_publish(exchange='',
                          routing_key=queue_md5,   # 指定队列的关键字为,这里是队列的名字
                          body='%s|cmd_result2' %hostname)  # 往队列里发的消息内容

channel.basic_consume(callback,
                      queue=queue_name,
                      no_ack=True)

channel.start_consuming()

master


#!/usr/bin/env python
# -*- coding: utf-8 -*-


import pika
import sys
import hashlib

# ############################## 生产者 ##############################

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.137.208'))
channel = connection.channel()

channel.exchange_declare(exchange='direct_logs',
                         type='direct')

severity = 'nginx'
m2 = hashlib.md5()
m2.update(severity.encode('utf-8'))
md5_security=m2.hexdigest()
print('md5_security:',md5_security)
message = 'cmd,%s' % md5_security

channel.basic_publish(exchange='direct_logs',
                      routing_key=severity,
                      body=message)
print(" [x] Sent %r:%r" % (severity, message))
connection.close()

#################################3
connection = pika.BlockingConnection(pika.ConnectionParameters(
                host='192.168.137.208'))
channel = connection.channel()

channel.queue_declare(queue=md5_security)  # 如果队列没有创建,就创建这个队列

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

channel.basic_consume(callback,
                      queue=md5_security,  # 队列名
                      no_ack=True)  # 不通知已经收到,如果连接中断消息就丢失

print(' [*] Waiting for message. To exit press CTRL+C')
channel.start_consuming()
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值