什么叫消息队列

有的资料说:消息队列是消息中间件的一种实现方式。

消息(Message)是指在应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。

MQ即消息队列(Message Queue)是一种应用程序之间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。

消息中间件是提供软件和软件之间连接的软件,以便于软件各部件之间的沟通。

使用消息中间件的优势

1.业务调用链短,用户等待时间短。

2.部分组件故障不会瘫痪整个业务

3.业务高峰期有缓冲。

4.业务高峰期不会产生大量的异步线程。

消息中间件的作用

1. .系统异步与解耦。上游系统对下游系统如果同步调用话,则会大大降低系统的吞吐量和并发度,且系统的耦合度太高,而异步调用会解决这个问题

2.限流削峰。如果微服务没有能力去处理超量消息,可以先把超量消息放到MQ中,以便系统可以慢慢处理,进而避免了消息丢失和系统被压垮。

3消息的广播

4.消息收集。分布式系统会产生海量的数据流,如业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后对这些数据流进行大数据分析,这是当前互联网平台必备的技术,通过MQ完成此类数据是最好的选择

5.最终一致性处理。我发送到消息中间件的消息,微服务一定会处理。

常见的消息队列

rabbitmq: 延迟最低可以达到微秒,并发能力极强。其吞吐量相较于kafka和racoketmq小

kafka:延迟可以达到毫秒(ms)数据写在磁盘上,其最大的特点就是高吞吐率,常用于大数据领域的实时计算,日志采集等领域,其没有遵循任何的MQ协议,使用其自研的协议。

racoket,使用java编写的MQ产品,性能和稳定性非常高,其没有遵循任何的MQ协议,使用其自研的协议。

 

为何用消息队列

消息队列是一种应用见的异步作机制

我们想象一下我们注册的的情景,当我们提交注册后服务器的操作

1.检测用户返回的字段是否符合要求。

2.如果符合要求,则把注册信息填入到数据库中。

3.给用户发送注册成功邮箱。

4.分析用户的注册信息,以便以后给用户推荐相应的信息。

5.返回注册成功页面。

对于用户来说,它提交完信息后,只需要知道注册成功了没有,其他的它根本就不用立马知道,他就可以去做其他的事情。所以服务器只需要顺序执行1.2.5步操作就可以了,其他的不需要立即执行的操作,可以放到消息队列中来,这样对于用户来说,节省了请求的时间,可以取干其他事情,同时放到消息队列中的操作,对于用户来说是异步操作。

消息队列的特点和作用

消息队列的特点是异步处理,主要目的是减少用户请求的时间和解耦。

应用场景:将比较耗时不需要立即返回结果的操作放到消息队列中,把需要立即返回结果的操作先执行完。其实就是用在大并发的时候。

消息中间件最主要的作用是解耦,中间件最标准的用法是生产者生产消息传送到队列,消费者从队列中拿取消息并处理,生产者不用关心是谁来消费,消费者不用关心谁在生产消息,当消费者需要拿数据的时候,只需要和消息队列沟通,如果有就拿,如果没有就等待,当消息队列中消息满了,消息队列会告诉生产者别先生产了。这样看来生产者和消费者之间解耦了,生产者不用等待消费吃完再生产东西,消费者不用等待生产者把所有的东西都吃完再生产包子,从而达到解耦的目的。在分布式的系统中,消息队列也会被用在很多其它的方面,比如:分布式事务的支持,RPC的调用等等。

  1. 应用解耦
  2. 提速
  3. 广播
  4. 消峰

具体内容见

https://www.zhihu.com/question/34243607/answer/58314162
下边祁达方的回答

 

RabbitMQ 

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

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

rabbitmq高性能的原因

RabbitMQ 是一个由 Erlang 语言开发的,erlang是一门为交换机软件开发诞生的编程语言

