Python实战之RabiitMQ消息队列(win7 and linux平台安装,PUBLISH\SUBSCRIBE(消息发布\订阅)三种模式:fanout、direct、topic,RPC实现)

之前学的PY threading Queue队列:用于多个线程数据共享同步交互的

如果Python的进程想要和JAVA进程或者PHP进程交互怎么办?

在不同的机器上实现生产与消费,进程都是独立,这个时候如何进行数据交互?

我们需要一个中间代理,就是RabbitMQ 消息队列

1.soctet可以解决两个进程通信  

2.disk进行序列化也可以(磁盘太慢了)

3.broker(中间商)

        broker(中间商)成熟产品软件:RabbitMQ、ZeroMQ、ActiveMQ...等等

        RabbitMQ最火,所以学 RabbitMQ

RabbitMQ 基于erlang语言开发的,所上必须向装erlang该语言

http://www.erlang.org/downloads(可以下线erlang)

 

window7安装

erlang安装目录

rabbit必须指定目录,默认目录因为空格问题导致问题

加入环境变量:

C:\Program Files\erl7.3\bin;D:\ribbt\rabbitmq_server-3.6.12\sbin

window安装pika模块

 

Linux安装

安装依赖文件:

yum install gcc glibc-devel make ncurses-devel openssl-devel xmlto -y

安装erlang

cd /home/burgess/tools/

[root@python tools]# wget http://erlang.org/download/otp_src_18.3.tar.gz

[root@python tools]#  tar -xzvf otp_src_18.3.tar.gz

[root@python tools]# cd otp_src_18.3/

[root@python otp_src_18.3]# ./configure --prefix=/opt/erlang

[root@python otp_src_18.3]#  make && make install

[root@python otp_src_18.3]#  cd /opt/erlang/

[root@python bin]# /opt/erlang/bin/erl

1>

[root@python bin]# echo "export PATH=$PATH:/opt/erlang/bin" >> /etc/profile

[root@python bin]# export PATH=$PATH:/opt/erlang/bin

[root@python bin]# source  /etc/profile

安装RabbitMQ

[root@python tools]# wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.1/rabbitmq-server-generic-unix-3.6.1.tar.xz

[root@python tools]# xz -d rabbitmq-server-generic-unix-3.6.1.tar.xz

[root@python tools]#  tar -xvf rabbitmq-server-generic-unix-3.6.1.tar -C /opt

[root@python opt]# ln -s rabbitmq_server-3.6.1/ /opt/rabbitmq-3.6.1

[root@python opt]# export PATH=$PATH:/opt/rabbitmq-3.6.1/sbin

[root@python opt]# echo 'export PATH=$PATH:/opt/rabbitmq-3.6.1/sbin' >>/etc/profile

[root@python opt]# source /etc/profile

[root@python opt]# cd rabbitmq-3.6.1/

[root@python rabbitmq-3.6.1]# cd sbin/

[root@python sbin]# ./rabbitmq-server -detached #启动

添加外网访问账户以及授权

[root@python ~]# rabbitmqctl add_user admin admin #创建用户

[root@python ~]# rabbitmqctl list_users       #查看所有用户           

Listing users ...

admin   []

guest   [administrator]

[root@python ~]# rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"  #授权

Setting permissions for user "admin" in vhost "/" ...

[root@python ~]# rabbitmqctl list_permissions -p /  #查看用户权限

Listing permissions in vhost "/" ...

guest   .*      .*      .*

admin   .*      .*      .*

[root@python ~]# rabbitmqctl set_user_tags admin administrator

Setting tags for user "admin" to [administrator] ...

[root@python ~]# rabbitmqctl list_users

Listing users ...

admin   [administrator]

guest   [administrator]

 

添加网页访问插件

关闭服务:

[root@iZ25e3bt9a6Z sbin]# ./rabbitmqctl stop

Stopping and halting node rabbit@iZ25e3bt9a6Z ...

配置网页插件

    首先创建目录,否则可能报错:

    mkdir /etc/rabbitmq

    然后启用插件:

    ./rabbitmq-plugins enable rabbitmq_management

    配置linux 端口 15672 网页管理  5672 AMQP端口

    然后访问http://localhost:15672即可

    默认用户guest 密码guest

 

rabbitMQz支持各种语言 ,Python取对应的语言区域下载模块

 

