python-rabbitmq

rabbitmq

rabbitmq安装

install rabbitmq-server # 直接搞定

结构图

在这里插入图片描述

		Broker:简单来说就是消息队列服务器实体。 
		Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。 
		Queue:消息队列载体,每个消息都会被投入到一个或多个队列。 
		Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。 
		Routing Key:路由关键字,exchange根据这个关键字进行消息投递。 
		vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。 
		producer:消息生产者,就是投递消息的程序。 
		consumer:消息消费者,就是接受消息的程序。 
		channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务 

通过上面这张应用相结合的结构图既能够清晰的看清楚整体的send Message到Receive Message的一个大致的流程。当然上面有很多名词都相比还没有介绍到,不要着急接下来我们就开始对其进行详细的讲解。

Queue

Queue(队列)RabbitMQ的作用是存储消息,队列的特性是先进先出。上图可以清晰地看到Client A和Client B是生产者,生产者生产消息最终被送到RabbitMQ的内部对象Queue中去,而消费者则是从Queue队列中取出数据。可以简化成表示为:
在这里插入图片描述
send.py

import pika

# 建立一个实例
connection = pika.BlockingConnection(
    pika.ConnectionParameters('localhost', 5672)  # 默认端口5672,可不写
)
# 声明一个管道,在管道里发消息
channel = connection.channel()

# 在管道里声明queue
channel.queue_declare(queue='hello', durable=True)  # durable 队列持久化

channel.basic_publish(exchange='',
                      routing_key='hello',  # queue名字
                      body='Hello World ! ',  # 消息内容
                      properties=pika.BasicProperties(
                          delivery_mode=2,
                      )  # 消息持久化
                      )  

print(" [x] Sent 'Hello World!'")
connection.close()  # 队列关闭

recevie.py

import pika
import time

# 建立实例
connection = pika.BlockingConnection(pika.ConnectionParameters(
    'localhost'))
# 声明管道
channel = connection.channel()

# 为什么又声明了一个‘hello'队列?
# 如果确定已经声明了,可以不声明。但是你不知道那个机器先运行,所以要声明两次。
channel.queue_declare(queue='hello',durable=True)


def callbacks(ch, method, properties, body):  # 四个参数为标准格式
    print(ch, method, properties)  # 打印看一下是什么
    # 管道内存对象 内容相关信息 后面讲
    print(" [x] Received %r" % body)
    time.sleep(15)
    ch.basic_ack(delivery_tag=method.delivery_tag)  # 告诉生成者,消息处理完成


channel.basic_qos(prefetch_count=1)  # 类似权重,按能力分发,如果有一个消息,就不在给你发
# channel.basic_consume(  # 消费消息
#    'hello',  # 你要从那个队列里收消息
#    on_message_callback=callbacks,  # 如果收到消息,就调用callback函数来处理消息
#
    # no_ack=True # 写的话,如果接收消息,机器宕机消息就丢了
    # 一般不写。宕机则生产者检测到发给其他消费者
# )

 channel.basic_consume( # 消费消息
    callback, # 如果收到消息,就调用callback函数来处理消息
    queue='hello', # 你要从那个队列里收消息
    # no_ack=True # 写的话,如果接收消息,机器宕机消息就丢了
    # 一般不写。宕机则生产者检测到发给其他消费者
    )

channel.start_consuming()  # 开始消费消息

生产者Send Message “A”被传送到Queue中,消费者发现消息队列Queue中有订阅的消息,就会将这条消息A读取出来进行一些列的业务操作。这里只是一个消费正对应一个队列Queue,也可以多个消费者订阅同一个队列Queue,当然这里就会将Queue里面的消息平分给其他的消费者,但是会存在一个一个问题就是如果每个消息的处理时间不同,就会导致某些消费者一直在忙碌中,而有的消费者处理完了消息后一直处于空闲状态,因为前面已经提及到了Queue会平分这些消息给相应的消费者。这里我们就可以使用prefetchCount来限制每次发送给消费者消息的个数。详情见下图所示:
在这里插入图片描述

这里的prefetchCount=1是指每次从Queue中发送一条消息来。等消费者处理完这条消息后Queue会再发送一条消息给消费者。

Exchange