erlang语言的特点:

  erlang是通用的面向并发的编程语言,适用于分布式系统。

  基于虚拟机解释运行,跨平台部署

  erlang语言进程间上下文切换的效率远高于c语言和java语言

  有着和原生socket一样的延迟,网络i/o的性能极高

官方文档

AMQP

amqp协议作为rabbitmq的规范,规定了rabbitmq对外的接口

AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。

RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。具体特点包括:

  1. 可靠性(Reliability)
    RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。

  2. 灵活的路由(Flexible Routing)
    在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。

  3. 消息集群(Clustering)
    多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。

  4. 高可用(Highly Available Queues)
    队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。

  5. 多种协议(Multi-protocol)
    RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。

  6. 多语言客户端(Many Clients)
    RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby 等等。

  7. 管理界面(Management UI)
    RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。

  8. 跟踪机制(Tracing)
    如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。

  9. 插件机制(Plugin System)
    RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。



作者:预流
链接:https://www.jianshu.com/p/79ca08116d57
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

rabbitMQ安装

ubuntu安装教程博客”:  https://blog.csdn.net/u010889616/article/details/80643892

无法访问控制台的解决办法https://blog.csdn.net/fgjfj/article/details/80307826?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

如果是阿里云服务器的话,别忘记配置安全组

 

for Linux:

安装配置epel源
   $ rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
 
安装erlang
   $ yum -y install erlang
 
安装RabbitMQ
   $ yum -y install rabbitmq-server
注意:service rabbitmq-server start/stop

for Mac:

bogon:~ yuan$ brew install rabbitmq
bogon:~ yuan$ export PATH=$PATH:/usr/local/sbin
bogon:~ yuan$ rabbitmq-server

rabbitMQ工作模型

模拟软件网址:http://tryrabbitmq.com/

简单模式

即exchange=" ",也就是说简单模式不用exchange模式,直接把数据放到队列中

 

 

 

#生产者
import pika

# 创建通向rabbitmq的连接
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost'))

# 获取rabbitmq通信的接口;channel
channel = connection.channel()

# 在rabbitmq通过channel创建一个队列:hello队列
channel.queue_declare(queue='q1',durable=True) 

#使用默认exchange时允许通过routing_key明确指定message将被发送给哪个queue
#body参数指定了要发送的message内容
channel.basic_publish(exchange='', #工作模式为简单模式
                      routing_key='q1',#指定队列名字
                      body=('china'))#生产者生产的东西

print(" 生产者 Sent 'Hello World!'")

#关闭与rabbimq Server间的连接
connection.close()

 

 

####消费者
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
# 获取rabbitmq通信的接口;channel
channel = connection.channel()
#在rabbitmq通过channel创建一个队列:hello队列,目的是,不管谁先启动都可以,这是一个好习惯
channel.queue_declare(queue='q1')

#定义回调函数,一旦从queue中接收到一个message回调函数将被调用
def callback(ch, method, properties, body): #回调函数,
    print(" [x] Received %r" % body)

#告诉rabbitmq使用callback来接收数据
channel.basic_consume(callback,
                      queue='q1',
                      no_ack=True)#默认是true,不对消息进行确认

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

#开始循环从queue中接收message并使用callback进行处理
channel.start_consuming()

 

相关参数

(1)no-ack = False,是为了避免消费者客户端崩掉了,拿不到数据,如果消费者的客户端崩掉了,那么,RabbitMQ会重新将该任务添加到队列中。这样唯一的缺陷是效率变慢了.

  • 回调函数中的ch.basic_ack(delivery_tag=method.delivery_tag)
  • basic_comsume中的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)  durable =True:防止rabbitMQ突然崩掉,队列中的消失消失,

# 生产者
#!/usr/bin/env python
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()


# 消费者
#!/usr/bin/env python
# -*- coding:utf-8 -*-
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()

 

(3) 消息获取顺序

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

2.channel.basic_qos(prefetch_count=1) 表示谁来谁取,不再按照奇偶数排列。

复制代码
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pika

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