我们安装pika  celery是分布式以后再讲

Linux安装pika模块

[root@python sbin]# pip3 install pika 安装python支持的模块

 

实现最简单的队列通信

最简单的生产者消费者试验

producer端

__author__ = "Burgess Zheng"

import pika

#生产者
#connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.0.0.150',
           credentials=pika.PlainCredentials('admin','admin')))#远程连接到rabbitmq服务器就需要账户和密码

connection = pika.BlockingConnection(
    pika.ConnectionParameters('localhost')
)#连接到本地的rabbitmq

#初始化  相当于建立一个socket

channel = connection.channel()#声明(生成)一个管道

channel.queue_declare(queue='hello',durable=True)#声明(生成)hello队列
                             #durable=True使该hello队列持久化

# n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange='',
                      routing_key='hello',#接收消息的队列(hello)
                      body='Hello World!', #发送出去的消息 ,发到routing_key
                      properties=pika.BasicProperties(delivery_mode=2)#队列消息持久化
                      )
print(" [x] Sent 'Hello World!'")#打印
connection.close()#关闭该socket

consumer端

__author__ = "Burgess Zheng"


import pika
import time

#connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.0.0.150',
           credentials=pika.PlainCredentials('admin','admin')))#远程连接到rabbitmq服务器就需要账户和密码


connection = pika.BlockingConnection(
    pika.ConnectionParameters('localhost')
    ) #连接到本地的rabbitmq
#初始化  相当于建立一个socket

channel = connection.channel()#声明(生成)一个管道

# You may ask why we declare the queue again ‒ we have already declared it in our previous code.
# We could avoid that if we were sure that the queue already exists. For example if send.py program
# was run before. But we're not yet sure which program to run first. In such cases it's a good
# practice to repeat declaring the queue in both programs.
channel.queue_declare(queue='hello',durable=True)# 声明(生成)queue队列
# 为什么这里还需要申明一个队列,因为不知道是消费者先启动还是生产者先启动

