RabbitMQ 笔记一

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/duxiangwushirenfei/article/details/78422303

前言

过往使用一些简单队列功能,直接上redis,包括pub/sub也都可以使用redis完成简单功能。不过既然RabbitMQ作为消息队列非常成熟的组件,还是值得学习使用。演示环境Centos7,MacOS,所有演示代码可去github上下载demo code,演示代码匀为python实现,所用版本python 3.5

概述

简介

Wiki中给出的介绍是:RabbitMQ是实现了高级消息队列协议(AMQP) 的开源消息代理软件(亦称面向消息的中间件)。

AMQP – Advanced Message Queuing Protocol,一个提供统一消息服务的消息队列协议,属于应用层协议。

RabbitMQ官网

RabbitMQ安装

CentOS 7

CentOS 7中可以直接通过yum源安装

# 依赖 erlang等,如果不能自动安装,手动yum安装下各个依赖
yum install -y rabbitmq-server
systemctl startrabbitmq-server 

手动安装可以去RabbitMQ官网下载

wget https://github.com/rabbitmq/rabbitmq-server/releases/download/rabbitmq_v3_6_12/rabbitmq-server-3.6.12-1.el7.noarch.rpm

# 缺少依赖使用yum安装即可
rpm -i rabbitmq-server-3.6.12-1.el7.noarch.rpm

docker启动RabbitMQ

docker hub上直接有RabbitMQ的镜像,可以直接使用docker来启动RabbitMQ服务。并且docker RabbitMQ镜像有很多种版本,选用带有RabbitMQ官方提供管理界面的RabbitMQ镜像启动,docker-compose.yml文件内容如下:

services:
  rbmq:
    image: "rabbitmq:3-management"
    container_name: test_rbmq
    ports:
     - "15672:15672"
     - "5672:5672"
    environment:
     - RABBITMQ_DEFAULT_USER=tester
     - RABBITMQ_DEFAULT_PASS=test_password
    restart: always

直接启动

# docker启动RabbitMQ
docker-compose up -d

docker的相关内容可以参见之前的博文:docker 笔记一docker 笔记二
服务启动后,可以直接访问界面 http://localhost:15672/#/
实际效果:
这里写图片描述

注意:
1. 截图中 Ports and contexts其中rabbitmq:3-management镜像一个监听了3个端口,分别是5672,15672,25672。其中5672是RabbitMQ服务所监听端口,直接在机器上安装的rabbitmq-server也是通过这个端口进行消息接收发送,15672端口接受的http协议,是控制界面的访问端口。另一个在演示中未用到,因此docker-compose中并未将其开放出来。
2. docker-compose中设置了RabbitMQ的用户名密码,因此使用时必须附带上认证信息才能进行消息交互,后续代码演示中会体现。手动安装的没有设置,可以直接访问。

个人建议使用docker方式启动RabbitMQ,因为控制界面能够直观帮助我们理解RabbitMQ很多基础理念。

基础使用

RabbitMQ架构

Google图库中找了一个比较贴切的RabbitMQ架构图:

使用RabbitMQ必须要明白几个概念:

  • exchange:消息交换池,所有发送至RabbitMQ的消息都是发送至exchange中
  • queue:消息队列,所有消息的消费都是从queue中取得
  • binding:exchange中的消息,有binding确定路由关系,将池中消息送至相应的queue中
  • connection:producer和consumer与RabbitMQ连接,是tcp的长连接
  • channel:一个应用需要多个连接到RabbitMQ并非创建多个tcp长连接,而是一个tcp连接中多个channel(姑且理解为信道),共享一个tcp连接
    详细的原文解释请参见官网

demo1

python调用RabbitMQ需要安装pika包,演示使用版本是0.11.0。
生产者 producer.py 代码段如下:

import pika
import time

# 用户名密码获取认证信息,附带该信息方可成功连接RabbitMQ
credentials = pika.PlainCredentials('tester', 'test_password')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    'localhost', credentials=credentials))

# 一个connection中开启多个channel
channel = connection.channel()
channel2 = connection.channel()

# 声明队列
channel.queue_declare(queue='first_queue')

i = 0
while i < 5:
    time.sleep(1)
    a = '{} message'.format(i)
    channel.basic_publish(exchange='', routing_key='first_queue', body=a)
    i += 1

connection.close()