# make message persistent
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_qos(prefetch_count=1)

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

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

work queues

将耗时的消息处理通过队列分配给多个consumer来处理,我们称此处的consumer为worker,我们将此处的queue称为Task Queue,其目的是为了避免资源密集型的task的同步处理,也即立即处理task并等待完成。相反,调度task使其稍后被处理。也即把task封装进message并发送到task queue,worker进程在后台运行,从task queue取出task并执行job,若运行了多个worker,则task可在多个worker间分配。

  

 

 

###生产者
import pika
import sys

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

# 仅仅对message进行确认不能保证message不丢失,比如RabbitMQ崩溃了queue就会丢失
# 因此还需使用durable=True声明queue是持久化的,这样即便Rabb崩溃了重启后queue仍然存在
channel.queue_declare(queue='task_queue', durable=True)

# 从命令行构造将要发送的message
message = ' '.join(sys.argv[1:]) or "Hello World!"

# 除了要声明queue是持久化的外,还需声明message是持久化的
# basic_publish的properties参数指定message的属性
# 此处pika.BasicProperties中的delivery_mode=2指明message为持久的
# 这样一来RabbitMQ崩溃重启后queue仍然存在其中的message也仍然存在
# 需注意的是将message标记为持久的并不能完全保证message不丢失,因为
# 从RabbitMQ接收到message到将其存储到disk仍需一段时间,若此时RabbitMQ崩溃则message会丢失
# 况且RabbitMQ不会对每条message做fsync动作
# 可通过publisher confirms实现更强壮的持久性保证
channel.basic_publish(exchange='',
                      routing_key='task_queue',
                      body=message,
                      properties=pika.BasicProperties(
                          delivery_mode=2,  # make message persistent
                      ))
print(
" [x] Sent %r" % (message,))
connection.close()

消费者

import random

import pika
import time

# 默认情况RabbirMQ将message以round-robin方式发送给下一个consumer
# 每个consumer接收到的平均message量是一样的
# 可以同时运行两个或三个该程序进行测试

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

# 仅仅对message进行确认不能保证message不丢失,比如RabbitMQ崩溃了
# 还需使用durable=True声明queue是持久化的,这样即便Rabb崩溃了重启后queue仍然存在其中的message不会丢失
# RabbitMQ中不允许使用不同的参数定义同名queue
channel.queue_declare(queue='task_queue', durable=True)

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


# 回调函数,函数体模拟耗时的任务处理:以message中'.'的数量表示sleep的秒数
def callback(ch, method, properties, body):
    print(" [x] Received %r" % (body,))
    time.sleep(random.random())
    print(
    " [x] Done")
    # 对message进行确认
    ch.basic_ack(delivery_tag=method.delivery_tag)


# 若存在多个consumer每个consumer的负载可能不同,有些处理的快有些处理的慢
# RabbitMQ并不管这些,只是简单的以round-robin的方式分配message
# 这可能造成某些consumer积压很多任务处理不完而一些consumer长期处于饥饿状态
# 可以使用prefetch_count=1的basic_qos方法可告知RabbitMQ只有在consumer处理并确认了上一个message后才分配新的message给他
# 否则分给另一个空闲的consumer
channel.basic_qos(prefetch_count=1)

# 这里移除了no_ack=True这个参数,也即需要对message进行确认(默认行为)
# 否则consumer在偶然down后其正在处理和分配到该consumer还未处理的message可能发生丢失
# 因为此时RabbitMQ在发送完message后立即从内存删除该message
# 假如没有设置no_ack=True则consumer在偶然down掉后其正在处理和分配至该consumer但还未来得及处理的message会重新分配到其他consumer
# 没有设置no_ack=True则consumer在收到message后会向RabbitMQ反馈已收到并处理了message告诉RabbitMQ可以删除该message
# RabbitMQ中没有超时的概念,只有在consumer down掉后重新分发message
channel.basic_consume(callback,
                      queue='task_queue')