def callback(ch, method, properties, body):#回调函数:标准格式带4个参数
    #print("--->",ch, method, properties)
    #ch 管道的内存对象地址  method:消息的一些信息
    # properties:等下用到在讲  body:收到的数据
    #time.sleep(30)
    print(" [x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)
#该ch.ba...命令是一个确认机制,如果信息接受完毕通知rabbitmq的hello队列,可以移除该信息

channel.basic_qos(prefetch_count=1)#如果接收的没处理完,下次不用发我
channel.basic_consume(
                      callback,#如果收到消息就调用callback函数来处理消息
                      queue='hello')#从哪个队列收消息
                      #no_ack=True)
#如果该no_ack=True参数存在,那么接受数据意外中断,或者取消,
#那么rabbitmq的hello队列里面的该信息会被移除(不管有没有接收完毕)

print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
#启动从管道开始收数据(一旦启动不会停止下来)除非手动停止
#注意接收都是已bytes类型接收的
#注意:如果有多个consumer,假如3个,目前是轮询状态.
# producer端发送信息,consumer1收到,其他两个收不到。
# producer端再次发送信息,consumer2收到,其他两个不收到。
# producer端再次发送信息,consumer3收到,其他两个不收到。
# producer端再次发送信息,consumer1收到,其他两个不收到,这样轮询下去

执行结果:

注意:如果有多个consumer,假如3个,目前是轮询状态.

 producer端发送信息,consumer1收到,其他两个收不到。

 producer端再次发送信息,consumer2收到,其他两个不收到。

 producer端再次发送信息,consumer3收到,其他两个不收到。

 producer端再次发送信息,consumer1收到,其他两个不收到,这样轮询下去

由于 consumer端no_ack=True这种状态导致:如果producer发送的消息,consumer收的时候突然中断或者死机,那么producer端发送的信息存在rabbitMB队列的该消息就等于丢失了,如果银行转账,客户的账户收取突然断了,钱没收到,,那就麻烦了

一般情况就不加该参数no_ack=True,那么consumer端一旦接收过程中断,producer端发送的信息存在rabbitMB队列里该信息就不会倍移除. consumer端连接断开,那么如果有其他的consumer端存在,就会给另外的consumer端接收,直到consumer端接收完毕进行确认才会把该消息从队列移除

除了不加该参数no_ack=True以外,还需要在接收完毕后加确认:

ch.basic_ack(delivery_tag=method.delivery_tag)

#该ch.ba...命令是一个确认机制,如果信息接受完毕通知rabbitmq的hello队列,可以移除该信息

 

上面实验还是有问题因为一旦挂机了就出现数据问题

如何解决这个问题实现在rabbitmqctl 所有队列永久保存(数据持久化)

在producer端和consumer端生成queue队列的时加上 durable=True (但是只持久化队列,消息还是保不住) 格式:channel.queue_declare(queue='hello',durable=True)#队列持久化

还需要producer端的基本定义(basic_consume)框架里面增加:properties=pika.BasicProperties(delivery_mode=2)#队列消息持久化

rabbitMB修改持久化试验

producer端

__author__ = "Burgess Zheng"
import pika

#生产者

#初始化 相当于建立一个socket
connection = pika.BlockingConnection(
    pika.ConnectionParameters('0.0.0.0')
    )
#声明(生成)一个管道
channel = connection.channel()

#声明(生成)一个hello队列容器
channel.queue_declare(queue='hello',durable=True)#durable=True使该hello队列持久化

#申明发送消息
channel.basic_publish(exchange='',
                   routing_key='hello',#接收消息的队列(hello)
                   body='Hello World!', #发送出去的消息 ,发到routing_key
                  properties=pika.BasicProperties(delivery_mode=2) #使队列消息持久化
                      ) 
print(" [x] Sent 'Hello World!'") #打印显示
connection.close()#关闭socket

consumer端(克隆窗口)

__author__ = "Burgess Zheng"

import pika

#消费者

#初始化 相当于建立一个socket
connection = pika.BlockingConnection(
    pika.ConnectionParameters('localhost')
    )

#声明(生成)一个管道
channel = connection.channel()

#声明(生成)hello队列
# 为什么这里还需要申明一个队列,因为不知道是消费者先启动还是生产者先启动
channel.queue_declare(queue='hello',durable=True)#使该hello队列持久化

def callback(ch,method,properties,body):#标准格式带4个参数
    #print("-->",ch,method,properties)
    #ch 管道的内存对象地址  method:消息的一些信息
    # properties:等下用到在讲  body:收到的数据
    print("[x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)
#该ch.ba...命令是一个确认机制,如果信息接受完毕通知rabbitmq的hello队列,可以移除该信息

channel.basic_qos(prefetch_count=1)#如果接收的没处理完,不在接收新的数据

#basic_consume申明接收消息
channel.basic_consume(
                      callback,#调用callback函数来处理消息
                      queue='hello')#从该hello队列收消息就启动callback                      #no_ack=True)
#如果该no_ack=True参数存在,那么接受数据意外中断,或者取消,
#那么rabbitmq的hello队列里面的该信息会被移除(不管有没有接收完毕)


print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()#start_consuming进入阻塞模式,有消息就取,没消息就等
#启动从管道开始收数据(一旦启动不会停止下来)除非手动停止
#注意接收都是已bytes类型接收的

执行结果

现在又有一个问题了

如果consumer端机器配置不同处理的效率不同怎么办

我不能发送给一个低配置,处理半天后面的人等着?

只需要要在consumer端加上一条语句:

channel.basic_qos(prefetch_count=1)#如果接收的没处理完,不在接收新的数据(模拟可以增加一个consumer端,然后sleep30秒就可以测试效果了,然后producer端一直发,)

之前是轮询状态的,就是producer端发送一条消息,该条消息只有一个consumer端获得。

如果我想producer端发送一条消息,所有consumer端获得呢?

Publish\Subscribe(消息发布\订阅) 

之前的例子都基本都是11的消息发送和接收,即消息只能发送到指定的queue里,但有些时候你想让你的消息被所有的Queue收到,类似广播的效果,这时候就要用到exchange了,

An exchange is a very simple thing. On one side it receives messages from producers and the other side it pushes them to queues. The exchange must know exactly what to do with a message it receives. Should it be appended to a particular queue? Should it be appended to many queues? Or should it get discarded. The rules for that are defined by the exchange type.

exchange 是一个非常简单的事情。一方面它收到消息从生产者和另一边推他们队列。exchange 必须知道如何处理接收到的消息。应该是附加到一个特定的队列吗?应该是附加到多队列?或者应该丢弃。取决于exchange的规则定义的类型。

Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息


fanout: 所有bind到此exchangequeue都可以接收消息(广播)
direct: 通过routingKeyexchange决定的哪个的queue可以接收消息(也就是要求exchange该组的queue需要验证)(类似组播)
topic:所有符合routingKey(此时可以是一个表达式)routingKeybindqueue可以接收消息(广播,组播都可以)

   表达式符号说明:#代表一个或多个字符,*代表任何字符
      例:#.a会匹配a.aaa.aaaa.a
          *.a会匹配a.ab.ac.a
     注:使用RoutingKey#Exchange Typetopic的时候相当于使用fanout 

headers: 通过headers 来决定把消息发给哪些queue

 

试验fanout类型 所有bind到此exchange的queue都可以接收消息

producer端

__author__ = "Burgess Zheng"

import pika
import sys

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost')
    )
#初始化  相当于建立一个socket

channel = connection.channel()#声明(生成)一个管道

channel.exchange_declare(exchange='logs',exchange_type='fanout')
#生成一个exchange组,组名:logs
#exchange组类型fanout  #python3.5.4下以下版本 type='fanout'
#fanout: 所有bind到此exchange的queue都可以接收消息

message = ''.join(sys.argv[1:]) or "info: Hello World!"
#sys.argv[1:]:取sys.argv下标1之后的元素==取执行该脚本输入的第1个传参后N个
#如果执行脚本没有输入第一个传参就发Hello World

# basic_publish:声明发消息
channel.basic_publish(exchange='logs',#使用exchange组,组名:logs
                      routing_key='',#queue名 由于没有生产queue队列就先空着
                      body=message)#发送出去的消息 ,发到routing_key
print(" [x] Sent %r" % message)
connection.close()#关闭该socket

#由于producer端没有生成queuq队列,
#消息是实时发布的,发送出去,在线的consumer端收得到,
#如果发出去。没有在线的consumer端,消息会直接进入黑洞
#这个时候启动consumer端是收不到消息的
#就是收音机的模型,开着收音机就可以收到,关闭了就收不到

consumer端

_author__ = "Burgess Zheng"

import pika

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost')
    )
