示例工程【免费下载链接】awesome-low-level-designLearn Low Level Design (LLD) and prepare for interviews using free resources.项目地址https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design点击查看免费下载导读本文以 solutions/cpp/pubsubsystem 目录下的 C 实现为核心系统讲解如何设计一个支持多发布者、多订阅者、按主题路由消息的发布-订阅Pub-Sub系统。你将掌握 Message、Topic、Subscriber、PubSubSystem 四个核心类的职责划分与调用链看懂基于std::vector的主题订阅管理、订阅者消息队列与去重逻辑并拿到一份可直接编译运行、验证订阅/退订/发布/离线不接收完整闭环的 Demo。这套实现思路正是低层设计LLD面试中消息中间件题目的高频考点。一、需求背景LLD 面试中的 Pub-Sub 经典题本仓库的原始问题定义见 problems/pub-sub-system.md其核心需求可归纳为六条系统允许发布者publisher向特定主题topic发布消息订阅者subscriber可以订阅感兴趣的主题并收到发布到该主题的消息系统需要支持多个发布者与多个订阅者消息应以实时real-time方式投递给主题的全部订阅者系统需要处理并发访问并保证线程安全消息投递要具备可扩展性与效率。在这份需求中实时投递给全部订阅者是 Pub-Sub 区别于点对点队列的核心语义——发布者与订阅者解耦发布者无需知道谁在收听。C 实现对该语义的落地方式是每个订阅者维护一个独立的消息队列std::vectorMessage发布动作将消息同步写入主题内所有订阅者的队列。这种队列内收件箱模型是后续分析全部源码的主线。二、整体架构与核心类职责在 solutions/cpp/pubsubsystem 目录下实现被拆成四个基础类与一个演示入口共 8 个源文件文件角色Message.hpp / Message.cpp消息载体携带主题、内容与时间戳Topic.hpp / Topic.cpp主题实体维护订阅者列表并负责广播Subscriber.hpp / Subscriber.cpp订阅者实体持有个人消息队列PubSubSystem.hpp / PubSubSystem.cpp门面控制器统一管理主题、订阅者与发布流程PubSubDemo.cpp演示程序串联完整业务流程从源码结构看PubSubSystem是整个系统的门面Facade所有对外操作建主题、加订阅者、订阅/退订、发布都收敛到它身上而具体的订阅集合维护与广播逻辑下沉到Topic消息队列维护下沉到Subscriber。职责边界清晰系统级谁在哪个主题由 PubSubSystem 通过两张std::vector指针表管理主题级订阅了谁由 Topic 管理订阅者级收到了什么由 Subscriber 管理。需要说明的是仓库 README.md 与 problems/pub-sub-system.md 中的英文原题描述面向 Java使用ConcurrentHashMap与ExecutorService而本文聚焦的 C 实现则是该题目在同一仓库内的多语言落地方案之一另有 Java、Python、C#、Go 等实现完整清单见 problems/pub-sub-system.md 的 Implementations 一节。C 版基于标准库std::vector、std::find_if与手写的Message/Subscriber类完成了同构设计因此概念可以一一对应迁移。三、逐类源码剖析3.1 Message消息载体Message.hpp 定义的Message类包含三个字段与四个公开方法class Message { private: std::string topic; // 消息所属主题 std::string content; // 消息正文 std::time_t timestamp; // 发布时间Unix 时间戳 public: Message(std::string topic, std::string content); std::string getTopic() const; std::string getContent() const; std::time_t getTimestamp() const; void displayInfo() const; };构造函数在 Message.cpp 中自动写入timestamp std::time(nullptr)即消息一旦构造便冻结发布时间之后无法修改——这保证了消息的不可变性immutability避免了投递过程中被篡改。displayInfo()依次打印主题、内容与格式化时间供订阅者回放消息时使用。3.2 Topic主题与广播中枢Topic.hpp 中Topic持有std::vectorSubscriber* subscribers订阅者指针集合、名称与描述、以及一个active状态位。四个关键方法addSubscriberTopic.cpp先判空再用std::find做去重——同一个订阅者重复调用订阅不会产生重复条目removeSubscriberTopic.cpp按subscriber-getId()用std::find_if定位并从 vector 中erasepublishMessageTopic.cpp若主题active为 false 则直接丢弃否则遍历 subscribers 列表逐个调用subscriber-receiveMessage(message)完成一次对全体订阅者的广播setActive支持将主题整体下线用于模拟主题暂停服务的场景。可以看到广播语义是同步的发布者调用publishMessage时所有订阅者队列会立即被写入。这是实时投递需求在单线程模型下的直接实现——以牺牲发布端阻塞为代价换取投递的即时性。3.3 Subscriber订阅者与个人收件箱Subscriber.hpp 定义的Subscriber拥有id系统生成的唯一标识、name、messageQueuestd::vectorMessage与active状态位。核心行为receiveMessageSubscriber.cpp只有在订阅者处于 active 状态时才将消息追加进队列——这是离线订阅者不接收消息语义的实现若订阅者已被标记为 inactive消息会被静默丢弃displayMessages遍历队列逐条调用Message::displayInfo()模拟订阅者消费消息clearMessages清空队列可理解为消费完成后释放内存displayInfo打印订阅者名称、ID、活跃状态与待消费消息数messageQueue.size()。这个每人一队列的设计与经典 Pub-Sub 的 push 模式吻合系统主动把消息推到订阅者的收件箱订阅者按需回放而不是主动去拉取。3.4 PubSubSystem门面控制器PubSubSystem.hpp 是系统的心脏内部维护std::vectorTopic* topics; // 全部主题 std::vectorSubscriber* subscribers; // 全部订阅者 int subscriberIdCounter; // 订阅者 ID 自增计数器对外 API 与内部私有方法的分工如下表公开方法职责底层实现createTopic(name, description)创建主题先findTopic查重重名返回nullptr否则new Topic入表removeTopic(name)删除主题find_if定位后delete并eraseaddSubscriber(name)注册订阅者自增生成SUB{n}格式 IDnew Subscriber入表removeSubscriber(id)注销订阅者先遍历所有主题移除其订阅再从订阅者表删除subscribe(id, name)订阅主题两表同时查找到位才topic-addSubscriber失败返回 falseunsubscribe(id, name)退订主题定位主题后topic-removeSubscriberpublish(name, content)发布消息主题存在且isActive()才构造Message并广播displayTopics()/displaySubscribers()状态查看遍历调用各对象的displayInfo几个值得注意的工程细节ID 生成PubSubSystem.cppSUB std::to_string(subscriberIdCounter)从 1 开始自增保证注册顺序与 ID 单调递增注销的级联清理PubSubSystem.cppremoveSubscriber先把该订阅者从每一个主题的订阅列表中摘除避免主题持有悬垂指针这是本实现中最容易遗漏但至关重要的内存安全步骤发布前置校验PubSubSystem.cpp主题不存在或!topic-isActive()时publish返回 false调用方可以据此判断发布失败内存管理析构函数PubSubSystem.cpp遍历两张表逐一delete与上面所有new严格配对无泄漏也无重复释放。从源码结构可以推断findTopic与findSubscriber均采用线性扫描std::find_if因此时间复杂度为 O(n)。在订阅者规模较小时完全够用若追求规模化可像题目描述的 Java 版本那样改用哈希表如std::unordered_map存储主题将查询降到 O(1)这也是需求第 6 条可扩展与效率的优化方向。四、端到端 Demo 与运行验证PubSubDemo.cpp 是一个完整的可运行示例它演示了这条主链路建主题创建Technology、Sports、Weather三个主题注册订阅者添加 John、Alice、Bob 三个订阅者建立订阅关系John 订阅 Technology 与 WeatherAlice 订阅 SportsBob 订阅 Technology 与 Sports发布消息向三个主题各发一条消息回放验证分别打印三个订阅者的消息队列确认每个订阅者只收到自己订阅主题的消息退订验证John 从 Weather 退订再向 Weather 发布Storm warning!打印 John 的队列确认其未收到新消息。第 3 步的订阅矩阵示意如下订阅者TechnologySportsWeatherJohn (SUB1)✅—✅随后退订Alice (SUB2)—✅—Bob (SUB3)✅✅—运行时displaySubscriberMessages会输出类似这样的回放结果John 的队列中只有 Technology 消息与退订前的Weather 消息退订后再发布的Storm warning!不会出现在 John 的队列里。同时Demo 还用std::this_thread::sleep_for(std::chrono::seconds(1))模拟时间流逝让Message::displayInfo()打印的时间戳更真实——说明发布与消费在时间上可以是异步发生的尽管本实现内部投递是同步的。编译与运行方式需要 C11 或更高标准因为代码使用了std::to_string、std::find_if与 lambda 表达式# 将上述 8 个源文件4 个 .hpp 4 个 .cpp置于同一目录 g -stdc11 -pthread PubSubDemo.cpp PubSubSystem.cpp Topic.cpp Subscriber.cpp Message.cpp -o pubsub_demo ./pubsub_demo说明-pthread是为 Demo 中std::this_thread::sleep_for所需的线程库链接部分编译器在指定-stdc11时已隐含但显式加上最稳妥。若你想自行验证离线不接收语义可以在订阅前调用sub-setActive(false)再发布消息观察该订阅者队列始终为空。五、线程安全与扩展性从单线程到并发需求第 5 条明确要求处理并发访问并保证线程安全。需要如实指出当前 C 实现本身是单线程模型其线程安全的保障主要来自两个方面数据封装与单一入口所有对主题、订阅者集合的修改都经由PubSubSystem门面类完成只要调用方保证对PubSubSystem实例的访问是串行的或外加互斥锁内部std::vector就不会出现数据竞争消息不可变Message构造后字段不可改广播时传递 const 引用多个订阅者共享同一份内容不会产生写冲突。对照题目原描述见 problems/pub-sub-system.mdJava 参考实现是通过ConcurrentHashMap存储主题、用ExecutorService异步投递来满足并发与实时性要求的。若要在 C 版上做同样的升级从源码结构看有两条明确路径存储层将std::vectorTopic*换成std::unordered_mapstd::string, Topic*并把subscribe/unsubscribe/publish的查改操作包进std::mutex或使用std::shared_mutex区分读写即可获得 O(1) 查询 并发安全投递层把Topic::publishMessage的同步循环改为向线程池如std::async或第三方线程池提交投递任务实现发布者不等投递完成的异步实时推送——这正是原题中ExecutorService的角色也是 Kafka 等生产级消息系统读写分离、异步刷盘思想的微缩模型。对 LLD 面试而言能讲清当前实现同步投递、如何改造成异步并发这条演进路线比直接堆砌并发代码更有说服力。六、设计要点总结职责分层Message只管数据、Subscriber只管个人收件箱、Topic只管订阅集合与广播、PubSubSystem只管编排是典型的分层设计解耦语义发布者只面向主题说话从不直接引用订阅者天然满足 Pub-Sub 的发布/订阅解耦去重与容错Topic::addSubscriber自带去重PubSubSystem对主题重名、订阅目标不存在等异常路径均返回nullptr/false接口可安全调用内存安全注销订阅者时级联从所有主题摘除析构函数统一delete指针生命周期管理闭环演进方向哈希表存储 读写锁 异步投递是这份单线程骨架走向并发可扩展系统的三步升级路径。七、延伸阅读问题定义与全部语言实现入口problems/pub-sub-system.md本仓库 Pub-Sub 题目对应的 UML 类图class-diagrams/pubsubsystem-class-diagram.png位于仓库根目录 class-diagrams 下与本文类职责划分一一对应其他语言的同题实现Javasolutions/java/src/pubsubsystem/、Pythonsolutions/python/pubsubsystem、C#solutions/csharp/pubsubsystem/、Gosolutions/golang/pubsubsystem/设计模式视角本实现的门面控制器与 design-patterns 目录中的 Facade 模式思路一致可作为对照阅读赞分享示例工程【免费下载链接】awesome-low-level-designLearn Low Level Design (LLD) and prepare for interviews using free resources.项目地址https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design点击查看免费下载相关推荐Encore事件驱动设计Pub/Sub消息队列的类型安全实现Encore事件驱动设计Pub/Sub消息队列的类型安全实现 1. 事件驱动架构的现代挑战 在分布式系统开发中事件驱动架构Event Driven Arc后端开发工具云原生微服务从OGB到CraftaxStable-Worldmodel支持的多样化环境生态完整指南从OGB到CraftaxStable Worldmodel支持的多样化环境生态完整指南 Stable Worldmodel是一个专为可重复世界模型研究和评估设人工智能强化学习模型评测机器人G-Helper 完整指南华硕笔记本三大核心控制与避坑验收清单G Helper 完整指南华硕笔记本三大核心控制与避坑验收清单 每次开机 Armoury Crate 都要转很久、后台常驻几百 MB而你想要的只是 切个模式桌面应用系统编程上一篇System Informer 系统监控完整实战手册从源码构建 4 步跑起来 4 个常见坑下一篇Duktape 2.x 中恢复 CommonJS 模块加载module-duktape 兼容框架集成与源码解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考