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

  1. 安装erlang环境
    apt-get install erlang
  2. 检查是否安装成功
    erl
  3. 搜索apt下是否有 rabbitmq-server
    apt-cache search rabbitmq-server
  4. 安装
    apt-get install rabbitmq-server
  5. 查看rabbitmq-server的版本
    rabbitmqctl status | grep rabbit
  6. 从rabbitmq-server官网查看erlang与rabbitmq-server版本对应关系
    erlang与rabbitmq-server版本对应关系
  7. 查看rabbitmq-server的状态
    systemctl status rabbitmq-server
  8. 配置可视化管理后台插件
    rabbitmq-plugins enable rabbitmq_management
  9. 创建用户
    rabbitmqctl add_user admin 123456
  10. 用户设置为管理员
    rabbitmqctl set_user_tags admin administrator
  11. 设置读写权限
    rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
  12. 查看用户权限列表
    rabbitmqctl list_permissions
    更详细的用户权限列表参考文档
  13. 防火墙打开端口:15672,通过ip:15672访问我们的后台站点即可。
  14. 开放的端口
4369 epmd(Erlang Port Mapper Daemon),erlang服务端口
5672 //client端通信口 AMQP
15672 //后台管理系统端口
25672 节点间通信(Erlang分发服务器端口)
61613 //Stomp协议端口

5. rabbitmq-简单模式

在本教程的这一部分中,我们将用 Python 编写两个小程序; 一个是发送单个消息的生产者(发送者) ,另一个是接收消息并将其打印出来的消费者(接收者)。

  1. 安装依赖
    python -m pip install pika
  2. 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())

  1. 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. 发布-订阅模式

在上一个教程中,我们创建了一个工作队列。工作队列背后的假设是,每个任务只交付给一个工作者。在本部分中,我们将做一些完全不同的事情——我们将向多个消费者传递消息。这种模式称为“发布/订阅”。

  1. 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()

2. subscription.py, 订阅者1将接收到的日志消息存储到日志文件中
点击查看代码
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()

3. subscription_02.py,订阅者2将接收到的消息打印大终端显示
点击查看代码
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路由模式

在本教程中,我们将为其添加一个特性——我们将使仅订阅消息的一个子集成为可能。例如,我们将能够只将关键错误消息定向到日志文件(以节省磁盘空间) ,同时仍然能够在控制台上打印所有日志消息。
我们使用的是扇出交换机,这没有给我们太多的灵活性——它只能进行无意识的广播。
我们改用直接交换。直接交换背后的路由算法很简单——消息到其绑定键与消息的路由键完全匹配的队列。

  1. 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()

2. subscription.py
点击查看代码
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()
3. subscription_02.py
点击查看代码
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”的所有日志。
为了在我们的日志系统中实现这一点,我们需要了解更复杂的主题交换。

  1. 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()

  1. 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()

3. subscription_02.py
点击查看代码
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。

  1. 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()

2. client.py
点击查看代码
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)

rabbitmq官方文档

posted @ 2023-03-19 12:11  专职  阅读(89)  评论(0编辑  收藏  举报