#初始化  相当于建立一个socket

channel = connection.channel()#声明(生成)一个管道

channel.exchange_declare(exchange='logs',exchange_type='fanout')
#生成一个exchange组,组名:logs
#exchange组类型fanout
#fanout: 所有bind到此exchange的queue都可以接收消息

result = channel.queue_declare(exclusive=True)
#由于不生产queue队列名字,rabbit会随机分配一个名字,
# exclusive=True会在使用此queue队列的消费者断开后,自动将queue删除
#producer端也没有生产queue队列,为什么还需要生成queue队列呢?

#把exchange=logs当成是一个组,#随机queue队列绑定加入到该组
#该组有消息的时候是存放在随机queue队列里的
#当然也可以自己生成队列..随机的队列好处就是接收完数据就清掉



queue_name = result.method.queue
#result.method.queue:获取rabbit随机生成的queue队列的名子


#是producer端exchange所以需要channel.queue_bind绑定到exchange队列
channel.queue_bind(exchange='logs',#绑定到exchange组,组名:logs
                   queue=queue_name)
               #queue=随机生成queue队列加入该logs组

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


def callback(ch, method, properties, body): #回调函数
    print(" [x] %r" % body)#打印接收的消息

# basic_consume声明收消息
channel.basic_consume(callback,#调用callback回调函数
                      queue=queue_name,#该随机队列有消息就启用callback
                      no_ack=True)
#只要接收消息,不管是否接收完毕不回应服务端,服务端会清除该消息

channel.start_consuming()#start_consuming进入阻塞模式,有消息就取,没消息就等
#启动从管道开始收数据(一旦启动不会停止下来)除非手动停止
#注意接收都是已bytes类型接收的

执行结果:

有选择的接收消息(exchange type=direct)

RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchangeexchange根据 关键字 判定应该将数据发送至指定队列。

direct: 通过routingKeyexchange决定的那个唯一的queue可以接收消息

 

试验direct类型 通过routingKey和exchange决定的哪个queue可以接收消息(也就是要求exchange该组的queue需要验证)

