消息中间件----rabbitmq单节点使用

window下启动与关闭

在安装目录下的sbin目录,打开终端;(也可以配置环境变量,就不用到sbin目录下)

  1. rabbitmqctl start_app; 开启rabbitmq-server
  2. rabbitmqctl status; 查看状态
  3. rabbitmqctl shutdown; 关闭服务
  4. rabbitmqctl --help 帮助信息
    也可以到浏览器中打开UI管理界面,地址http://localhost:15672/
    账号:guest
    密码:guest

 

python客户端

使用python语言作为客户端,开发消息队列,演示消息队列的使用方法。
在这里插入图片描述
python作为客户端,连接rabbitmq服务,需要安装相应的包,这里使用pika。

pip install pika

生产者&消费者模式

  • hello world模式
    在这里插入图片描述
    一个生产者、一个消费者。
# 生产者 producer.py
# __author__ = "laufing"
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties


# 认证证书
credentials = PlainCredentials("guest", "guest")
# 建立TCP连接
connection = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials, virtual_host="/"))
# 创建channel通道
channel = connection.channel()

# 声明交换机 (这里Hello World模式使用默认的交换机,不用声明)

# 声明 一个 队列
channel.queue_declare(queue="laufing")

# 发布消息
channel.basic_publish(exchange="", routing_key="laufing", properties=BasicProperties(correlation_id="q1-1"),
                      body="hello world")

# 关闭通道
channel.close()
# 关闭连接
connection.close()

下面是消费者代码:

# __author__ = "laufing"
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties


# 认证证书
credentials = PlainCredentials("guest", "guest")
# 建立TCP连接
connection = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials, virtual_host="/"))
# 创建channel通道
channel = connection.channel()

# 声明交换机 (这里Hello World模式使用默认的交换机,不用声明)

# 声明 一个 队列
channel.queue_declare(queue="laufing")


# 定义消息处理函数
def deal_func(ch, delivery, properties, body):
    """
    :param ch:  消费者的通道channel
    :param delivery: 转发信息
    :param properties: 消息属性
    :param body:  消息体
    :return:
    """
    print("channel:", ch, delivery)
    print("接收到的消息:", body) # 字节串


# 消费消息
channel.basic_consume(queue="laufing", on_message_callback=deal_func, auto_ack=True) # 自动确认容易丢失消息

# 开启消费循环
channel.start_consuming() # 一直监听队列

  • work queue模式
    一个生产者、一个队列、多个消费者
    在这里插入图片描述
    只需在HelloWorld模式下启动多个消费者即可。

这里生产者改用多线程模式:

# __author__ = "laufing"
import time
import sys
from threading import Thread, Lock
from pika import PlainCredentials, BlockingConnection, ConnectionParameters
from pika.adapters.blocking_connection import BlockingChannel


def func(ch: BlockingChannel, msg: str) -> None:
    # 声明队列
    ch.queue_declare(queue="q1")
    # 发布消息
    ch.basic_publish(exchange="", routing_key="q1", body=msg)
    ch.close()


# 多线程 建立多个channel ,公用一个TCP连接
def start_task(task_list: list) -> None:
    ths = [Thread(target=func, args=(ch, msg)) for ch, msg in task_list]
    for th in ths:
        # 设置守护线程   主线程终止,子线程不管有没有执行完,都立即停止
        th.setDaemon(True)
        th.start() # 子线程开始执行

    for th in ths:
        th.join() # 主线程(代码执行完毕后)等待所有的子线程执行结束


if __name__ == '__main__':
    # lock = Lock()
    credentials = PlainCredentials("guest", "guest")
    # 全局变量 TCP连接
    conn = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials,
                                                   virtual_host='/', heartbeat=0))
    # 创建三个channel 逻辑通道
    channels = [conn.channel() for i in range(3)]
    task_list = ["jack", "tom", "lucy"]
    start_task(zip(channels, task_list))

    # 主线程退出时才关闭连接
    try:
        while True:
            time.sleep(24*60*60)
    except KeyboardInterrupt:
        conn.close()
        sys.exit(0)

