消息队列的协作模式

复制代码
综述:消息队列的协作模式分如下2种:
    (1) 只有一个主题的推拉模式。只有一个主题,消息生产者将消息推送到(一个或多个)队列中,消息消费者从队列中拉取消息进行处理。
    (2) 发布/订阅模式。会有多个主题。消息被发送到一个主题,多个消费者订阅该主题,并从对应的队列中接收消息。

一、只有一个主题的推拉模式举例
python 复制代码
from abc import ABC
import threading
import queue
import copy
import time

#生产者/消费者基类
class BaseComponent(ABC):
    def __init__(self):
        self._producers = []
        self._consumers = []

    def bind_producer(self, upstream):
        self._producers.append(upstream)

    def bind_consumer(self, downstream):
        self._consumers.append(downstream)

class Send_Recv(BaseComponent):
    def __init__(self):
        super().__init__()
        self._work_flag = None

    def start(self):
        self._work_flag = True

    def send_msg(self, msg):
        pass

    def stop(self):
        self._work_flag = False

class SendConsumer(BaseComponent):
    def __init__(self):
        super().__init__()
        self._send_thread = None
        self._run_flag = None

    def start(self):
        self._run_flag = True
        self._send_thread = threading.Thread(name="MainThread", target=self._main_process)
        self._send_thread.start()

    def _main_process(self):
        while self._run_flag:
            data=[0x02]
            received_data = self._producers[0].send_msg(data)
            for consumer in self._consumers:
                consumer.consume(received_data)

    def consume(self, msg):
        #消费数据
        pass

    def stop(self) -> None:
        self._run_flag = False

class DataParseConsumer_1(BaseComponent):
    def __init__(self):
        super().__init__()
        self._data_queue = queue.Queue()

    def consume(self, raw_data):
        #把数据放在缓存队列中
        self._data_queue.put(copy.copy(raw_data))
        pass

    def _get_data(self):
        while self._run_flag:
            try:
                data = self._data_queue.get(block=False)
            except queue.Empty as e:
                time.sleep(0.001)
                continue
            # 处理data

    def start(self):
        self._run_flag = True
        super().start()
        pass

    def stop(self):
        super().stop()

class DataParseConsumer_2(BaseComponent):
    def __init__(self):
        super().__init__()
        self._data_queue = queue.Queue()

    def consume(self, raw_data):
        #把数据放在缓存队列中
        self._data_queue.put(copy.copy(raw_data))
        pass

    def _get_data(self):
        while self._run_flag:
            try:
                data = self._data_queue.get(block=False)
            except queue.Empty as e:
                time.sleep(0.001)
                continue
            # 处理data

    def start(self):
        self._run_flag = True
        super().start()
        pass

    def stop(self):
        super().stop()

def bind_producer_and_consumer(producer, consumer):
    producer.bind_consumer(consumer)
    consumer.bind_producer(producer)

#生成生产者/消费者对象
data_parse_consume_1 = DataParseConsumer_1()
data_parse_consume_2 = DataParseConsumer_2()
p = Send_Recv()
p_c = SendConsumer()

#关联生产者/消费者对象
bind_producer_and_consumer(p, p_c)
bind_producer_and_consumer(p_c, data_parse_consume_1)
bind_producer_and_consumer(p_c, data_parse_consume_2)
复制代码
上述举例中,对于p_c来说,data_parse_consume_1和data_parse_consume_2都是消费者,属于同一个主题。
p_c的_consumers有同属于一个主题的两个消费者,即[data_parse_consume_1, data_parse_consume_2]。

二、下面举例两个主题的消息队列

python 复制代码
import threading
import time
import queue
from typing import Dict, List


class MessageBroker:
    """发布订阅消息代理中心:维护主题->订阅者队列映射"""
    def __init__(self):
        # key: topic主题, value: 该主题下所有订阅者的队列列表
        self._topics: Dict[str, List[queue.Queue]] = {}
        self._lock = threading.Lock()
        self._exit_sentinel = None  # 哨兵退出标记

    def subscribe(self, topic: str) -> queue.Queue:
        """订阅某个主题,返回该订阅者专属队列"""
        sub_q = queue.Queue()
        with self._lock:
            if topic not in self._topics:
                self._topics[topic] = []
            self._topics[topic].append(sub_q)
        return sub_q

    def unsubscribe(self, topic: str, sub_q: queue.Queue):
        """取消订阅"""
        with self._lock:
            if topic in self._topics and sub_q in self._topics[topic]:
                self._topics[topic].remove(sub_q)

    def publish(self, topic: str, msg):
        """发布消息:广播给该主题全部订阅者,每个订阅者队列放入一份消息副本"""
        with self._lock:
            if topic not in self._topics:
                return
            for q in self._topics[topic]:
                q.put(msg)

    def broadcast_exit(self, topic: str):
        """给指定主题所有订阅者发送退出哨兵消息,通知线程退出"""
        self.publish(topic, self._exit_sentinel)