producer端

__author__ = "Burgess Zheng"
import pika
import sys

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost')
    )
#初始化  相当于建立一个socket

channel = connection.channel()#声明(生成)一个管道

channel.exchange_declare(exchange='direct_logs',exchange_type='direct')
#生成一个exchange组,组名:direct_logs
#exchange组类型direct  #python3.5.4下以下版本 type='direct'
#direct : 通过routingKey和exchange决定的那个唯一的queue可以接收消息

severity = sys.argv[1] if len(sys.argv) > 1 else 'info'
#sys.argv[1]解释如下 在Linux上
#脚本     [root@python Part_eleven]# vim abc.py
#        import sys
#        severity = sys.argv[1] if len(sys.argv) > 1 else 'info'
#        print(sys.argv)
#        print(severity)

# 执行  [root@python Part_eleven]# py abc.py   不带传参1
#       ['abc.py']    #sys.argv 打印执行输入的文件名的相对路径,以列表格式出现
#       info         #sys.argv[1]:因为没有传参所以默认打印info

# 执行  [root@python Part_eleven]# py abc.py 1  带传参1
#       ['abc.py', '1'] #sys.argv 打印执行输入文件名和传参的相对路径,以列表格式出现
#        1              #sys.argv[1]:取['abc.py', '1'] 下标1的元素
                        #判断len(sys.argv)= len(['abc.py', '1']) 长度是2


message = ' '.join(sys.argv[2:]) or 'Hello World!'
#sys.argv[2:]:取sys.argv下标2之后的元素==取执行该脚本输入的第2个传参后N个
#如果执行脚本没有输入传参2就发Hello World

# basic_publish声明发消息
channel.basic_publish(exchange='direct_logs',#使用exchange组,组名:direct_logs
                      routing_key=severity,
#severity不是queue,简单理解:关键字验证,cosummer端必须有该关键字才可以获得消息
#默认我们不输入参数 关键字是:info
                      body=message)#发送的消息 发到routing_key
print(" [x] Sent %s:%s" % (severity, message))
connection.close()#关闭该socket

#由于producer端没有生成queuq队列,
#消息是实时发布的,发送出去,在线的consumer端收得到,
#如果发出去。没有在线的consumer端,消息会直接进入黑洞
#这个时候启动consumer端是收不到消息的
#就是收音机的模型,开着收音机就可以收到,关闭了就收不到

consumer端

__author__ = "Burgess Zheng"

import pika
import sys

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost')
    )
#初始化  相当于建立一个socket

channel = connection.channel()#声明(生成)一个管道

channel.exchange_declare(exchange='direct_logs',exchange_type='direct')
#生成一个exchange组,组名:direct_logs
#exchange组类型direct  #python3.5.4下以下版本 type='fanout'
#direct : 通过routingKey和exchange决定的那个唯一的queue可以接收消息

result = channel.queue_declare(exclusive=True)
#由于不生产queue队列名字,rabbit会随机分配一个名字,
# exclusive=True会在使用此queue队列的消费者断开后,自动将queue删除
#producer端也没有生产queue队列,为什么还需要生成queue队列呢?

#把exchange=logs当成是一个组,#随机queue队列绑定加入到该组
#该组有消息的时候是存放在随机queue队列里的
#当然也可以自己生成队列..随机的队列好处就是接收完数据就清掉

queue_name = result.method.queue
#result.method.queue:获取rabbit随机生成的queue队列的名子

severities = sys.argv[1:]#取sys.argv下标1之后的元素 == 执行脚本N个传参
#假如:producer端默认没输入关键字severity是info关键字,那么我们应该输入info
if not severities:#如果没有输入传参 报错退出
    sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
    sys.exit(1)

for severity in severities:#你输入的参数进行导入
    # 是producer端exchange所以需要channel.queue_bind绑定到exchange队列
    channel.queue_bind(exchange='direct_logs',#绑定到exchange组,组名:direct_logs
                       queue=queue_name,#queue=随机生成queue队列加入该direct_logs组
                       routing_key=severity)#关键字severity验证

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


def callback(ch, method, properties, body):#回调函数
    print(" [x] %s:%s" % (method.routing_key, body))