以上生产者启动报错:
在这里插入图片描述
在这里插入图片描述

这是因为pika是线程不安全的,三个子线程同时操作一个TCP连接的逻辑通道,即同时操作共享的数据,造成数据混乱。
解决

  1. 子线程加线程锁- Lock(); 可以解决,但子线程同步执行,无异步效果;
# 子线程中实现
lock.acquire()
.....
lock.release()
  1. 每个线程中创建TCP连接和channel (线程池 + 线程局部变量)
    在这里插入图片描述
# __author__ = "laufing"
import time
import sys
from threading import local, current_thread
from concurrent.futures import ThreadPoolExecutor
from pika import PlainCredentials, BlockingConnection, ConnectionParameters
from pika.adapters.blocking_connection import BlockingChannel


def init():
    # 每个线程中创建TCP连接 + channel,为避免频繁创建TCP连接,应避免频繁创建线程-->使用线程池
    print("创建tcp连接...")
    credentials = PlainCredentials("guest", "guest")
    conn = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials,
                                                   virtual_host='/', heartbeat=0))
    return conn.channel()


def func(msg: str) -> None:
    if not hasattr(local, "ch"):
        print("当前线程:", current_thread().ident)
        local.ch = init() # 子线程内部 创建TCP连接,并返回一个channel
    print("local: ", local.__dict__.keys())
    local.ch.queue_declare(queue="q1")
    local.ch.basic_publish(exchange="", routing_key="q1", body=msg)


if __name__ == '__main__':
    # 线程的局部变量 (为每个线程创建自己的变量)
    local = local()
	
    task_list = ["jack", "tom", "lucy", "laufing1", "laufing2", "laufing3"]

    # 创建线程池
    # 避免频繁的创建、销毁线程,这里只创建三个线程,循环利用
    # 来执行所有的任务
    pool = ThreadPoolExecutor(max_workers=3)
    for msg in task_list:
        pool.submit(func, msg)  # 提交任务,同时执行三个任务; 其他任务依次等待

    # 主线程退出时才关闭连接
    try:
        while True:
            time.sleep(24*60*60)
    except KeyboardInterrupt:
        pool.shutdown()  # 关闭线程池
        sys.exit(0)

 
消费者不做修改,打开三个终端,开启三次即有三个消费者。

# __author__ = "laufing"
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties


credentials = PlainCredentials("guest", "guest")
conn = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials,
                                               virtual_host='/', heartbeat=0))
channel = conn.channel()


channel.queue_declare(queue="q1")


# 消费消息的函数
def func(ch, delivery, prop, body):
	"""
		ch: 消费者的channel
		delivery: 消息转发的信息
		prop: 消息的属性
		body: 消息体  字节串
	"""
    print(ch is channel)
    print("消费的信息:", body)
	# ch.basic_ack(delivery_tag=delivery.delivery_tag)


channel.basic_consume(queue="q1", on_message_callback=func, auto_ack=True)
channel.start_consuming() # 循环监听

默认Broker 会依次均匀分发消息给所有的消费者,即轮询;
这对消费慢的程序不公平,可以关闭自动确认,采用手动确认,确认完成后再发另一个消息。

 

发布订阅模式

在这里插入图片描述

  • fanout广播

    • 生产者声明交换机及类型;
    • 消费者声明各自的队列、交换机,绑定队列与交换机;
    • 生产者发布消息给交换机,交换机广播消息到所有绑定该交换机的队列;

     
    生产者:

 # 声明交换机及类型;
channel.exchange_declare(exchange="e1", exchange_type="fanout")

# 发布消息
channel.basic_publish(exchange="e1", routing_key="", body=msg)

 
消费者1:

# __author__ = "laufing"
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties


