一、推荐网址
http://www.rabbitmq.com/ Rabbitmq官网
https://www.jb51.net/article/135989.htm Rabbitmq常见问题解决办法
queues.io 统计了很多消息队列的官方网址
二、RabbitMQ概念:
RabbitMQ是部署最广泛的开源消息代理。RabbitMQ在全球范围内在小型初创公司和大型企业中进行了超过35,000次RabbitMQ生产部署,是最受欢迎的开源消息代理。
三、消息代理的应用
a:将消息路由到一个或多个目的地
b:将消息转换为替代表示
c:执行消息聚合,将消息分解为多个消息并将其发送到目的地,然后将响应重新组合成一条消息以返回给用户
d:与外部存储库交互以扩充消息或存储消息
e:调用Web服务以检索数据
f:回应事件或错误
g:使用发布 - 订阅模式提供内容和基于主题的消息路由
四、消息协议
网址:http://www.rabbitmq.com/tutorials/amqp-concepts.html
AMQP 0-9-1 (高级消息队列协议)是一种消息传递协议,它使符合要求的客户端应用程序能够与符合要求的消息传递中间件代理进行通信
AMQP 是一种可编程的协议,定义了三种实体(对象):queues, exchanges and bindings
- queues: 队列,存储信息,用于消费(获取)
- exchanges:消息中转站,包含多种类型
- bindings: 消息转发规则,定义了route(路由),规定怎么把消息发到队列
exchanges的属性
Name:名字
Durability :持久化,出现意外退出时,重启时候恢复exchange
Auto-delete:自动删除 (所有队列都解除绑定时候)
exchanges的类型
Direct exchange (Empty string) and amq.direct
Fanout exchange amq.fanout
Topic exchange amq.topic
Headers exchange amq.match (and amq.headers in RabbitMQ)
exchanges类型解释:
Direct Exchange(默认):queue创建时,绑定一个同名的routing key。用途:把任务分配给多个workers,每个work做特定工作,比如写日志。简单地说,就是按照queue来发送信息,可以想象为私聊
fanout Exchange:传递消息到每一个queue,忽略routing key。用途:多处记录日志,统一发布通知,球赛更新比分,分布式系统更新配置信息。简单地说,就是忽略queue,只要你订阅我,那我就发给你。不管你想不想接收。可以想象为群发
Topic Exchange:根据规则匹配相应的queue,实现发布/订阅。注意: * 代表一个单词, # 代表任意个单词(0或n)。用途:根据不同标签更新新闻,根据位置信息提供商品
Headers Exchange:根据多个属性当作消息头,忽略routing key,需要开发者定义更多内容。用途:当direct exchanges的routing key不是字符串时,可使用这个自定义属性匹配
五、根据官网在服务器linux上安装RabbitMQ
https://www.rabbitmq.com/download.html
六、在linux上启动
启动:systemctl start rabbitmq-server
查看是否启动:ps -aux | grep rabbit
rabbitmq自带的查看状态方法:rabbitmqctl status
以15672端口为例,启动server:rabbitmq-plugins enable rabbitmq_management。如果遇到问题查看上面的常见问题解决办法网页。此时用浏览器localhost@15762的时候会提示只能用localhost登录,这时需要我们添加节点:
添加用户:rabbitmqctl add_user kenny kenny12345
添加用户角色(如管理员):rabbitmqctl set_user_tags kenny administrator
设置权限:rabbitmqctl set_permissions -p / kenny ".*" ".*" ".*"
此时就可以在浏览器用kenny这个用户登录了
七、用Python语言使用RabbitMQ的时候,需要安装pika库,这个库是用来连接rabbitmq
pip install pika
八、练习一下,需要注意的是,因为RabbitMQ是一个消息代理,所以需要消息的发送方和接收方,我们在练习的时候同时打开至少两个窗口,一个发送一个接收。
1、第一个练习,简单的实现一下发送和接收功能
# msg_worker
# 接收消息
"""
1、连接服务
2、消费消息
"""
import pika
# 连接服务
creds = pika.PlainCredentials("kenny", "kenny12345")
params = pika.ConnectionParameters(host="192.168.226.130", credentials=creds)
# 我们在网页操作的时候,浏览器url为host@15672,然后输入用户名密码。ConnectionParameters这一函数就是模拟我们登陆的操作。
# credentials也是一个类,作用是认证,我们通过pika.PlainCredentials(username,password)导入进来的
connection = pika.BlockingConnection(params)
# 创建消息队列
channel2 = connection.channel()
channel2.queue_declare(queue="msg_queue")
# 接收消息
# callback的参数是固定的,可以先这么写
def callback(ch, method, properties, body):
print(f"收到消息:{body}")
channel2.basic_consume('msg_queue', callback)
channel2.start_consuming()
# msg_pub
# 发送消息
"""
1、连接服务
2、创建消息队列
2、发送消息
"""
import pika
# 连接服务
creds = pika.PlainCredentials("kenny", "kenny12345")
params = pika.ConnectionParameters(host="192.168.226.130", credentials=creds)
# credentials也是一个类,作用是认证,我们通过pika.PlainCredentials(username,password)导入进来的
connection = pika.BlockingConnection(params)
# 创建消息队列
channel1 = connection.channel()
channel1.queue_declare(queue="msg_queue")
# 发送消息
msg = "new message from kenny"
channel1.basic_publish(exchange="", routing_key="msg_queue", body=msg)
print(f"发送消息:{msg}")
connection.close()
2、第二个练习,添加持久化、任务均衡代码
当接收方接收消息的时候忽然故障崩溃了,导致消息没有接收完,等系统恢复的时候不能确定上一条消息有没有接收,这样就会出现未知的错误。此时就需要我们的可持久性了。通道和消息具备可持久性后,接收方出故障了,再次运行时会重新接受消息。这条消息不会丢失。
在创建队列的时候加一个参数durable=True,使得队列能持久化,即channel1.queue_declare(queue="msg_queue", durable=True)。
发送消息的时候加一个参数properties=pika.BasicProperties(delivery_mode=2),使得消息能持久化,即channel1.basic_publish(exchange="", routing_key="msg_queue", body=msg, properties=pika.BasicProperties(delivery_mode=2))
接收消息的时候需要一个确认通知,即告知发送方已经收到消息了,即在callback函数里加一句: ch.basic_ack(delivery_tag=method.delivery_tag)
!!注意!!修改队列的时候不能在原基础上修改,因为队列已经建立了,强行修改会报错的,此时只要修改队列名queue就可以变成另一个队列了。
轮询:当有多个客户端同时接收时,是一个一个按顺序接收,这个消息客户端1接收,下个消息客户端2接收,以此类推。。。。。
在接收方的callback函数里加入time.sleep(body.count())可以来模拟一下处理时长,用来在多个接收端同时工作的时候来均衡。官方的操作是数body里有多少个'.',有多少个'.'就休息多少秒,这样来模拟任务处理时间。
因为轮询的关系,导致多个客户端同时接收的时候,只有一个客户端能接到消息。这就会出现这样的场景:奇数任务由客户端1处理,偶数任务由客户端2处理,但是当偶数任务都很费时的时候,客户端2就会出现积压情况,此时在就收任务channel2.basic_consume()之前加上一句均衡任务的代码channle2.basic_qos(prefetch_count=1),这代码的意思是每一个客户端只积压一个任务,超过一个任务的时候就会分配到其他的客户端,起到均衡任务的功能。可以用time.sleep()来模拟。
# msg_worker
# 接收消息
# 持久化
# 均衡任务
"""
1、连接服务
2、消费消息
"""
import time
import pika
config = {
"username": "kenny",
"password": "kenny12345",
"host": "192.168.226.130",
"port": ""
}
# 连接服务
creds = pika.PlainCredentials(config["username"], config["password"])
params = pika.ConnectionParameters(host=config["host"], credentials=creds)
# credentials也是一个类,作用是认证,我们通过pika.PlainCredentials(username,password)导入进来的
connection = pika.BlockingConnection(params)
# 创建消息队列
channel2 = connection.channel()
channel2.queue_declare(queue="msg_queue1", durable=True)
# 接收消息
# callback的参数是固定的,可以先这么写
def callback(ch, method, properties, body):
print(f"收到消息:{body.decode()}")
time.sleep(body.count(b"-"))
ch.basic_ack(delivery_tag=method.delivery_tag)
# 均衡任务
channel2.basic_qos(prefetch_count=1)
channel2.basic_consume('msg_queue1', callback)
channel2.start_consuming()
# msg_pub
# 发送消息
# 命令可加参数
# 持久化
"""
1、连接服务
2、创建消息队列
2、发送消息
"""
import sys
import pika
config = {
"username": "kenny",
"password": "kenny12345",
"host": "192.168.226.130",
"port": ""
}
# 连接服务
creds = pika.PlainCredentials(config["username"], config["password"])
params = pika.ConnectionParameters(host=config["host"], credentials=creds)
# credentials也是一个类,作用是认证,我们通过pika.PlainCredentials(username,password)导入进来的
connection = pika.BlockingConnection(params)
# 创建消息队列
channel1 = connection.channel()
channel1.queue_declare(queue="msg_queue1", durable=True)
# 发送消息
msg = ' '.join(sys.argv[1:]) or "new msg from kenny"
channel1.basic_publish(exchange="", routing_key="msg_queue1", body=msg, properties=pika.BasicProperties(delivery_mode=2))
print(f"发送消息:{msg}")
connection.close()
3、第三个练习,之前的两个练习都是默认的exchange类型,即私聊,当多个接收端的时候会出现轮询接收的情况,接下来我们来看看fanout类型的exchange,即群发,多个接收端同时接收消息。
# Rabbitmq自定义基类,包括订阅和发布的方法
import sys
import pika
from settings import CONFIG
class BaseMQ():
"""Rabbitmq自定义基类"""
def __init__(self):
"""初始化"""
self.connection = self.make_connection()
self.channel = self.connection.channel()
def make_connection(self):
"""连接通信管道"""
creds = pika.PlainCredentials(CONFIG["username"], CONFIG["password"])
params = pika.ConnectionParameters(host=CONFIG["host"], credentials=creds)
connection = pika.BlockingConnection(params)
return connection
def make_exchange(self, exchange="news", exchange_type="fanout"):
"""创建fanout类型通道"""
self.channel.exchange_declare(exchange=exchange, exchange_type=exchange_type)
def make_random_queue(self):
"""随机生成fanout类型的exchange下的通道"""
q = self.channel.queue_declare('', exclusive=True)
return q.method.queue
def bind_queue(self, queue, exchange):
"""fanout类型通道绑定queue"""
self.channel.queue_bind(queue, exchange)
def make_queue(self, queue_name):
"""绑定消息队列"""
self.channel.queue_declare(queue=queue_name, durable=True)
def publish(self, msg, exchange, routing_key=""):
"""发送消息"""
self.channel.basic_publish(exchange=exchange, routing_key=routing_key, body=msg, properties=pika.BasicProperties(delivery_mode=2))
def consume(self, callback, queue_name):
"""均衡任务"""
self.channel.basic_qos(prefetch_count=1)
self.channel.basic_consume(queue=queue_name, on_message_callback=callback)
self.channel.start_consuming()
def close_connection(self):
"""关闭连接"""
self.connection.close()
def main():
msg = ' '.join(sys.argv[1:]) or "new msg from kenny"
pubb = BaseMQ()
if __name__ == "__main__":
main()
# msg_pub
# 发送消息 订阅/发布
# 命令可加参数
# 抽象为类
"""
1、连接服务
2、创建消息队列
2、发送消息
"""
import sys
from basemq import BaseMQ
class PubMQ(BaseMQ):
def __init__(self):
super().__init__()
def main():
msg = ' '.join(sys.argv[1:]) or "new msg from kenny"
mq_pub = PubMQ()
mq_pub.make_exchange(exchange="news", exchange_type="fanout")
# mq_pub.make_queue("supper_queue")
# mq_pub.publish(msg, "supper_queue")
mq_pub.publish(msg, "news")
print(f"发送消息:{msg}")
# mq_pub.close_connection()
if __name__ == "__main__":
main()
# msg_worker
# 接收消息,订阅/发布
"""
1、连接服务
2、消费消息
"""
import time
from basemq import BaseMQ
class ConMQ(BaseMQ):
def __init__(self):
super().__init__()
def callback(self, ch, method, properties, body):
print(f"收到消息:{body.decode()}")
time.sleep(body.count(b"-"))
print("ok")
ch.basic_ack(delivery_tag=method.delivery_tag)
def main():
mq_con = ConMQ()
mq_con.make_exchange(exchange="news", exchange_type="fanout")
queue_name = mq_con.make_random_queue()
print(queue_name)
mq_con.bind_queue(queue_name, "news")
mq_con.consume(mq_con.callback, queue_name)
if __name__ == "__main__":
main()