# basic_consume声明收消息
channel.basic_consume(callback,#调用callback回调函数
                      queue=queue_name,#该随机队列有消息就启用callback
                      no_ack=True)#只要接收消息,不管是否接收完毕不回应服务端,服务端会清除该消息

channel.start_consuming()#start_consuming进入阻塞模式,有消息就取,没消息就等
#启动从管道开始收数据(一旦启动不会停止下来)除非手动停止
#注意接收都是已bytes类型接收的

执行结果

 

更细致的消息过滤

Although using the direct exchange improved our system, it still has limitations - it can't do routing based on multiple criteria.

虽然使用的直接交换改进我们的系统,但它仍然有一定的局限性,它不能做基于多个标准路由。

In our logging system we might want to subscribe to not only logs based on severity, but also based on the source which emitted the log. You might know this concept from the syslog unix tool, which routes logs based on both severity (info/warn/crit...) and facility (auth/cron/kern...).

日志系统中我们不仅要订阅根据严重程度的日志,也基于源发出的日志。你可能知道这个概念的syslog unix工具,这路线日志根据严重程度(信息/警告/暴击…)和设备(身份验证/ cron / kern……)

That would give us a lot of flexibility - we may want to listen to just critical errors coming from 'cron' but also all logs from 'kern'.

这将给我们一个很大的灵活性——我们可能想听关键错误来自cron,但也从“'kern”的所有日志。

topic:所有符合routingKey(此时可以是一个表达式)routingKeybindqueue可以接收消息   

表达式符号说明:#代表一个或多个字符,*代表任何字符
      例:#.a会匹配a.aaa.aaaa.a
          *.a会匹配a.ab.ac.a
     注:使用RoutingKey#Exchange Typetopic的时候相当于使用fanout

 

试验topic类型:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息(脚本内容和direct一模一样只是类型该成topic)topic的好处就是可以模糊匹配 consummer端 使用#号代表接收所有、 consummer端 *.xx 匹配任意.xx

producer端

__author__ = "Burgess Zheng"

import pika
import sys

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost')
    )
#初始化  相当于建立一个socket

channel = connection.channel()#声明(生成)一个管道

channel.exchange_declare(exchange='topic_logs',exchange_type='topic')
#生成一个exchange组,组名:direct_logs
#exchange组类型direct  #python3.5.4下以下版本 type='direct'
#direct : 通过routingKey和exchange决定的那个唯一的queue可以接收消息

routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'
#取执行该文件输入的第1个传参 如果有routing_key = 第一个传参
#如果没有routing_key = 'anonymous.info'


message = ' '.join(sys.argv[2:]) or 'Hello World!'
#取执行该文件输入的第2个传参 如果有message =第2个传参
##如果没有message = 'Hello World!'

# basic_publish声明发消息
channel.basic_publish(exchange='topic_logs',#使用exchange组,组名:topic_logs
                      routing_key=routing_key,
#severity不是queue,简单理解:关键字验证,cosummer端必须有该关键字才可以获得消息
#默认我们不输入参数 关键字是:anonymous.info
                      body=message)#发送的消息 发到routing_key

print(" [x] Sent %r:%r" % (routing_key, message))
connection.close()#关闭该socket

#由于producer端没有生成queuq队列,
#消息是实时发布的,发送出去,在线的consumer端收得到,
#如果发出去。没有在线的consumer端,消息会直接进入黑洞
#这个时候启动consumer端是收不到消息的
#就是收音机的模型,开着收音机就可以收到,关闭了就收不到

consumer端

__author__ = "Burgess Zheng"

import pika
import sys

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost')
    )
#初始化  相当于建立一个socket


channel = connection.channel()#声明(生成)一个管道

channel.exchange_declare(exchange='topic_logs',exchange_type='topic')
#生成一个exchange组,组名:topic_logs
#exchange组类型topic #python3.5.4下以下版本 type='topic'
#topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息

result = channel.queue_declare(exclusive=True)
#由于不生产queue队列名字,rabbit会随机分配一个名字,
# exclusive=True会在使用此queue队列的消费者断开后,自动将queue删除
#producer端也没有生产queue队列,为什么还需要生成queue队列呢?

#把exchange=logs当成是一个组,#随机queue队列绑定加入到该组
#该组有消息的时候是存放在随机queue队列里的
#当然也可以自己生成队列..随机的队列好处就是接收完数据就清掉