# 连接
credentials = PlainCredentials("guest", "guest")
conn = BlockingConnection(ConnectionParameters(host="localhost", port=5672, credentials=credentials,
                                               virtual_host='/', heartbeat=0))
channel = conn.channel()

# 声明队列
channel.queue_declare(queue="q1")
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="fanout")

# 队列绑定交换机
channel.queue_bind(queue="q1", exchange="e1", routing_key="")


# 定义消费程序
def func(ch, delivery, prop, body):
    print(ch is channel)
    print("消费的信息:", body)


# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q1", on_message_callback=func, auto_ack=True)
channel.start_consuming()

 
消费者2:

# 核心代码
# 声明队列
channel.queue_declare(queue="q2")
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="fanout")

# 队列绑定交换机
channel.queue_bind(queue="q2", exchange="e1", routing_key="")


# 定义消费程序
def func(ch, delivery, prop, body):
    print(ch is channel)
    print("消费的信息:", body)


# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q2", on_message_callback=func, auto_ack=True)
channel.start_consuming()

 
测试结果:
在这里插入图片描述

 

  • direct定向,根据routing_key定向到指定的队列
    在这里插入图片描述
    生产者:
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="direct")
channel.basic_publish(exchange="e1", routing_key="error", body=msg) # 根据routing_key 定向转发

消费者error队列:

# error_consumer.py
# 声明队列
channel.queue_declare(queue="q1")
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="direct")

# 队列绑定交换机
channel.queue_bind(queue="q1", exchange="e1", routing_key="error")


# 定义消费程序
def func(ch, delivery, prop, body):
    print(ch is channel)
    print("消费的信息:", body)


# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q1", on_message_callback=func, auto_ack=True)
channel.start_consuming()

消费者info队列:

# info_consumer.py
# 声明队列
channel.queue_declare(queue="q2")
# 声明交换机
channel.exchange_declare(exchange="e1", exchange_type="direct")

# 队列绑定交换机
channel.queue_bind(queue="q2", exchange="e1", routing_key="info")


# 定义消费程序
def func(ch, delivery, prop, body):
    print(ch is channel)
    print("消费的信息:", body)


# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q2", on_message_callback=func, auto_ack=True)
channel.start_consuming()

结果:
在这里插入图片描述
 

  • topic 根据routing_key定向转发, 这里的routing_key 是正则匹配模式,*匹配一个单词,#匹配0个以上。
    在这里插入图片描述
    生产者:
    生产者指定具体的routing_key
 # 声明交换机
channel.exchange_declare(exchange="e2", exchange_type="topic") # 
channel.basic_publish(exchange="e2", routing_key="wang.laufing", body=msg) # 根据routing_key 定向转发

消费者1:
消费者端进行正则匹配。

# 声明队列
channel.queue_declare(queue="q2")
# 声明交换机
channel.exchange_declare(exchange="e2", exchange_type="topic")

# 队列绑定交换机
channel.queue_bind(queue="q2", exchange="e2", routing_key="*.jack") # 


# 定义消费程序
def func(ch, delivery, prop, body):
    print(ch is channel)
    print("消费的信息:", body)


# 确认每次接收一个消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue="q2", on_message_callback=func, auto_ack=True)
channel.start_consuming()

消费者2:


# 声明队列
channel.queue_declare(queue="q1")
# 声明交换机
channel.exchange_declare(exchange="e2", exchange_type="topic") # 正则转发

# 队列绑定交换机
channel.queue_bind(queue="q1", exchange="e2", routing_key="*.laufing") # 消费者端 正则匹配

结果:
在这里插入图片描述
 
报错:改一个交换机的名字即可。
在这里插入图片描述
 

rpc模式

  • 这里生产者、消费者一般叫做客户端、服务端;
  • 客户端声明自己的接收响应的队列;
  • 服务端声明接收请求的队列;
  • 两者均使用默认的交换机;
    在这里插入图片描述
    生产者(客户端),发布的消息带有两个属性reply_to & correlation_id
    reply_to,表示服务器返回的响应要放入的队列;
    correlation_id,表示返回的响应是对应哪个请求的。