channel.start_consuming()

 

exchange模型

exchange的作用

exchange是rabbitmq的核心,作用根据根据绑定关系和路由键。为消息提供路由,将消息转发至相应的队列

exchange模型与简单模式区别是,简单模式创建一个channel.queue_declare队列,而exchange模型创建的是channel.exchange_declare交换机。

3.1 发布订阅(广播路由)

 

 

 

发布订阅和简单的消息队列区别在于,发布订阅会将消息发送给所有的订阅者,而消息队列中的数据只能被一个消息者取走。

routing key不需要指定哪个队列,banding key的名字随便起

所以,RabbitMQ实现发布和订阅时,会为每一个订阅者创建一个队列,而发布者发布消息时,会将消息放置exchange交换机中,然后交换机会把消息放到每个队列中,然后等待消费者的获取。

优点:保证生产者发出的消息传给每一个消费者。

 

exchange_type = fanout

 

# 生产者
#!/usr/bin/env python
import pika
import sys

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

#创建交换机
channel.exchange_declare(exchange='logs',
                         exchange_type='fanout')

message = ' '.join(sys.argv[1:]) or "info: Hello World!" #sys.argv[1:]在终端传入数据


channel.basic_publish(exchange='logs',
                      routing_key='',#设置为空,因为不需要指定哪个队列,这里是交换机
                      body=message)
print(" [x] Sent %r" % message)
connection.close()


# 消费者
#!/usr/bin/env python
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

#把创建的队列绑定到交换机上
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()

 

3.2 直接路由

配置规则:rouning key=Binding key,容易配置和使用

如果message中的routingkey 和bangdingkey一致, direct exchange 则将message发到对应的queue中

 exchange_type = direct

之前事例,发送消息时明确指定某个队列并向其中发送消息,RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据 关键字 判定应该将数据发送至指定队列。

例子:生产者在发送数据的时候将关键字定义为info, 那么这个数据,只能被绑定关键字为info的消费者读取到。

# 生产者
#!/usr/bin/env python
import pika
import sys

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

#创建交换机
channel.exchange_declare(exchange='direct_logs',
                         exchange_type='direct')


message = " Hello World!" #sys.argv[1:]在终端传入数据


channel.basic_publish(exchange='logs',
                      routing_key='info',#这里就是我们定义的关键字,当定义为info时,exchange只会把消息放到info队列中,
                      body=message)
print(" [x] Sent %r" % message)
connection.close()

 

#消费者
#!/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) # Only allow access by the current connection 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="info")#绑定哪个关键字 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()

 

 3.3 模糊匹配

 exchange_type = topic

功能较为复杂,但是能降级为direct,建议优先使用,为以后使用留有余地

根据routing key及通配规则,将消息分发到指定的队列中

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

有两种匹配模式:

 

  • #  表示可以匹配 0 个 或 多个 单词
  • *   表示只能匹配 一个 单词
  • 全匹配相当于direct
发送者的关键字为              消费者关键字为                匹配结果
old.boy.python                  old.*                     -- 不匹配
old.boy.python                  old.#                     -- 匹配成功       

 

 示例:

复制代码
#!/usr/bin/env python
import pika
import sys

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