queue_name = result.method.queue
#result.method.queue:获取rabbit随机生成的queue队列的名子

binding_keys = sys.argv[1:]#取sys.argv下标1之后的元素 == 执行脚本N个传参
#和direct一模一样,只是改了类型,可以模糊匹配
# 如:producer端routing_key=anonymous.info  我们可以输入*.info进行匹配
if not binding_keys:#如果没有输入传参 报错退出
    sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
    sys.exit(1)

for binding_key in binding_keys:#你输入的参数进行导入
    # 是producer端exchange所以需要channel.queue_bind绑定到exchange队列
    channel.queue_bind(exchange='topic_logs',#绑定到exchange组,组名:topic_logs
                       queue=queue_name,#queue=随机生成queue队列加入该topic_logs组
                       routing_key=binding_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))

# basic_consume声明收消息
channel.basic_consume(callback,#调用callback回调函数
                      queue=queue_name,#该随机队列有消息就启用callback
                      no_ack=True)#只要接收消息,不管是否接收完毕不回应服务端,服务端会清除该消息

channel.start_consuming()#start_consuming进入阻塞模式,有消息就取,没消息就等
#启动从管道开始收数据(一旦启动不会停止下来)除非手动停止
#注意接收都是已bytes类型接收的

执行结果

 

To receive all the logs run:

python receive_logs_topic.py "#"  收所有的

To receive all logs from the facility "kern":

python receive_logs_topic.py "kern.*"  收kern.开头的

Or if you want to hear only about "critical" logs:

python receive_logs_topic.py "*.critical"  收以.critical结尾的

You can create multiple bindings:

python receive_logs_topic.py "kern.*" "*.critical" 同时收多个模糊匹配

And to emit a log with a routing key "kern.critical" type: 

python emit_log_topic.py "kern.critical" "A critical kernel error"

收多个完全匹配的

 

RABBITMQ RPC实现

现在要求实现producer端和consumer端既可以发可也可以收 

Remote procedure call (RPC)

RPC = remote procedure call 远程调用

RPC服务:发给指令过去他替我执行返回个结果过来

snmp:简单网络管理协议:客户端发送个指令就可以调用远程服务器的CPU之类的状态

To illustrate how an RPC service could be used we're going to create a simple client class. It's going to expose a method named call which sends an RPC request and blocks until the answer is received:

来说明一个RPC服务可以使用我们要创建一个简单的客户端类。它会暴露一个方法调用发送一个RPC请求和街区,直到收到答案:

 

通过rabbitMB 实现RPC模式试验 实现客户端发送一条命令 服务端返回数据

有点复杂已经标好了顺序1-24.

客户端

 

[root@python Part_eleven]# vim RPC_client.py
__author__ = "Burgess Zheng"
import pika
import uuid


class FibonacciRpcClient(object):#类
    def __init__(self):
        self.connection = pika.BlockingConnection(
            pika.ConnectionParameters(host='localhost')
        )
        # 初始化  相当于建立一个socket

        self.channel = self.connection.channel()#声明(生成)一个管道

        result = self.channel.queue_declare(exclusive=True)
        # 由于不生产queue队列名字,rabbit会随机分配一个名字,
        # exclusive=True会在使用此queue队列的消费者断开后,自动将queue删除
        # producer端也没有生产queue队列,为什么还需要生成queue队列呢?
        # 把exchange=logs当成是一个组,#随机queue队列绑定加入到该组
        # 该组有消息的时候是存放在随机queue队列里的
        # 当然也可以自己生成队列..随机的队列好处就是接收完数据就清掉

        self.callback_queue = result.method.queue
        # result.method.queue:获取rabbit随机生成的queue队列的名子

        #16.basic_consume声名收消息
        self.channel.basic_consume(self.on_response,# 18.启动回调函数
                                   no_ack=True,
#18.接收消息后,服务端删除该消息(不管是否完整接收
                                   queue=self.callback_queue)
       #17.收到了该self.callback_queue队列里面的消息832040,启动回调函数


    def on_response(self, ch, method, props, body): #19.回调函数
        if self.corr_id == props.correlation_id:
       #20.props:取收到消息里面的服务端properties的correlation_id
       #20.服务端的correlation_id 刚好是接收客户端发送的correlation_id
       #20.客户端的correlation_id = self.corr_id =str(uuid.uuid4())#生成随机数字
       #20.接收到了body是832040
            self.response = body #21.收到的消息赋值给self.response = 832040

    def call(self, n):# 2.n=30
        self.response = None# 3. 赋值self.response
        self.corr_id = str(uuid.uuid4())#4.生成随机数字(唯一标识符)
        print(self.corr_id)

        #5. basic_publish申明发消息
        self.channel.basic_publish(exchange='',
                                   routing_key='rpc_queue',
                                   #7.字符串30存放到了rpc_queue队列
                                   properties=pika.BasicProperties(
                                       reply_to=self.callback_queue,
                   #7.发送随机生成队列队列,服务端可以用props调取
                                       correlation_id=self.corr_id,
#7.随机数字,服务端可以用props调取
                                   ),
                                   body=str(n))
