RabbitMQ
什么叫消息队列
消息(Message)是指在应用间传送的数据。消息能够很是简单,好比只包含文本字符串,也能够更复杂,可能包含嵌入对象。python
消息队列(Message Queue)是一种应用间的通讯方式,消息发送后能够当即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不论是谁发布的。这样发布者和使用者都不用知道对方的存在。服务器
为什么用消息队列
从上面的描述中能够看出消息队列是一种应用间的异步协做机制,那何时须要使用 MQ 呢?负载均衡
以常见的订单系统为例,用户点击【下单】按钮以后的业务逻辑可能包括:扣减库存、生成相应单据、发红包、发短信通知。在业务发展初期这些逻辑可能放在一块儿同步执行,随着业务的发展订单量增加,须要提高系统服务的性能,这时能够将一些不须要当即生效的操做拆分出来异步执行,好比发放红包、发短信通知等。这种场景下就能够用 MQ ,在下单的主流程(好比扣减库存、生成相应单据)完成以后发送一条消息到 MQ 让主流程快速完结,而由另外的单独线程拉取MQ的消息(或者由 MQ 推送消息),当发现 MQ 中有发红包或发短信之类的消息时,执行相应的业务逻辑。异步
RabbitMQ
RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。rabbitMQ是一款基于AMQP协议的消息中间件,它可以在应用之间提供可靠的消息传输。在易用性,扩展性,高可用性上表现优秀。使用消息中间件利于应用之间的解耦,生产者(客户端)无需知道消费者(服务端)的存在。并且两端可使用不一样的语言编写,大大提供了灵活性。ide

中文文档函数
rabbitMQ安装
复制代码 安装配置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
rabbitMQ工做模型
简单模式
示例
# ######################### 生产者 ######################### #!/usr/bin/env python import pika connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel() channel.queue_declare(queue='hello') channel.basic_publish(exchange='', routing_key='hello', body='Hello World!') print(" [x] Sent 'Hello World!'") connection.close()
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.queue_declare(queue='hello') def callback(ch, method, properties, body): print(" [x] Received %r" % body) channel.basic_consume( callback, queue='hello', no_ack=True) print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming()
相关参数
(1)no-ack = False,若是消费者遇到状况(its channel is closed, connection is closed, or TCP connection is lost)挂掉了,那么,RabbitMQ会从新将该任务添加到队列中。post
- 回调函数中的
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 :消息不丢失fetch
# 生产者 #!/usr/bin/env python import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello1', durable=True)
channel.basic_publish(exchange='',
routing_key='hello1',
body='Hello World%s!',
properties=pika.BasicProperties(delivery_mode=2)) # make message persistent
print(" [x] Sent 'Hello World!'")
connection.close()
# 消费者
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello1', durable=True)
def callback(ch, method, properties, body):
# exit()
print(" [x] Received %r" % body)
# 若是有消费者挂了可使用,
# no_ack = False
ch.basic_ack(delivery_tag=method.delivery_tag) # 回馈机制打开,打开此行
channel.basic_consume(callback,
queue='hello1',
no_ack=False) # 回馈机制打开
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
(3) 消息获取顺序
默认消息队列里的数据是按照顺序被消费者拿走,例如:消费者1 去队列中获取 奇数 序列的任务,消费者1去队列中获取 偶数 序列的任务。
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()
exchange模型
3.1 发布订阅
发布订阅和简单的消息队列区别在于,发布订阅会将消息发送给全部的订阅者,而消息队列中的数据被消费一次便消失。因此,RabbitMQ实现发布和订阅时,会为每个订阅者建立一个队列,而发布者发布消息时,会将消息放置在全部相关队列中。
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') # 建立一个channel 而后声明其为exchange模式 生产者或消费者哪一个先运行,谁先建立(在rabbitmq中建立) message = ' '.join(sys.argv[1:]) or "info: Hello World!" channel.basic_publish(exchange='logs', # 将数据写入到exchange中 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) # 在channel声明并声明一个订阅者队列 queue_name = result.method.queue # 自动给queue起个名字 channel.queue_bind(exchange='logs', # 将刚才声明的queue与exchange绑定 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 关键字发送
exchange type = direct
以前事例,发送消息时明确指定某个队列并向其中发送消息,RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据 关键字 断定应该将数据发送至指定队列。

#!/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() 消费者

#!/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') severity = sys.argv[1] if len(sys.argv) > 1 else 'info' message = ' '.join(sys.argv[2:]) or 'Hello World!' channel.basic_publish(exchange='direct_logs', routing_key=severity, body=message) print(" [x] Sent %r:%r" % (severity, message)) connection.close() 生产者
3.3 模糊匹配
exchange type = topic
发送者路由值 队列中 old.boy.python old.* -- 不匹配 old.boy.python old.# -- 匹配
在topic类型下,可让队列绑定几个模糊的关键字,以后发送者将数据发送到exchange,exchange将传入”路由值“和 ”关键字“进行匹配,匹配成功,则将数据发送到指定队列。
- # 表示能够匹配 0 个 或 多个 单词
- * 表示只能匹配 一个 单词
示例:

#!/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() 消费者

#!/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') 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() 生产者
基于RabbitMQ的RPC
Callback queue 回调队列
一个客户端向服务器发送请求,服务器端处理请求后,将其处理结果保存在一个存储体中。而客户端为了得到处理结果,那么客户在向服务器发送请求时,同时发送一个回调队列地址reply_to
。
Correlation id 关联标识
一个客户端可能会发送多个请求给服务器,当服务器处理完后,客户端没法辨别在回调队列中的响应具体和那个请求时对应的。为了处理这种状况,客户端在发送每一个请求时,同时会附带一个独有correlation_id
属性,这样客户端在回调队列中根据correlation_id
字段的值就能够分辨此响应属于哪一个请求。
客户端发送请求:某个应用将请求信息交给客户端,而后客户端发送RPC请求,在发送RPC请求到RPC请求队列时,客户端至少发送带有reply_to以及correlation_id两个属性的信息
服务器端工做流: 等待接受客户端发来RPC请求,当请求出现的时候,服务器从RPC请求队列中取出请求,而后处理后,将响应发送到reply_to指定的回调队列中
客户端接受处理结果: 客户端等待回调队列中出现响应,当响应出现时,它会根据响应中correlation_id字段的值,将其返回给对应的应用
服务器端
客户端
# !/usr/bin/env python
import pika
import uuid
class FibonacciRpcClient(object):
def __init__(self):
"""
客户端启动时,建立回调队列,会开启会话用于发送RPC请求以及接受响应
"""
# 创建链接,指定服务器的ip地址,链接到rabbitmq服务器
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)) # 将数据发送到rpc_queue中
while self.response is None: # 断定response是否有值,没有值的话一直在这while循环
self.connection.process_data_events() # basic_consume和他有关
return int(self.response)
# 创建客户端
fibonacci_rpc = FibonacciRpcClient() # 第一步建立对象执行init方法
# 发送RPC请求
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30) # 第二步执行call方法
print(" [.] Got %r" % response)
sudo rabbitmqctl add_user alex 123 # 设置用户为administrator角色 sudo rabbitmqctl set_user_tags alex administrator # 设置权限 sudo rabbitmqctl set_permissions -p "/" alex '.''.''.' # 而后重启rabbiMQ服务 sudo /etc/init.d/rabbitmq-server restart # 而后可使用刚才的用户远程链接rabbitmq server了。 ------------------------------ credentials = pika.PlainCredentials("alex","123") connection = pika.BlockingConnection(pika.ConnectionParameters('192.168.14.47',credentials=credentials))