channel.exchange_declare(exchange='topic_logs',
                         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

 

 基于RabbitMQ的RPC

详情见博客

RPC(remote procedure call)远程程序调用,它是一种通过网络调用远程计算机上程序的函数或方法,而不需要了解底层网络的技术。是构件分布式程序的一种方式。

RPC是一种协议和模式,用于分布式系统中不同节点之间的通信和调用远程过程,在rpc中客户端可以像调用本地方法一样调用远程方法。无需关心底层的网络通信细节,rpc可以使用不同的传输协议TCP、UDP、HTTP等,也可以使用不同的序列化协议,比如json,xml等。

 

我们平时都是在一台服务器上调用,用不到rpc.

试想果我们需要在远程电脑上运行一个函数方法,并且还要等待一个返回结果,这种模式通常被我们称为远程过程调用或者RPC.

本文使用RabbitMQ实现RPC调用方式。

客户端启动后,

1.会生成一个correlation_id这个是为请求设立的唯一标识,用来以后根据这个标识来从回调队列中取数据。

2.把创建一个回调队列,这个回调队列的作用就是用来接收服务端返回的数据。

3.客户端绑定rabbitmq的请求队列routing_key,然后把correlation_id和reply_to即回调队列发送给客户端。

服务端:

1.创建一个请求队列,

2.从队列中拿前端发过来的数据,并把数据当做参数传给回调函数并执行回调函数

3.回调函数调用 处理数据的函数,并把处理完的数据和唯一标识发送到指定的回调队列中。

 

客户端拿到数据后:

客户端根据唯一标识从指定的回调队列中拿数据

 

 

 

 

 

 

客户端:

import pika

import uuid


class FibonacciRpcClient(object):
    def __init__(self):
        """
        客户端启动时,创建回调队列,会开启会话用于发送RPC请求以及接受响应
        """


        # 建立连接,指定服务器的ip地址
        self.connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))

        # 建立一个会话,每个channel代表一个会话任务
        self.channel = self.connection.channel()

        # 声明回调队列,再次声明的原因是,服务器和客户端可能先后开启,该声明是幂等的,多次声明,但只生效一次
        result = self.channel.queue_declare(exclusive=True)
        # 将队列指定为当前客户端的回调队列
        self.callback_queue = result.method.queue

        # 客户端订阅回调队列,当回调队列中有响应时,调用`on_response`方法对响应进行处理;
        self.channel.basic_consume(self.on_response, no_ack=True,
                                   queue=self.callback_queue)

    # 对回调队列中的响应进行处理的函数
    def on_response(self, ch, method, props, body):
        if self.corr_id == props.correlation_id:
            self.response = body

    # 发出RPC请求
    def call(self, n):

        # 初始化 response
        self.response = None

        # 生成correlation_id唯一标识符号
        self.corr_id = str(uuid.uuid4())

        # 发送RPC请求内容到RPC请求队列`rpc_queue`,同时发送的还有`reply_to`和`correlation_id`
        self.channel.basic_publish(exchange='', #这里用的是简单模式,一对一
                                   routing_key='rpc_queue', #绑定请求队列
                                   properties=pika.BasicProperties(
                                       reply_to=self.callback_queue,   
                                       correlation_id=self.corr_id,
                                   ),
                                   body=str(n)) 

        while self.response is None: #这步的意思是如果服务端没有返回数据将阻塞,知道有数据返回
            self.connection.process_data_events()
        return int(self.response)


# 建立客户端
fibonacci_rpc = FibonacciRpcClient()

# 发送RPC请求
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(32)
print(" [.] Got %r" % response)

 

服务端:

import pika

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

channel=connection.channel()

#声明RPC请求队列

channel.queue_declare(queue="rpc_queue")

#数据处理方法

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

#对RPC请求队列中的请求进行处理

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, #绑定回调函数队列
                     properties=pika.BasicProperties(correlation_id= \
                                                         props.correlation_id), #标识符
                     body=str(response)) #数据
    ch.basic_ack(delivery_tag=method.delivery_tag)


# 负载均衡,同一时刻发送给该服务器的请求不超过一个
channel.basic_qos(prefetch_count=1)

#将队列和on_request函数绑定 channel.basic_consume(on_request, queue
='rpc_queue') print(" server Awaiting RPC requests.....")
#循环从队列中取数据并用on_request函数处理 channel.start_consuming()

 

 注意这两段代码如果运行在同一服务器上时,试验时客户端只能运行一次,不在同一服务器上时,客户端可以运行多次,这是我遇见的最大的坑

 

 

 

posted on 2018-03-30 19:07  程序员一学徒  阅读(166)  评论(0编辑  收藏  举报