#6.body=字符串30 发送到 routing_key    7.后转战服务端

        while self.response is None:
#22.这个时候self.response = 832040 就不循环下面的
            #如果self.response=N循环下面
            self.connection.process_data_events()
     #这里客户端发送了30该数据给了服务端,就需要等待服务器处理返回的结果,那么我们自然也需要启动consumer端:channel.start_consuming()##start_consuming:进入阻塞模式,有消息就取,没消息就等##
     #但是这里我们是异步了,等于我执行了以后肯定需要可以其他操作,状态,所以这里的consume端就不可以用channel.start_consuming() 
     #process_data_events理解非阻塞版的start_consuming#等于没有消息就不阻塞,一直循环
          
        return int(self.response)# 23.返回832040转成数字类型


fibonacci_rpc = FibonacciRpcClient()#实例化一个实例

print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30)#1.调用了该实例的父类的call方法 实参:30
print(" [.] Got %r" % response)# 24.接收了返回值832040打印并显示

服务端

__author__ = "Burgess Zheng"
# _*_coding:utf-8_*_
import pika
import time

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost')
    )
#初始化  相当于建立一个socket

channel = connection.channel()#初始化  相当于建立一个socket

channel.queue_declare(queue='rpc_queue')
#声明(生成)rpc_queue队列


def fib(n): #12.n=30
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)
#反正不知道怎么计算了 值知道结果是832040
#1 1 2 3 5 8 13 21 34 .......832040


def on_request(ch, method, props, body):# 9.回调函数
    n = int(body)  #10.int(30) 把30转成数字类型  n=30
    print(" [.] fib(%s)" % n)
    response = fib(n)# 11. .调用斐波那契函数执行 30作为实参 得到结果832040
    #13
   #basic_publish申明发消息
    ch.basic_publish(exchange='',
                     routing_key=props.reply_to,
#15.props取客户端 properties的reply_to=随机生成的队列self.callback_queue,
#15.832040存放在该self.callback_queue队列
                     properties=pika.BasicProperties(correlation_id= \
                                               props.correlation_id),
#15.转战客户端        #15.props.取客户端的correlation_id=随机生成数字  
                                               body=str(response))
#14.832040类型转字符串 发送到 routing_key
    ch.basic_ack(delivery_tag=method.delivery_tag)#确保任务完成的通知


channel.basic_qos(prefetch_count=1)

#basic_consume声明收消息
channel.basic_consume(on_request, queue='rpc_queue')
#8.客户端启动发送了30到rpc_queue队列
#8.本端接收到该队列的数据30 启动了回调函数on_request


print(" [x] Awaiting RPC requests")
channel.start_consuming()
# start_consuming:进入阻塞模式,有消息就取,没消息就等

执行结果

如果服务器多台的话就使用 exchange 模式的 topic模式或者direct模式的routing_key区别服务器就可以了

但是还有个问题就是客户端执行就要等结果才可以继续操作。

解决:可以客户端执行发送命令后,不用等待服务端返回结果立刻返回一个task_i_xx可以继续其他操作, 如果后续有需求这个结果 可以get ,task_i_xx找到该结果

task_i_xx相当于一个queue(队列)名 ,所有服务端的结果返回到该task_id_xx队列,后去get该队列的数据就可以了 ,也可以建立多个task_id队列,区别服务器的返回结果,也可以在返回结果上做标记区别取服务器

 

猜你喜欢

转载自blog.csdn.net/Burgess_zheng/article/details/85843670