# __author__ = "laufing"
import uuid
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties


credential = PlainCredentials("guest", "guest")
connection = BlockingConnection(ConnectionParameters(host='localhost', port=5672,
                                                     credentials=credential, virtual_host="/"))
channel = connection.channel() # 创建逻辑通道
# 声明消费的队列(获取响应)
channel.queue_declare(queue="response")
# 发布一个消息(请求)
req_id = str(uuid.uuid4())
channel.basic_publish(exchange="", routing_key="request", properties=BasicProperties(reply_to="response",
                                                                                     correlation_id=req_id),
                      body="lauf")
response = None


def handle_response(ch, delivery, prop, body):
    # 判断是否 是自己的响应
    if prop.correlation_id == req_id:
        print("接收响应:", body.decode())
        global response
        response = body
        # 接收到响应  自主回复确认
        channel.basic_ack(delivery_tag=delivery.delivery_tag)


# 接收响应
channel.basic_consume(queue='response', on_message_callback=handle_response, auto_ack=False)

while response is None:
    # 没收到响应时, 一直处于保持通信状态
    connection.process_data_events()

消费者(服务端)

# __author__ = "laufing"
import uuid
from pika import PlainCredentials, BlockingConnection, ConnectionParameters, BasicProperties


cre = PlainCredentials("guest", "guest")
connection = BlockingConnection(ConnectionParameters(host='localhost', port=5672, virtual_host="/", credentials=cre))
channel = connection.channel()

# 声明接收请求的queue
channel.queue_declare(queue='request')


def handle_request(ch, delivery, prop, body):
    print("处理请求:", body)
    # 返回响应
    channel.basic_publish(exchange="", routing_key=prop.reply_to, properties=BasicProperties(correlation_id=prop.correlation_id),
                          body="你好" + body.decode())
    # 返回响应后 回复确认
    channel.basic_ack(delivery_tag=delivery.delivery_tag)


channel.basic_qos(prefetch_count=1) # 每次获取一个消息
channel.basic_consume(queue='request', on_message_callback=handle_request, auto_ack=False)
channel.start_consuming()

结果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
中间件是指位于应用程序和操作系统之间的软件层,用于协调不同应用程序之间的通信和数据传输。RabbitMQ是一种流行的开源消息中间件,它实现了高级消息队列协议(AMQP)。 RabbitMQ的主要特点包括: 1. 可靠性:RabbitMQ使用消息确认机制来确保消息的可靠传递。发送方可以收到关于消息是否成功发送到队列的确认信息,并且接收方可以发送确认消息来告知RabbitMQ已经成功处理了消息。 2. 灵活的消息路由:RabbitMQ支持多种消息路由方式,包括直接路由、主题路由和扇出路由。这使得开发人员可以根据具体需求将消息发送到不同的队列或交换机。 3. 高可用性:RabbitMQ支持集群模式,可以将多个节点组成一个集群,提供高可用性和负载均衡。 4. 消息持久化:RabbitMQ可以将消息持久化到磁盘,即使在服务器重启后也能保证消息的可靠性。 5. 多语言支持:RabbitMQ提供了多种编程语言的客户端库,包括Java、Python、C#等,方便开发人员在不同的平台上使用使用RabbitMQ时,你需要了解以下几个核心概念: 1. 生产者(Producer):负责发送消息到RabbitMQ。 2. 消费者(Consumer):负责从RabbitMQ接收消息并进行处理。 3. 队列(Queue):用于存储消息的容器,生产者将消息发送到队列,消费者从队列中接收消息。 4. 交换机(Exchange):用于接收生产者发送的消息,并将消息路由到一个或多个队列。 5. 绑定(Binding):用于将交换机和队列进行绑定,定义了消息的路由规则。 6. 路由键(Routing Key):生产者在发送消息时指定的关键字,用于交换机将消息路由到相应的队列。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

laufing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值