模式盘点

各种模式详解

模式之间的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──────────────┐
│ 并发模式 │ ← 线程/任务
└──────────────┘

┌──────────────┐
│ 架构模式 │ ← 模块/职责
└──────────────┘

┌──────────────┐
│ 消息模式 │ ← 进程通信
└──────────────┘

┌──────────────┐
│ 企业集成模式 │ ← 企业级可靠性
└──────────────┘

并发模式

模式 描述 应用场景
生产者消费者 缓冲队列解耦生产者和消费者 任务队列、日志处理
读写锁 读共享,写互斥的锁机制 缓存系统、配置管理
线程池 复用线程,避免频繁创建销毁 Web服务器、数据库连接池
领导者追随者 一个线程处理事件,其他线程等待 高性能网络服务器
反应器模式 事件驱动,多路复用I/O Nginx、Netty、Node.js
主动对象 异步方法调用,分离方法调用和执行 异步任务处理
屏障模式 同步多个线程的执行点 并行计算、分阶段处理
双重检查锁定 减少同步开销的延迟初始化 单例模式实现

架构模式

模式 描述 应用场景
分层架构 系统分为多层,每层有明确职责 传统企业应用
MVC/MVVM 分离数据、视图、控制逻辑 Web应用、桌面应用
微服务架构 小型、自治的服务组成系统 大型分布式系统
事件驱动架构 组件通过事件通信,松耦合 实时系统、消息系统
管道过滤器 数据流经一系列处理器 编译器、ETL工具
CQRS 分离命令和查询职责 复杂业务系统
六边形架构 业务逻辑为核心,适配器处理外部交互 领域驱动设计
服务网格 基础设施层处理服务间通信 云原生应用

消息模式

模式 描述 应用场景
发布订阅 消息发送给所有订阅者 事件通知、广播
点对点队列 消息只被一个消费者处理 任务分发、负载均衡
请求-响应 同步等待回应的通信 RPC调用、API调用
管道过滤器 消息流经处理链 数据处理管道
消息路由器 根据内容路由到不同目标 消息分发、条件处理
消息转换器 转换消息格式 系统集成
竞争消费者 多个消费者竞争处理消息 负载均衡
消息聚合器 收集相关消息后统一处理 批量处理

工程实例

  • MQTT
  • RabbitMQ
  • ZeroMQ
  • Redis Pub/Sub

发布订阅模式

image-20241004232138161

在软件架构中,这个模式通常包含三个部分:

  1. 发布者 (Publisher):产生消息或事件的对象。它把消息发送给“调度中心”,而不直接发给具体的接收方。
  2. 调度中心/消息代理 (Broker / Event Bus / Message Queue):这是最重要的中间件。它维护一份“谁订阅了什么话题”的清单。当收到发布者的消息时,它负责把消息分发给所有订阅了该话题的订阅者。
  3. 订阅者 (Subscriber):对某类消息感兴趣的对象。它向“调度中心”注册自己,告诉中心:“如果有关于‘体育’的消息,请通知我。”

它解决了什么问题?(核心优势)

最主要的作用是 解耦 (Decoupling)

  • 空间解耦:发布者和订阅者不需要知道对方的IP地址或存在。
  • 时间解耦:发布者发消息时,订阅者甚至可以不在线(如果配合消息队列使用)。
  • 可扩展性:如果你想增加一个新的功能(例如:有人下单后,不仅要发邮件,还要发短信),你只需要新增一个“短信订阅者”即可,完全不需要修改“下单发布者”的代码。

案例代码

C++为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <string>

using namespace std;

// 1. 调度中心 (Broker)
class EventBus {
public:
// 定义通用的回调函数签名:接收一个 string 消息
using Handler = function<void(const string&)>;

// 订阅:将 topic 和回调函数绑定
void Subscribe(const string& topic, Handler handler) {
subscribers[topic].push_back(handler);
}

// 发布:找到 topic 对应的所有回调并执行
void Publish(const string& topic, const string& message) {
// 如果没人订阅这个 topic,直接跳过
if (subscribers.find(topic) == subscribers.end()) return;

for (auto& handler : subscribers[topic]) {
handler(message);
}
}

private:
// 核心数据结构:Topic -> 回调函数列表
unordered_map<string, vector<Handler>> subscribers;
};

// --- 使用示例 ---
int main() {
EventBus bus;

// 2. 订阅者 (使用 Lambda 表达式模拟)
bus.Subscribe("login_event", [](const string& msg) {
cout << "[日志模块] 记录登录: " << msg << endl;
});

bus.Subscribe("login_event", [](const string& msg) {
cout << "[UI模块] 弹窗欢迎: " << msg << endl;
});

bus.Subscribe("logout_event", [](const string& msg) {
cout << "[清理模块] 清除缓存: " << msg << endl;
});

// 3. 发布者
cout << "--- 触发登录 ---" << endl;
bus.Publish("login_event", "User_001");

cout << "\n--- 触发登出 ---" << endl;
bus.Publish("logout_event", "User_001");

return 0;
}

代码解析 (针对 C++ 特性)

  1. std::unordered_map<string, vector<Handler>>:
    • 这是发布订阅模式的灵魂。它是一个字典,Key 是“话题”,Value 是“一群等待通知的人”
    • 逻辑:如果是 Map["A"],里面存的就是所有关心话题 A 的函数指针列表。
  2. std::function<void(const string&)>:
    • 作用:类型擦除
    • 它让你可以把普通函数、静态函数、甚至 Lambda 表达式都当成同一个东西存进 vector 里。这对于 C++ 实现回调至关重要(类似于你熟悉的 C# Action<string>
  3. Lambda 表达式 [](...) { ... }:
    • 允许我们在 main 函数里直接定义订阅者的行为,而不需要专门去写一个 Subscriber 类,极大地精简了代码

MQTT

MQTT实现双向通信的关键:

  1. 每个客户端都有双重身份:既是发布者,又是订阅者
  2. 通过主题(topic)路由:客户端订阅感兴趣的主题
  3. Broker负责转发:不关心谁发布,只根据主题转发
  4. 客户端可以互相通信

企业集成模式

类别 模式 描述
消息构造 命令消息、事件消息、文档消息 定义消息类型和内容
消息路由 内容路由器、消息过滤器、动态路由器 控制消息流向
消息转换 消息转换器、信封包装器、内容丰富器 转换消息格式和内容
消息端点 消息网关、服务激活器、轮询消费者 连接应用和消息系统
系统管理 消息存储、智能代理、测试消息 监控和维护

Kafka / ESB / 企业总线都在这里

分布式系统模式

  • 一致性模式:2PC、Paxos、Raft
  • 缓存模式:写穿、写回、旁路缓存
  • 容错模式:熔断器、重试、限流、降级
  • 服务发现:客户端发现、服务端发现

云设计模式

  • 大使模式(Ambassador)
  • 挎斗模式(Sidecar)
  • 后端聚合(Backends for Frontends)
  • 重试模式
  • 健康端点监控

反模式(要避免的)

  • 大泥球(Big Ball of Mud)
  • 上帝对象(God Object)
  • 过早优化
  • 循环依赖
  • 硬编码