单独执行 producer.py,管理界面效果如下:
这里写图片描述
上图可以清晰看出各模块数据信息,点击队列名称可以看到队列详细信息。
消费者 consumer.py 代码段如下:

import pika
import time

# 消费函数
def callback(ch, method, properties, body):
    time.sleep(2)
    print("consumer_1 [x] Received %r" % (body,))
    # ch.basic_ack(delivery_tag=method.delivery_tag)

credentials = pika.PlainCredentials('tester', 'test_password')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    'localhost', credentials=credentials))

channel = connection.channel()

channel.queue_declare(queue='first_queue')

# 为匹配队列指定消费的回调函数
channel.basic_consume(callback, queue='first_queue', no_ack=True)

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

分别启动demo code default工程中consumer1 和 consumer2,在启动producer产生消息,运行结果如下:
这里写图片描述

5条消息分别被consumer1 和 consumer2 认领消费。RabbitMQ使用一种循环分发的机制,顺序将到来的message分给每个consumer,当负载增加时,只需添加更多的consumer来完成拓展即可。

消息确认

上面的demo中在队列声明时指定了no_ack=True,也就是说当消息被consumer认领后,就认为该message已经被消费,此时若consumer异常退出未能正常消费该message,那么此条message就丢了。为此可以在消费端声明使用队列时默认使用需要ack的方式。
修改consumer代码,删除no_ack=True,并且在consumer2修改callback函数,添加1/0,中断consumer2。执行程序如下:
这里写图片描述


注意
1. 如果consumer2消费后没有发送确认,那么consumer2所有处理过的message在consumer2退出后会被重新发送给其他consumer
2. callbcck函数编写其参数时回调式默认传入的四个参数,可以进入 channel.basic_consume 方法中查看,源码注释中已经给出详细解释。

消息持久化

上文的ack确认可以保证消息被正常消费,可是RabbitQM 服务如果在消费尚未进行出现停止,此时已进入RabbitQM 队列中的消息会如何?维持上文produecr代码并执行完,进入Queue管理界面此时能看到first_queue队列中存有5条message。重启RabbitQM后登陆管理界面 first_queue已丢失。
RabbitQM中持久化首先是队列的持久化,调整producer中声明队列代码:

# 声明持久化队列
channel.queue_declare(queue='first_queue', durable=True)

重复之前操作,此时队列first_queue在重启RabbitQM仍然存在,但是其中的消息没有了,已经做到了队列持久化。

在此调整producer中代码如下:

# 将发布的消息设置为持久化
channel.basic_publish(
        exchange='', routing_key='first_queue', body=a,
        properties=pika.BasicProperties(
            delivery_mode=2,  # make message persistent
        )
)

再次重复操作,重启RabbitQM后队列,消息均维持原样。

注意:
delivery_mode=2是一个常量设置,在paki包的spec.py中定义了BasicProperties类,并且定义了
TRANSIENT_DELIVERY_MODE = 1
PERSISTENT_DELIVERY_MODE = 2
两个常量用以区分短暂消息和持久消息。在RabbitQM官网中规定了delivery_mode 1和2分别指定的消息类型。

路由算法

exchange有三种类型,官网中给出了相应的解释,分别是:

  1. direct:routing_key匹配,exchange将消息发送给所有routing_key匹配的队列中,但不是重复发送,一个消息只会发送给所有匹配routing_key队列中的一个,此种模式多用于负载均衡的消费形式
  2. fanout:广播模式,类似网络广播一样所有属于该exchange中的队列都能收到
  3. topic:通过队列设定的routing_key pattern,exchange 将消息发送给所有匹配的对列,只要匹配,队列就会收到消息,这是其与direct不同之处,topic多用于pub/sub的功能场景。

direct demo

demo code 工程中分别执行两个consumer1,和一个consumer2,之后执行producer,执行结果如下:
这里写图片描述
是否觉得仿佛与之前说明的direct 并不相同?两个consumer1都接收到消息。

producer代码如下:

import pika
import time

credentials = pika.PlainCredentials('tester', 'test_password')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    'localhost', credentials=credentials))
channel = connection.channel()

channel.exchange_declare(exchange='direct_exchange', exchange_type='direct')

i = 0
while i < 5:
    time.sleep(1)
    a = '{} message'.format(i)
    if i < 2:
        channel.basic_publish(
            exchange='direct_exchange', routing_key='mini', body=a)
    else:
        channel.basic_publish(
            exchange='direct_exchange', routing_key='max', body=a)

    i += 1

