rabbitmq基础教程
1. 常见MQ对比
2. rabbitmq基础架构图
3. rabbitmq简介
- broker: 接收和分发消息的应用,RabbitMq server就是Message broker
- connection: 连接,应用服务于server的链接
- channel: 信道,客户端可以建立多个channel,每个channel代表一个会话任务
- message: 消息,由messageproperties和body组成,messageproperties、AMQP.BasicProperties可对消息的优先级、过期时间等参数进行设置,其中参数correlation_id可作为消息的主键
- Exchange:交换机,消息将根据routeKey被交换机转发给对应的绑定队列
- Queue: 队列,消息最终被送到这里等待消费者取走,参数中的Auto-delete意为当前队列的最后一个消息被取出后是否自动删除
- Binding: 绑定exchange和queue之间的虚拟链接,二者通过routingkey进行绑定
- Routingkey: 路由规则,交换机通过它来确定消息被路由到哪里
- Virtual host: 虚拟主机,用来进行逻辑隔离,是最上层的消息路由,一个虚拟主机中可以有多个exchange和queue,同一个虚拟主机中不能有一样的exchange和queue。
3.1 rabbitmq的6中工作模式
简单模式、work queues、Publish/Subscribe发布与订阅模式、Routing路由模式、Topic主题模式、RPC远程调用模式
4. rabbitmq的安装与配置-debian
- 安装erlang环境
apt-get install erlang
- 检查是否安装成功
erl
- 搜索apt下是否有 rabbitmq-server
apt-cache search rabbitmq-server
- 安装
apt-get install rabbitmq-server
- 查看rabbitmq-server的版本
rabbitmqctl status | grep rabbit
- 从rabbitmq-server官网查看erlang与rabbitmq-server版本对应关系
erlang与rabbitmq-server版本对应关系 - 查看rabbitmq-server的状态
systemctl status rabbitmq-server
- 配置可视化管理后台插件
rabbitmq-plugins enable rabbitmq_management
- 创建用户
rabbitmqctl add_user admin 123456
- 用户设置为管理员
rabbitmqctl set_user_tags admin administrator
- 设置读写权限
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
- 查看用户权限列表
rabbitmqctl list_permissions
更详细的用户权限列表参考文档 - 防火墙打开端口:15672,通过
ip:15672
访问我们的后台站点即可。 - 开放的端口
4369 epmd(Erlang Port Mapper Daemon),erlang服务端口
5672 //client端通信口 AMQP
15672 //后台管理系统端口
25672 节点间通信(Erlang分发服务器端口)
61613 //Stomp协议端口
5. rabbitmq-简单模式
在本教程的这一部分中,我们将用 Python 编写两个小程序; 一个是发送单个消息的生产者(发送者) ,另一个是接收消息并将其打印出来的消费者(接收者)。
- 安装依赖
python -m pip install pika
- producer.py
import urllib.parse
import pika
def sender_basic(body):
credentials = pika.PlainCredentials("username", "password")
parameters = pika.ConnectionParameters(host="0.0.0.0", port=5672, credentials=credentials)
connection = pika.BlockingConnection(parameters)
channel = connection.channel()
channel.queue_declare("hello")
channel.basic_publish(exchange="", routing_key="hello", body=body.encode())
connection.close()
def sender_url(url, body):
# amqp://username:password@host:port/<virtual_host>[?query-string]
# 创建链接
connection = pika.BlockingConnection(pika.URLParameters(url))
# 创建通道
channel = connection.channel()
# 创建或指定队列
channel.queue_declare(queue="hello")
# 发送消息
# 我们现在需要知道的是如何使用由空字符串标识的默认交换。
# 这个交换是特殊的-它允许我们精确地指定消息应该去哪个队列。队列名称需要在 routing _ key 参数中指定:
channel.basic_publish(exchange="", routing_key="hello", body=body.encode())
# 关闭链接
connection.close()
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/")
url = f"amqp://admin:password@host:5672/{virtual_host}?socket_timeout=3&stack_timeout=5"
print("请输入要发送的内容:")
sender_url(url, input())
- consumer.py
import pika
import urllib.parse
def receive_url(url):
def callback(ch, method, properties, body):
print("Received:", body.decode())
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建队列是等幂的——我们可以随意运行该命令多次,而且只会创建一个
channel.queue_declare("hello")
channel.basic_consume(queue="hello", on_message_callback=callback, auto_ack=True)
channel.start_consuming()
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/")
url = f"amqp://admin:password@host:5672/{virtual_host}?socket_timeout=3&stack_timeout=5"
receive_url(url)
6. work-queue工作队列模式
在第一个教程中,我们编写了从命名队列发送和接收消息的程序。在本文中,我们将创建一个工作队列,用于在多个工作者之间分配耗时的任务。
工作队列(又名: 任务队列)背后的主要思想是避免立即执行资源密集型任务,并且不得不等待它完成。相反,我们将任务安排在以后完成。我们将任务封装为消息并将其发送到队列。在后台运行的辅助进程将弹出任务并最终执行作业。当您运行许多工作线程时,任务将在它们之间共享。
- new_task.py
import sys
import urllib.parse
import pika
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
url = f"amqp://username:password@host:port/{virtual_host}?socket_timeout=3&stack_timeout=5"
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# durable:支持队列的持久化
# 作用是防止当我们的rabbitmq-server中途挂掉了,就会导致队列和消息丢失
channel.queue_declare("task_queue", durable=True)
message = "".join(sys.argv[1:]) or "Hello World"
channel.basic_publish(
exchange="", routing_key="task_queue", body=message.encode(),
# 标记消息为持久的
properties=pika.BasicProperties(delivery_mode=pika.spec.PERSISTENT_DELIVERY_MODE)
)
- worker.py
import time
import urllib.parse
import pika
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
url = f"amqp://username:password@host:port/{virtual_host}?socket_timeout=3&stack_timeout=5"
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 队列持久化: durable=True
channel.queue_declare("task_queue", durable=True)
def callback(ch, method, properties, body):
ret = body.decode()
time.sleep(ret.count("."))
print(ret)
# 回复rabbitmq-server,任务已经处理完毕,可以删除掉了
# 这样可以确保如果worker在处理的过程中挂掉了,任务可以继续被其它worker处理
ch.basic_ack(delivery_tag=method.delivery_tag)
# 我们可以使用 Channel # basic _ qos 通道方法,并设置 prefetcount = 1。这使用 basic.qos 协议方法
# 告诉 RabbitMQ 不要一次向一个 worker 发送多条消息。或者,换句话说,在工作线程处理并确认前一个消息之前,
# 不要将新消息发送给工作线程。相反,它会将其分派给下一个不再忙碌的工作者。
channel.basic_qos(prefetch_count=1)
# auto_ack=False: 关闭消费者从rabbitmq-server端拿到任务后立即确认,而是要等回调函数执行完成后手动恢复确认ack
# 这样即使消费者在处理的过程中挂掉了,该任务也会重新被其它消费者获取执行
channel.basic_consume(queue="task_queue", on_message_callback=callback, auto_ack=False)
channel.start_consuming()
7. 发布-订阅模式
在上一个教程中,我们创建了一个工作队列。工作队列背后的假设是,每个任务只交付给一个工作者。在本部分中,我们将做一些完全不同的事情——我们将向多个消费者传递消息。这种模式称为“发布/订阅”。
- publish.py,发布者将日志消息发送到交换器中
点击查看代码
import urllib.parse
import pika
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建扇形交换器
exchange = channel.exchange_declare(exchange="logs", exchange_type="fanout")
print("请输入要发送的消息:")
channel.basic_publish(exchange="logs", routing_key="", body=input().encode())
# 关闭链接
connection.close()
点击查看代码
import pathlib
import urllib.parse
import pika
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建扇形交换器
exchange = channel.exchange_declare(exchange="logs", exchange_type="fanout")
# 创建一个随机名称队列, exclusive=True:使用者链接关闭,队列就被删除
result = channel.queue_declare(queue="", exclusive=True)
# 我们已经创建了扇形交换器和队列,现在我们需要告诉交换器将消息发送到我们的队列,交换器和队列之间的关系为绑定
queue_name = result.method.queue
channel.queue_bind(exchange="logs", queue=queue_name)
def callback(ch, method, properties, body):
file_path = pathlib.Path(__file__).parent.joinpath("rabbit.log")
with open(file_path, "ab") as f:
f.write(body)
f.write("\n".encode())
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
点击查看代码
import urllib.parse
import pika
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建扇形交换器
exchange = channel.exchange_declare(exchange="logs", exchange_type="fanout")
# 创建一个随机名称队列, exclusive=True:使用者链接关闭,队列就被删除
result = channel.queue_declare(queue="", exclusive=True)
# 我们已经创建了扇形交换器和队列,现在我们需要告诉交换器将消息发送到我们的队列,交换器和队列之间的关系为绑定
queue_name = result.method.queue
channel.queue_bind(exchange="logs", queue=queue_name)
def callback(ch, method, properties, body):
print(body.decode())
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
8. Routing路由模式
在本教程中,我们将为其添加一个特性——我们将使仅订阅消息的一个子集成为可能。例如,我们将能够只将关键错误消息定向到日志文件(以节省磁盘空间) ,同时仍然能够在控制台上打印所有日志消息。
我们使用的是扇出交换机,这没有给我们太多的灵活性——它只能进行无意识的广播。
我们改用直接交换。直接交换背后的路由算法很简单——消息到其绑定键与消息的路由键完全匹配的队列。
- publish.py
点击查看代码
import sys
import urllib.parse
import pika
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建直接交换器
exchange = channel.exchange_declare(exchange="direct_logs", exchange_type="direct")
severity, body = sys.argv[1], sys.argv[2]
channel.basic_publish(exchange="direct_logs", routing_key=severity, body=body.encode())
# 关闭链接
connection.close()
点击查看代码
import pathlib
import urllib.parse
import pika
severities = ["info", "warning", "error"]
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建直接交换器
exchange = channel.exchange_declare(exchange="direct_logs", exchange_type="direct")
# 创建一个随机名称队列, exclusive=True:使用者链接关闭,队列就被删除
result = channel.queue_declare(queue="", exclusive=True)
# 接收消息的工作方式与前面的教程类似,只有一个例外——我们将为感兴趣的每个严重性创建一个新绑定。
queue_name = result.method.queue
channel.queue_bind(exchange="direct_logs", queue=queue_name, routing_key=severities[-1])
def callback(ch, method, properties, body):
file_path = pathlib.Path(__file__).parent.joinpath("rabbit.log")
with open(file_path, "ab") as f:
f.write(body)
f.write("\n".encode())
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
点击查看代码
import urllib.parse
import pika
severities = ["info", "warning", "error"]
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建直接交换器
exchange = channel.exchange_declare(exchange="direct_logs", exchange_type="direct")
# 创建一个随机名称队列, exclusive=True:使用者链接关闭,队列就被删除
result = channel.queue_declare(queue="", exclusive=True)
# 接收消息的工作方式与前面的教程类似,只有一个例外——我们将为感兴趣的每个严重性创建一个新绑定。
queue_name = result.method.queue
for severity in severities:
channel.queue_bind(exchange="direct_logs", queue=queue_name, routing_key=severity)
def callback(ch, method, properties, body):
print(body.decode())
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
9. Topic
在我们的日志系统中,我们可能不仅希望根据严重性订阅日志,还希望根据发出日志的源订阅日志。您可能从 syslog unix 工具中了解到这个概念,该工具根据严重性(info/police/crit...)和工具(auth/cron/kern...)路由日志。
这将给我们带来很大的灵活性——我们可能只想听取来自“ cron”的关键错误,但也想听取来自“ kern”的所有日志。
为了在我们的日志系统中实现这一点,我们需要了解更复杂的主题交换。
- publish.py
点击查看代码
import sys
import urllib.parse
import pika
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建主题交换器
exchange = channel.exchange_declare(exchange="topic_logs", exchange_type="topic")
routing_key, body = sys.argv[1], sys.argv[2]
channel.basic_publish(exchange="topic_logs", routing_key=routing_key, body=body.encode())
# 关闭链接
connection.close()
- subscription.py
点击查看代码
import pathlib
import urllib.parse
import pika
# *代表一个单词,#代表零个或多个单词
severities = ["*.info", "*.warning", "*.error"]
sources = ["kenel.*", "haha.*", "heihie.*"]
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建主题交换器
exchange = channel.exchange_declare(exchange="topic_logs", exchange_type="topic")
# 创建一个随机名称队列, exclusive=True:使用者链接关闭,队列就被删除
result = channel.queue_declare(queue="", exclusive=True)
# 接收消息的工作方式与前面的教程类似,只有一个例外——我们将为感兴趣的每个严重性创建一个新绑定。
queue_name = result.method.queue
channel.queue_bind(exchange="topic_logs", queue=queue_name, routing_key=severities[-1])
channel.queue_bind(exchange="topic_logs", queue=queue_name, routing_key=sources[0])
def callback(ch, method, properties, body):
file_path = pathlib.Path(__file__).parent.joinpath("rabbit.log")
with open(file_path, "ab") as f:
f.write(body)
f.write("\n".encode())
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
点击查看代码
import urllib.parse
import pika
# *代表一个单词,#代表零个或多个单词
severities = ["*.info", "*.warning", "*.error"]
sources = ["kenel.*", "haha.*", "heihie.*"]
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
# 创建直接交换器
exchange = channel.exchange_declare(exchange="topic_logs", exchange_type="topic")
# 创建一个随机名称队列, exclusive=True:使用者链接关闭,队列就被删除
result = channel.queue_declare(queue="", exclusive=True)
# 接收消息的工作方式与前面的教程类似,只有一个例外——我们将为感兴趣的每个严重性创建一个新绑定。
queue_name = result.method.queue
for severity in severities:
channel.queue_bind(exchange="topic_logs", queue=queue_name, routing_key=severity)
for source in sources:
channel.queue_bind(exchange="topic_logs", queue=queue_name, routing_key=source)
def callback(ch, method, properties, body):
print(body.decode())
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
10. RPC模式
在第二个教程中,我们学习了如何使用工作队列在多个工作者之间分配耗时的任务。
但是,如果我们需要在远程计算机上运行一个函数并等待结果,该怎么办呢?那就另当别论了。此模式通常称为远程过程调用或 RPC。
- rpc_server.py
点击查看代码
import pathlib
import urllib.parse
import pika
def fib(n):
if n < 2:
return n
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,
properties=pika.BasicProperties(correlation_id=props.correlation_id),
body=str(response),
)
ch.basic_ack(delivery_tag=method.delivery_tag)
if __name__ == '__main__':
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
connection = pika.BlockingConnection(pika.URLParameters(url))
channel = connection.channel()
channel.queue_declare("rpc_queue")
# 处理完一条在给我分配下一条
# 我们可能需要运行多个服务器进程。为了在多个服务器上平均分配负载,我们需要设置 prefet_ count 设置。
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='rpc_queue', on_message_callback=on_request)
channel.start_consuming()
点击查看代码
import pika
import uuid
import urllib.parse
class FibonacciRpcClient(object):
def __init__(self):
virtual_host = urllib.parse.quote_plus("/mayanan")
# url = amqp://username:password@host:port/<virtual_host>[?query-string]
self.connection = pika.BlockingConnection(
pika.URLParameters(url)
)
self.channel = self.connection.channel()
result = self.channel.queue_declare(queue='', exclusive=True)
self.callback_queue = result.method.queue
self.channel.basic_consume(
queue=self.callback_queue,
on_message_callback=self.on_response,
auto_ack=True,
)
self.response = None
self.corr_id = None
def on_response(self, ch, method, props, body):
if self.corr_id == props.correlation_id:
self.response = body
def call(self, n):
self.response = None
self.corr_id = str(uuid.uuid4())
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))
self.connection.process_data_events(time_limit=None)
return int(self.response)
fibonacci_rpc = FibonacciRpcClient()
response = fibonacci_rpc.call(10)
print(response)