我们在开篇的时候就留了一个坑,就是那个应用结构图里面,消费者Client A和消费者Client B是如何知道我发送的消息是给Queue1还是给Queue2,有没有过这个问题,那么我们就来解开这个面纱,看看到底是个什么构造。首先明确一点就是生产者产生的消息并不是直接发送给消息队列Queue的,而是要经过Exchange(交换器),由Exchange再将消息路由到一个或多个Queue,当然这里还会对不符合路由规则的消息进行丢弃掉,这里指的是后续要谈到的Exchange Type。那么Exchange是怎样将消息准确的推送到对应的Queue的呢?那么这里的功劳最大的当属Binding,RabbitMQ是通过Binding将Exchange和Queue链接在一起,这样Exchange就知道如何将消息准确的推送到Queue中去。简单示意图如下所示:

在这里插入图片描述

在绑定(Binding)Exchange和Queue的同时,一般会指定一个Binding Key,生产者将消息发送给Exchange的时候,一般会产生一个Routing Key,当Routing Key和Binding Key对应上的时候,消息就会发送到对应的Queue中去。那么Exchange有四种类型,不同的类型有着不同的策略。也就是表明不同的类型将决定绑定的Queue不同,换言之就是说生产者发送了一个消息,Routing Key的规则是A,那么生产者会将Routing Key=A的消息推送到Exchange中,这时候Exchange中会有自己的规则,对应的规则去筛选生产者发来的消息,如果能够对应上Exchange的内部规则就将消息推送到对应的Queue中去。那么接下来就来详细讲解下Exchange里面类型。
Exchange Type

我来用表格来描述下类型以及类型之间的区别。
  • fanout 纯广播、all
    fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。

在这里插入图片描述
上图所示,生产者(P)生产消息1将消息1推送到Exchange,由于Exchange Type=fanout这时候会遵循fanout的规则将消息推送到所有与它绑定Queue,也就是图上的两个Queue最后两个消费者消费。

fanout_sent.py

import pika

# 创建实例
connection = pika.BlockingConnection(pika.ConnectionParameters('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()

fanout_recevie.py

import pika

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

# 声明广播路由
channel.exchange_declare(exchange='logs',
                         exchange_type='fanout')

# 创建广播管道
# 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除
result = channel.queue_declare('', exclusive=True) # 此处空值项为queue
# 获取随机的queue名字
queue_name = result.method.queue
print("queuename:", queue_name)

# queue绑定到转发器上
channel.queue_bind(exchange='logs',
                   queue=queue_name)


# 创建callback
def callback(ch, methon, properties, body):
    print(ch)
    print(methon)
    print(properties)
    print('接受消息,{0}'.format(body))


#channel.basic_consume(
#    queue_name,
#    callback,
#    True
#)

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





channel.start_consuming()

  • direct 有选择的接收消息
    direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中

在这里插入图片描述
当生产者(P)发送消息时Rotuing key=booking时,这时候将消息传送给Exchange,Exchange获取到生产者发送过来消息后,会根据自身的规则进行与匹配相应的Queue,这时发现Queue1和Queue2都符合,就会将消息传送给这两个队列,如果我们以Rotuing key=create和Rotuing key=confirm发送消息时,这时消息只会被推送到Queue2队列中,其他Routing Key的消息将会被丢弃。

fanout_sent.py

import pika

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

# 创建路由
channel.exchange_declare(exchange='direct_logs',
                         exchange_type='direct')

severity = '3'    # 级别
channel.basic_publish(exchange='direct_logs',
                      routing_key=severity,
                      body='hello')

print('he')
connection.close()

fanout_recevie.py

import pika

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
# 获取运行脚本所有的参数
severity = '3'

# 绑定
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()
  • topic 模糊匹配
    前面提到的direct规则是严格意义上的匹配,换言之Routing Key必须与Binding Key相匹配的时候才将消息传送给Queue,那么topic这个规则就是模糊匹配,可以通过通配符满足一部分规则就可以传送。它的约定是:

routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
binding key与routing key一样也是句点号“. ”分隔的字符串
binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
在这里插入图片描述

当生产者发送消息Routing Key=F.C.E的时候,这时候只满足Queue1,所以会被路由到Queue中,如果Routing Key=A.C.E这时候会被同是路由到Queue1和Queue2中,如果Routing Key=A.F.B时,这里只会发送一条消息到Queue2中。

sent.py

import pika
import sys

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

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

routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'

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

recevie.py

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

binding_keys = sys.argv[1:]
if not binding_keys:
    sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
    sys.exit(1)

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

Exchange规则
|

类型名称类型描述
fanout把所有发送到该Exchange的消息路由到所有与它绑定的Queue中
directRouting Key==Binding Key
topic模糊匹配
headersExchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。

  • RabbitMQ RPC 实现(Remote procedure call)
    不知道你有没有发现,上面的流都是单向的,如果远程的机器执行完返回结果,就实现不了了。
    如果返回,这种模式叫什么呢,RPC(远程过程调用),snmp就是典型的RPC
    RabbitMQ能不能返回呢,怎么返回呢?既是发送端又是接收端。
    但是接收端返回消息怎么返回?可以发送到发过来的queue里么?不可以。
    返回时,再建立一个queue,把结果发送新的queue里
    为了服务端返回的queue不写死,在客户端给服务端发指令的的时候,同时带一条消息说,你结果返回给哪个queue

client.py

import uuid
import pika


class FibonacciRpcClient(object):
    def __init__(self):
        self.connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))
        self.channel = self.connection.channel()
        result = self.channel.queue_declare(exclusive=True)
        self.callback_queue = result.method.queue

        self.channel.basic_consume(self.on_response,  # 只要一收到消息就调用on_response
                                   no_ack=True,
                                   queue=self.callback_queue)  # 收这个queue的消息

    def on_response(self, ch, method, props, body):  # 必须四个参数
        # 如果收到的ID和本机生成的相同,则返回的结果就是我想要的指令返回的结果
        if self.corr_id == props.correlation_id:
            self.response = body

    def call(self, n):
        self.response = None  # 初始self.response为None
        self.corr_id = str(uuid.uuid4())  # 随机唯一字符串
        self.channel.basic_publish(
            exchange='',
            routing_key='rpc_queue',  # 发消息到rpc_queue
            properties=pika.BasicProperties(  # 消息持久化
                reply_to=self.callback_queue,  # 让服务端命令结果返回到callback_queue
                correlation_id=self.corr_id,  # 把随机uuid同时发给服务器
            ),
            body=str(n)
        )
        while self.response is None:  # 当没有数据,就一直循环
            # 启动后,on_response函数接到消息,self.response 值就不为空了
            self.connection.process_data_events()  # 非阻塞版的start_consuming()
            # print("no msg……")
            # time.sleep(0.5)
        # 收到消息就调用on_response
        return int(self.response)