connection.close()

consumer1中代码如下:

import pika
import time


def callback(ch, method, properties, body):
    time.sleep(2)
    print("consumer [x] Received %r" % (body,))
    ch.basic_ack(delivery_tag=method.delivery_tag)


credentials = pika.PlainCredentials('tester', 'test_password')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    'localhost', credentials=credentials))

channel = connection.channel()

channel.exchange_declare(exchange='direct_exchange', exchange_type='direct')

result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue

channel.queue_bind(
    exchange='direct_exchange', routing_key='mini', queue=queue_name)

channel.basic_consume(callback, queue=queue_name)

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

与之前的demo代码相比,producer端没有声明队列,而是将消息交exchange,而在consumer1中声明队列多了exclusive=True参数,表示队列跟consumer同步存在,consumer退出该队列也就随即消失。当启动多个consumer1时,实际产生了多个丢列,每个consumer都是消费自己队列中的,管理控制台显示:这里写图片描述

因此可以修改consumer1文件:


# 固定队列名称,去除exclusive=True参数,否则无法启动多个
channel.queue_declare(queue='mini_queue')

channel.queue_bind(
    exchange='direct_exchange', routing_key='mini', queue='mini_queue')

channel.basic_consume(callback, queue='mini_queue')

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

再次启动两个consumer1执行代码结果如下:
这里写图片描述

可以看出,两个consumer1是同时工作消费mini_queue队列中的消息,不会重复消费。

fanout demo

fanout是一种广播模式,所有处于广播模式exchange中的队列都会收到消息,类似上文方式,启动两个consumer1,一个consumer2,执行producer发送消息,结果如下:
这里写图片描述

此时 consumer1所用队列和consumer2所用队列均收到了5条message,并且consumer1启动了两个,同时消费其队列内容。具体代码内容请参见工程中示例,在管理控制界面,代码中所声明的 fanout_exchange 信息如下:
这里写图片描述

topic demo

主题形式的实用场景,诸如订阅微信公众号,参与群聊,都是此种形式的消息分发。topic类型的exchange中的routing_key是有一定规范写法的,两种特殊符号:

  • ‘*’:表示任意一个单词
  • ‘#’:表示0个或多个单词

producer代码如下:

import pika
import time

credentials = pika.PlainCredentials('tester', 'test_password')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    'localhost', credentials=credentials))
channel = connection.channel()

channel.exchange_declare(exchange='topic_exchange', exchange_type='topic')

routing_mapping = {
    0: 'tech.front',
    1: 'tech.end.t1',
    2: 'hr.g1.t1',
    3: 'hr.g2.t1',
    4: 'hr.g2.t2.',
}

i = 0
while i < 5:
    time.sleep(1)
    a = '{} message'.format(i)
    routing = routing_mapping[i % 5]
    channel.basic_publish(
        exchange='topic_exchange', routing_key=routing, body=a)
    i += 1

connection.close()

consumer1 代码如下:

import pika
import time


def callback(ch, method, properties, body):
    time.sleep(2)
    print("consumer [x] Received %r" % (body,))
    ch.basic_ack(delivery_tag=method.delivery_tag)


credentials = pika.PlainCredentials('tester', 'test_password')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    'localhost', credentials=credentials))

channel = connection.channel()

channel.exchange_declare(exchange='topic_exchange', exchange_type='topic')

result = channel.queue_declare(exclusive=True)

queue_name = result.method.queue

channel.queue_bind(
    exchange='topic_exchange', routing_key='*.*.t1', queue=queue_name)

channel.basic_consume(callback, queue=queue_name)

print('Consumer1 waiting for messages. To exit press CTRL+C')
channel.start_consuming()

consumer1 订阅了所有 第三级是 t1的路径下的消息,consumer2的 routing_key=’tech.#’表示订阅所有 一级 tech 下的全部消息,producer根据mapping表发送消息,启动consumer1,consumer2执行producer,结果如下:
这里写图片描述

小结

至此RabbitMQ的基础框架,各种消息分发形式,python如何使用RabbitMQ通信基本介绍完毕,高阶功能再行规整。

猜你喜欢

转载自blog.csdn.net/duxiangwushirenfei/article/details/78422303
今日推荐