class Publisher(threading.Thread):
    """发布者(生产者):面向对象封装,向指定主题发布消息"""
    def __init__(self, broker: MessageBroker, topic: str, publish_cnt: int, name: str):
        super().__init__()
        self.broker = broker
        self.topic = topic
        self.publish_cnt = publish_cnt
        self.name = name

    def run(self):
        for i in range(self.publish_cnt):
            msg = {"publisher": self.name, "data": f"data_{i}"}
            self.broker.publish(self.topic, msg)
            print(f"【{self.name}】发布主题[{self.topic}] → {msg}")
            time.sleep(0.3)
        print(f"==== {self.name} 发布完成 ====")


class Subscriber(threading.Thread):
    """订阅者(消费者):订阅主题,接收广播消息"""
    def __init__(self, broker: MessageBroker, topic: str, name: str):
        super().__init__()
        self.broker = broker
        self.topic = topic
        self.name = name
        self.sub_queue = broker.subscribe(topic)  # 订阅,拿到专属队列

    def run(self):
        while True:
            msg = self.sub_queue.get()
            # 读到哨兵,退出循环
            if msg is None:
                print(f"==== {self.name} 收到退出信号,结束订阅 ====")
                self.sub_queue.task_done()
                break
            print(f"【{self.name}】收到主题[{self.topic}]消息: {msg}")
            time.sleep(0.5)  # 模拟消费耗时
            self.sub_queue.task_done()

    def close(self):
        self.broker.unsubscribe(self.topic, self.sub_queue)


if __name__ == "__main__":
    # 1. 创建消息代理中心
    broker = MessageBroker()
    topic_name = "sensor_topic"

    # 2. 创建发布者(生产者)
    pub1 = Publisher(broker, topic=topic_name, publish_cnt=4, name="发布者‑A")

    # 3. 创建2个订阅者,订阅同一个主题;发布一条消息两个订阅者都会收到
    sub1 = Subscriber(broker, topic=topic_name, name="订阅者‑1")
    sub2 = Subscriber(broker, topic=topic_name, name="订阅者‑2")

    # 启动线程
    pub1.start()
    sub1.start()
    sub2.start()

    # 等待发布者全部消息发布完毕
    pub1.join()

    # 广播退出信号,通知该主题所有订阅者退出
    broker.broadcast_exit(topic_name)

    # 等待订阅者线程安全结束
    sub1.join()
    sub2.join()

    sub1.close()
    sub2.close()
    print("\n>>> 整个发布‑订阅程序全部结束")

broker是消息代理中心,其_topics可以有多个主题。

_topics={"主题1":订阅者1的queue1, 订阅者1的queue2, "主题2":订阅者3的queue3, 订阅者4的queue4}

相关推荐
君顾11 小时前
本地城市低空经济管理系统定制:城市级低空运营平台架构与落地实战
java·开发语言·飞手
苏苏susuus1 小时前
核密度估计(KDE)与高斯核(概念分享)
人工智能·python·ocr
宸津-代码粉碎机2 小时前
微服务线上踩坑复盘:接口超时、负载倾斜隐形问题根治方案(生产级配置)
java·大数据·人工智能·python·spring
速易达网络2 小时前
Python + Tkinter 实现基于 TCP/IP 的局域网聊天工具
python·信息与通信
多多鼠2 小时前
System Prompt 的“版本漂移”问题:从变更管理到 A/B 测试体系
开发语言·网络·人工智能·python·langchain
昔我往昔2 小时前
pytest日志问题排查记录
python·pytest
WiChP2 小时前
【V0.1B16】从零开始的2D游戏引擎开发之路
开发语言·算法·游戏引擎
Python自动化直播2 小时前
用 Python 爬虫给 AI 直播间做数据反馈机制
人工智能·python·ai·直播
孙启超3 小时前
【AI开发之Rust】第 8 课:泛型、trait 与 trait 对象
开发语言·后端·rust
努力学习的代码小白3 小时前
源码学习04
python