if __name__ == '__main__':
    fibonacci_rpc = FibonacciRpcClient()
    print(" [x] Requesting fib(7)")
    response = fibonacci_rpc.call(7)
    print(" [.] Got %r" % response)

server.py

import pika


def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)


def on_request(ch, method, props, body):
    n = int(body)
    print(" [.] fib(%s)" % n)
    response = fib(n)

    ch.basic_publish(
        exchange='',  # 把执行结果发回给客户端
        routing_key=props.reply_to,  # 客户端要求返回想用的queue
        # 返回客户端发过来的correction_id 为了让客户端验证消息一致性
        properties=pika.BasicProperties(correlation_id=props.correlation_id),
        body=str(response)
    )
    ch.basic_ack(delivery_tag=method.delivery_tag)  # 任务完成,告诉客户端


if __name__ == '__main__':
    connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
    channel = connection.channel()
    channel.queue_declare(queue='rpc_queue')  # 声明一个rpc_queue ,

    channel.basic_qos(prefetch_count=1)
    # 在rpc_queue里收消息,收到消息就调用on_request
    channel.basic_consume(on_request, queue='rpc_queue')
    print(" [x] Awaiting RPC requests")
    channel.start_consuming()

补充说明:
  ConnectionFactory、Connection、Channel
  ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑。ConnectionFactory为Connection的制造工厂。
  Channel是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。

Connection就是建立一个TCP连接,生产者和消费者的都是通过TCP的连接到RabbitMQ Server中的,这个后续会再程序中体现出来。

Channel虚拟连接,建立在上面TCP连接的基础上,数据流动都是通过Channel来进行的。为什么不是直接建立在TCP的基础上进行数据流动呢?如果建立在TCP的基础上进行数据流动,建立和关闭TCP连接有代价。频繁的建立关闭TCP连接对于系统的性能有很大的影响,而且TCP的连接数也有限制,这也限制了系统处理高并发的能力。但是,在TCP连接中建立Channel是没有上述代价的。

callback方法中的四个参数
	channel: pika.Channel	 
    method: pika.spec.Basic.Deliver
    properties: pika.spec.BasicProperties				# 主要是pick.BasicProperties实现实例的的一些属性
    body: str, unicode, or bytes (python 3.x)

转载:https://www.jb51.net/article/150386.htm
https://www.cnblogs.com/dwlsxj/p/RabbitMQ.html

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值