微信月活跃用户超过 13 亿。每天有超过 450 亿条消息在这个系统中流转,峰值时每秒的消息量达到数千万级别。这个数字背后,是一套从移动网络层到存储层、从消息同步协议到支付系统的完整技术体系。
本文从微信的实际技术选型和公开技术分享出发,拆解微信在长连接管理、消息同步、朋友圈扩散、红包洪峰等场景下的架构设计。重点关注三个问题:移动端弱网环境下如何保持连接稳定,十亿级消息如何高效同步,以及春节红包这样的极端流量场景如何应对。
一、微信技术架构演进概览
微信的技术架构经历了三个明显的阶段。
1.1 第一阶段:快速上线(2011-2012)
微信 1.0 版本的核心目标是快速验证产品。技术栈选择了最成熟的方案:
- 后端使用 C++ 编写核心服务,部分业务逻辑使用 PHP
- 存储层以 MySQL 为主,消息存储在关系型数据库中
- 接入层使用自研的长连接网关
这个阶段的用户量级在千万级别,单机房架构能够支撑。
1.2 第二阶段:亿级扩展(2013-2016)
用户量突破 3 亿后,原有架构遇到了明显瓶颈:
- MySQL 存储消息的方案在写入吞吐上无法满足需求
- 单机房的网络带宽和故障域风险成为制约
- 移动端在弱网环境下的连接成功率不够理想
微信团队做了几项关键改造:
- 自研分布式存储系统,替代 MySQL 承载消息数据
- 部署上海和深圳双机房,实现同城双活
- 开发 Mars 网络库,统一移动端网络层
1.3 第三阶段:十亿级精细化(2017-至今)
用户量突破 10 亿后,架构优化的重点从”能不能撑住”转向”如何更高效”:
- 消息同步协议从拉模式(Pull)演进为推拉结合
- 引入自研的 KV 存储引擎 PaxosStore,基于 Paxos 协议保证数据一致性
- 微信支付独立为完整的金融级架构体系
- 小程序引入了独立的运行时和渲染引擎
下面这张图展示了微信当前架构的核心层次:
graph TB
subgraph 客户端层
iOS[iOS 客户端]
Android[Android 客户端]
Desktop[桌面客户端]
end
subgraph 接入层
LB[全球负载均衡]
LongConn[长连接网关]
ShortConn[短连接网关]
end
subgraph 业务逻辑层
MsgSvc[消息服务]
MomentSvc[朋友圈服务]
PaySvc[支付服务]
ContactSvc[通讯录服务]
GroupSvc[群聊服务]
AuthSvc[认证服务]
end
subgraph 基础服务层
Mars[Mars 网络库]
MsgSync[消息同步服务]
SeqSvr[序列号服务 SeqSvr]
IDGen[ID 生成器]
end
subgraph 存储层
PaxosStore[PaxosStore]
KVStore[KV 存储]
MySQL[(MySQL)]
HDFS[HDFS 文件存储]
end
iOS --> LB
Android --> LB
Desktop --> LB
LB --> LongConn
LB --> ShortConn
LongConn --> MsgSvc
LongConn --> MomentSvc
ShortConn --> PaySvc
MsgSvc --> MsgSync
MsgSync --> SeqSvr
MsgSvc --> PaxosStore
MomentSvc --> KVStore
PaySvc --> MySQL
ContactSvc --> PaxosStore
GroupSvc --> PaxosStore
SeqSvr --> KVStore
二、长连接架构与 Mars 网络库
移动端即时通信的核心挑战是:如何在复杂多变的移动网络环境下,保持一条稳定的长连接。
2.1 移动网络的现实困境
移动网络和固定宽带网络有本质区别:
- 网络切换频繁:用户在 Wi-Fi、4G、5G 之间切换,每次切换 IP 地址都会变化
- NAT 超时:运营商的 NAT 设备通常在 5-10 分钟内会回收空闲连接的端口映射
- 弱网高延迟:地铁、电梯等场景下,丢包率可能超过 30%,往返延迟(RTT)可能超过 2 秒
- DNS 劫持:部分运营商会劫持 DNS 解析,导致连接失败或被重定向
传统的 HTTP 长轮询在这种环境下表现很差:每次请求都需要完整的 TCP 握手和 TLS 协商,延迟和功耗都不可接受。
2.2 Mars 网络库的设计
Mars 是微信团队开源的跨平台网络库(https://github.com/Tencent/mars),专门针对移动场景设计。它包含四个核心模块:
Mars 架构
├── STN(Signal Transport Network):信令传输网络
│ ├── 长连接管理
│ ├── 短连接管理
│ └── 智能心跳
├── SDT(Signal Detection Tool):网络诊断
│ ├── 连通性检测
│ └── 网络质量评估
├── Comm:公共库
│ ├── 日志组件 xlog
│ ├── 线程模型
│ └── 平台适配层
└── Xlog:高性能日志
├── mmap 写入
└── 压缩加密
2.3 STN 的连接策略
STN(Signal Transport Network,信令传输网络)是 Mars 的核心模块,负责管理客户端与服务器之间的网络连接。
2.3.1 复合连接策略
STN 不依赖单一的连接方式,而是同时尝试多种策略:
// Mars STN 连接策略伪代码
class ConnectionManager {
public:
void startConnection() {
// 第一优先级:尝试长连接
// 长连接使用自定义二进制协议,端口 8080 或 443
tryLongConnection();
// 如果长连接在 5 秒内未建立,并行尝试短连接
scheduleTimeout(5000, [this]() {
if (!longConnEstablished_) {
tryShortConnection();
}
});
}
private:
void tryLongConnection() {
// 获取服务器 IP 列表(优先使用本地缓存)
auto ipList = getServerIPs();
// 并行发起多个 TCP 连接(竞速模式)
for (const auto& ip : ipList) {
asyncConnect(ip, 8080, [this](Socket* sock) {
if (!longConnEstablished_) {
longConnEstablished_ = true;
activateConnection(sock);
// 关闭其他并行连接
cancelOtherConnections();
}
});
}
}
void tryShortConnection() {
// 短连接走 HTTPS,兼容性最好
// 用于发送即时消息,不保持长连接
httpPost(shortConnUrl_, payload_);
}
std::vector<std::string> getServerIPs() {
// 三级 IP 获取策略
// 1. 本地缓存(上次成功连接的 IP)
auto cached = loadCachedIPs();
if (!cached.empty()) return cached;
// 2. HTTP DNS(绕过运营商 DNS 劫持)
auto httpDns = queryHTTPDNS();
if (!httpDns.empty()) return httpDns;
// 3. 系统 DNS(最后兜底)
return querySystemDNS();
}
bool longConnEstablished_ = false;
std::string shortConnUrl_;
std::string payload_;
};这个策略的核心思想是竞速:不等任何一种方式超时,而是让多种方式同时竞争,用最先成功的那条连接。
2.3.2 智能心跳机制
长连接建立后,需要定期发送心跳包来防止 NAT 超时。但心跳间隔的选择是一个权衡问题:
- 心跳太频繁 → 耗电量增加,流量浪费
- 心跳太稀疏 → 连接可能被 NAT 设备断开,消息推送延迟
Mars 使用了一个自适应心跳算法:
class SmartHeartbeat {
public:
// 心跳间隔范围:最短 60 秒,最长 300 秒
static constexpr int MIN_INTERVAL = 60; // 秒
static constexpr int MAX_INTERVAL = 300; // 秒
static constexpr int STEP = 30; // 步进值
int computeInterval() {
if (lastHeartbeatFailed_) {
// 上次心跳失败,回退到上一个成功的间隔
currentInterval_ = lastSuccessInterval_;
failureCount_++;
if (failureCount_ >= 3) {
// 连续失败 3 次,使用最小间隔
currentInterval_ = MIN_INTERVAL;
failureCount_ = 0;
}
} else {
// 心跳成功,记录当前间隔并尝试增大
lastSuccessInterval_ = currentInterval_;
failureCount_ = 0;
if (currentInterval_ + STEP <= MAX_INTERVAL) {
currentInterval_ += STEP;
}
}
return currentInterval_;
}
void onNetworkChange() {
// 网络环境变化时(Wi-Fi/4G 切换),重置心跳探测
currentInterval_ = MIN_INTERVAL;
lastSuccessInterval_ = MIN_INTERVAL;
failureCount_ = 0;
}
private:
int currentInterval_ = MIN_INTERVAL;
int lastSuccessInterval_ = MIN_INTERVAL;
int failureCount_ = 0;
bool lastHeartbeatFailed_ = false;
};算法的本质是二分探测:逐步增大心跳间隔,直到发现某个间隔会导致心跳失败,然后回退到上一个成功的值。不同运营商、不同网络类型的 NAT 超时时间差异很大,这种自适应方式比写死一个固定值要可靠得多。
2.4 Xlog 高性能日志
Mars 的日志模块 Xlog 解决了移动端日志的一个经典问题:如何在保证不丢日志的前提下,尽量减少对性能和电池的影响。
Xlog 的核心设计:
- 使用
mmap将日志文件映射到内存,写日志等价于写内存,极低延迟 - 即使进程崩溃,
mmap映射的数据也会被操作系统刷到磁盘,不会丢失 - 日志在写入前使用 Zlib 压缩 + 自定义加密,减少存储空间和保护用户隐私
// Xlog mmap 写入的简化实现
class MmapLogWriter {
public:
bool init(const std::string& logPath, size_t bufferSize) {
fd_ = open(logPath.c_str(), O_RDWR | O_CREAT, 0644);
if (fd_ < 0) return false;
// 预分配文件空间
ftruncate(fd_, bufferSize);
// mmap 映射
buffer_ = (char*)mmap(nullptr, bufferSize,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd_, 0);
if (buffer_ == MAP_FAILED) return false;
bufferSize_ = bufferSize;
writePos_ = 0;
return true;
}
void write(const char* data, size_t len) {
// 压缩数据
auto compressed = zlibCompress(data, len);
// 加密
auto encrypted = encrypt(compressed);
// 写入 mmap 缓冲区(等价于写内存)
if (writePos_ + encrypted.size() <= bufferSize_) {
memcpy(buffer_ + writePos_,
encrypted.data(), encrypted.size());
writePos_ += encrypted.size();
} else {
// 缓冲区满,切换到新文件
rotateLogFile();
}
}
private:
int fd_;
char* buffer_;
size_t bufferSize_;
size_t writePos_;
void rotateLogFile();
std::vector<char> zlibCompress(const char* data, size_t len);
std::vector<char> encrypt(const std::vector<char>& data);
};三、消息收发与存储模型
3.1 消息的基本流转路径
一条微信消息从发送到接收,经历以下步骤:
sequenceDiagram
participant A as 发送方客户端
participant GW as 长连接网关
participant MS as 消息服务
participant Seq as SeqSvr
participant Store as PaxosStore
participant NS as 通知服务
participant B as 接收方客户端
A->>GW: 发送消息(加密)
GW->>MS: 转发消息
MS->>Seq: 申请消息序列号
Seq-->>MS: 返回 sequence
MS->>Store: 写入消息(发送方信箱 + 接收方信箱)
Store-->>MS: 写入确认
MS-->>GW: 发送成功 ACK
GW-->>A: 确认发送成功
MS->>NS: 通知接收方有新消息
NS->>B: 推送通知(携带最新 sequence)
B->>MS: 拉取新消息(携带本地最大 sequence)
MS->>Store: 读取消息
Store-->>MS: 返回消息列表
MS-->>B: 返回消息数据
B-->>MS: 确认收到(ACK)
3.2 消息存储的信箱模型
微信的消息存储采用信箱模型(Mailbox Model),而不是传统即时通信系统常用的会话模型。
两种模型的区别:
| 维度 | 会话模型 | 信箱模型 |
|---|---|---|
| 存储结构 | 按会话存储,一个会话的所有消息在一起 | 按用户存储,每个用户有独立信箱 |
| 写入操作 | 一条消息写一份,存在会话中 | 一条消息写多份,分别写入发送方和接收方信箱 |
| 读取操作 | 查看会话消息需要按会话 ID 查询 | 查看自己的消息只需要扫描自己的信箱 |
| 多端同步 | 需要额外的同步机制来标记各端的读取位置 | 每个信箱独立维护 sequence,天然支持多端同步 |
| 群聊扩展 | 群消息只存一份,读取效率高 | 群消息写 N 份(N 为群成员数),写入放大 |
| 离线消息 | 需要单独的离线消息队列 | 信箱天然支持离线消息,上线后拉取信箱增量 |
| 消息删除 | 一方删除需要标记而非真删 | 各自信箱独立,删除操作互不影响 |
信箱模型的写入放大是明显的代价。一条群消息如果群里有 500 人,就需要写 500 份。微信通过以下方式缓解:
- 群消息的内容只存一份,信箱中存储的是消息的引用(消息 ID)
- 批量写入,将同一时间窗口内多条消息合并写入
- 写操作走异步流程,先写入本地日志,再异步同步到远端信箱
3.3 SeqSvr:序列号生成服务
消息的有序性依赖于 SeqSvr(Sequence Server,序列号服务器)。微信的 SeqSvr 有几个设计要点:
- 每个用户拥有独立的递增序列号,不同用户的序列号互不影响
- 序列号按段分配(Section),减少对存储的访问频率
- 使用两级存储:内存中缓存当前段,持久化存储段的最大值
/**
* SeqSvr 段分配策略的简化实现
* 每次从存储层分配一个步长为 10000 的段
* 段内的序列号分配在内存中完成,无需访问存储
*/
public class SeqAllocator {
private static final long SECTION_SIZE = 10000L;
private final ConcurrentHashMap<String, SeqSection> sections
= new ConcurrentHashMap<>();
private final PaxosStoreClient storeClient;
public SeqAllocator(PaxosStoreClient storeClient) {
this.storeClient = storeClient;
}
public long allocSeq(String userId) {
SeqSection section = sections.computeIfAbsent(
userId, k -> loadNewSection(k)
);
synchronized (section) {
if (section.current < section.max) {
return ++section.current;
}
// 当前段用完,加载新段
SeqSection newSection = loadNewSection(userId);
sections.put(userId, newSection);
return ++newSection.current;
}
}
private SeqSection loadNewSection(String userId) {
// 从存储层原子性地分配一个新段
// CAS 操作保证并发安全
long currentMax = storeClient.getAndAdd(
"seq:" + userId, SECTION_SIZE
);
SeqSection section = new SeqSection();
section.current = currentMax;
section.max = currentMax + SECTION_SIZE;
return section;
}
private static class SeqSection {
long current;
long max;
}
}段分配的好处是:只要段没用完,序列号的分配就是纯内存操作,延迟极低。一个用户一天发送的消息量通常不会超过 10000 条,所以大多数情况下一天只需要访问一次存储层。
四、消息同步协议(Diff-Sync)
4.1 同步问题的本质
微信需要解决的消息同步问题是:用户可能在手机、平板、PC 等多个设备上登录,每个设备的消息状态可能不一致。例如:
- 手机收到了一条新消息,但 PC 端还没收到
- 用户在手机上删除了一条消息,PC 端需要同步这个删除操作
- 用户长时间离线,重新上线后需要拉取所有未读消息
4.2 基于 Sequence 的增量同步
微信的消息同步协议可以概括为一句话:每个设备维护自己的最大 sequence,拉取时只请求比本地 sequence 大的消息。
这就是差异同步(Diff-Sync)的核心思想。具体流程如下:
sequenceDiagram
participant Device as 客户端设备
participant Server as 消息服务器
participant Store as 存储层
Note over Device: 本地最大 sequence = 1000
Server->>Device: 推送通知(最新 sequence = 1050)
Device->>Server: 同步请求(本地 sequence = 1000)
Server->>Store: 查询 sequence > 1000 的消息
Store-->>Server: 返回 50 条消息
Server-->>Device: 返回消息列表 + 最新 sequence = 1050
Note over Device: 更新本地 sequence = 1050
Note over Device: 一段时间后...
Server->>Device: 推送通知(最新 sequence = 1052)
Device->>Server: 同步请求(本地 sequence = 1050)
Server->>Store: 查询 sequence > 1050 的消息
Store-->>Server: 返回 2 条消息
Server-->>Device: 返回消息列表 + 最新 sequence = 1052
Note over Device: 更新本地 sequence = 1052
4.3 同步协议的关键细节
4.3.1 推拉结合
早期微信使用纯拉模式:客户端定期轮询服务器检查是否有新消息。这种方式延迟高、资源浪费大。改进后采用推拉结合的模式:
- 推:服务器通过长连接推送一个轻量通知,告知”有新消息了”,通知中携带最新的 sequence 值
- 拉:客户端收到通知后,主动向服务器拉取具体的消息内容
推送的通知非常小(只包含最新 sequence),真正的消息数据通过拉取获得。这种设计的好处是:
- 推送通道只需要传输极少的数据,即使推送偶尔丢失也不影响正确性
- 拉取操作是幂等的,可以安全重试
- 客户端可以控制拉取的时机和频率,避免被推送淹没
4.3.2 分页拉取
当用户长时间离线后重新上线时,可能有大量未读消息需要同步。一次性拉取所有消息会导致:
- 服务器压力大:一次查询返回大量数据
- 网络传输慢:大量数据在弱网环境下容易超时
- 客户端处理慢:一次性渲染大量消息会卡顿
微信对消息拉取做了分页处理:
class MessageSyncClient:
"""客户端消息同步逻辑"""
PAGE_SIZE = 20 # 每次拉取 20 条消息
def __init__(self):
self.local_seq = self.load_local_seq()
def on_new_message_notify(self, server_seq: int):
"""收到服务器推送的新消息通知"""
if server_seq <= self.local_seq:
return # 没有新消息
self.sync_messages(server_seq)
def sync_messages(self, target_seq: int):
"""增量同步消息,分页拉取"""
current_seq = self.local_seq
while current_seq < target_seq:
# 分页拉取
response = self.fetch_messages(
from_seq=current_seq,
limit=self.PAGE_SIZE
)
if not response.messages:
break
# 处理并存储消息
for msg in response.messages:
self.save_message_to_local_db(msg)
current_seq = max(current_seq, msg.sequence)
# 更新本地 sequence
self.local_seq = current_seq
self.save_local_seq(current_seq)
# 通知 UI 层刷新
self.notify_ui_update(response.messages)
def fetch_messages(self, from_seq: int, limit: int):
"""从服务器拉取消息"""
request = SyncRequest(
user_id=self.user_id,
last_seq=from_seq,
count=limit
)
return self.network_client.sync(request)
def load_local_seq(self) -> int:
"""从本地数据库加载最大 sequence"""
pass
def save_local_seq(self, seq: int):
"""持久化最新 sequence"""
pass
def save_message_to_local_db(self, msg):
"""存储消息到本地 SQLite 数据库"""
pass
def notify_ui_update(self, messages):
"""通知界面刷新"""
pass4.3.3 冲突处理
多端同步不可避免地会遇到冲突场景。微信的处理策略相对简单——最后写入胜出(Last-Writer-Wins,LWW)。由于消息是追加写入的,真正的冲突只发生在状态类操作上(比如消息撤回、已读标记)。对于这类操作,服务器端的 sequence 保证了全局顺序,各端按照 sequence 顺序重放操作即可。
五、朋友圈的读扩散架构
5.1 写扩散 vs 读扩散
社交信息流的架构有两种经典模式:
| 维度 | 写扩散(Fan-out on Write) | 读扩散(Fan-out on Read) |
|---|---|---|
| 发布时 | 将内容写入每个粉丝的收件箱 | 只写入发布者的发件箱 |
| 读取时 | 直接读取自己的收件箱,无需聚合 | 遍历所有关注者的发件箱,实时聚合 |
| 延迟 | 读取延迟低(预计算) | 发布延迟低,读取延迟高 |
| 存储 | 存储量大(写入放大) | 存储量小 |
| 适用场景 | 粉丝数少、读多写少 | 粉丝数多、写多读少 |
| 代表系统 | Twitter 早期 | 微信朋友圈 |
微信朋友圈选择了读扩散模式。原因是:
- 微信是双向好友关系,一个用户的好友数上限是 5000,不会出现百万粉丝的场景
- 朋友圈的浏览频率远低于消息收发,大部分用户不会频繁刷新朋友圈
- 写扩散在发布时需要写入所有好友的收件箱,5000 个好友就是 5000 次写入,对发布延迟影响大
5.2 朋友圈的存储结构
朋友圈的存储分为三部分:
- 内容表(Content Table):存储朋友圈的原始内容(文字、图片 ID、视频 ID 等),按发布者 ID 索引
- 评论表(Comment Table):存储评论和点赞,按内容 ID 索引
- 时间线索引(Timeline Index):存储每个用户的朋友圈时间线,按时间倒序排列
/**
* 朋友圈时间线的读扩散实现
*/
public class MomentTimelineService {
private final ContentStore contentStore;
private final ContactService contactService;
private final CommentStore commentStore;
private final CacheService cache;
/**
* 获取用户的朋友圈时间线
* 读扩散:实时聚合所有好友的朋友圈内容
*/
public List<MomentItem> getTimeline(
String userId, long beforeTime, int count) {
// 获取好友列表
List<String> friendIds = contactService.getFriends(userId);
friendIds.add(userId); // 包含自己
// 从缓存或存储中获取每个好友的最新内容
PriorityQueue<MomentItem> mergeHeap = new PriorityQueue<>(
(a, b) -> Long.compare(b.getCreateTime(), a.getCreateTime())
);
for (String friendId : friendIds) {
// 每个好友最多取 count 条(减少不必要的扫描)
List<MomentItem> items = contentStore.getByUser(
friendId, beforeTime, count
);
mergeHeap.addAll(items);
}
// 归并排序取 Top-N
List<MomentItem> result = new ArrayList<>();
while (!mergeHeap.isEmpty() && result.size() < count) {
MomentItem item = mergeHeap.poll();
// 权限过滤(检查是否对当前用户可见)
if (isVisible(item, userId)) {
// 加载评论和点赞(只加载共同好友的)
item.setComments(
commentStore.getVisibleComments(
item.getId(), userId
)
);
result.add(item);
}
}
return result;
}
private boolean isVisible(MomentItem item, String viewerId) {
// 检查朋友圈可见性设置
// 部分可见、不给谁看等逻辑
return true; // 简化
}
}5.3 朋友圈的缓存策略
读扩散的性能瓶颈在于每次刷新都需要聚合多个好友的数据。微信通过多级缓存来缓解:
- 客户端缓存:最近浏览过的朋友圈内容缓存在本地 SQLite 数据库中
- 更新通知:当有好友发布新朋友圈时,服务器推送一个轻量通知,客户端显示”有新动态”的红点
- 增量加载:客户端记住上次浏览的时间戳,只请求比这个时间更新的内容
- 服务端缓存:热门用户的朋友圈内容在服务端也有缓存层
六、微信支付架构
6.1 金融级系统的核心要求
微信支付从社交工具的附属功能,发展成为日均交易笔数超过 10 亿的支付平台。金融级系统与普通互联网系统有本质区别:
- 数据一致性要求极高:钱不能多也不能少,不允许出现重复扣款或重复到账
- 可用性要求极高:支付链路的任何中断都直接影响商户经营
- 审计和合规:每笔交易都需要完整的审计日志
6.2 支付核心链路
微信支付的核心链路包括以下步骤:
graph LR
subgraph 用户端
User[用户确认支付]
end
subgraph 支付网关
Gateway[支付网关]
Risk[风控引擎]
end
subgraph 支付核心
Order[订单服务]
Account[账户服务]
Channel[渠道路由]
end
subgraph 外部系统
Bank[银行系统]
ClearHouse[清算中心]
end
User --> Gateway
Gateway --> Risk
Risk -->|通过| Order
Risk -->|拒绝| User
Order --> Account
Account --> Channel
Channel --> Bank
Bank --> ClearHouse
6.3 幂等性设计
支付系统中最常见的事故是重复扣款。网络超时、客户端重试、服务端重启都可能导致同一笔支付请求被执行多次。微信支付通过幂等性设计来解决:
/**
* 支付幂等性处理
* 基于全局唯一的交易号保证同一笔支付只执行一次
*/
public class PaymentService {
private final TransactionStore txStore;
private final AccountService accountService;
private final DistributedLock lock;
public PayResult pay(PayRequest request) {
String txId = request.getTransactionId();
// 第一步:查询是否已处理过
Transaction existingTx = txStore.findByTxId(txId);
if (existingTx != null) {
// 幂等返回:直接返回上次的处理结果
return buildResultFromTx(existingTx);
}
// 第二步:获取分布式锁,防止并发重复执行
String lockKey = "pay_lock:" + txId;
if (!lock.tryLock(lockKey, 10, TimeUnit.SECONDS)) {
throw new PayException("PROCESSING",
"支付正在处理中,请勿重复提交");
}
try {
// 双重检查
existingTx = txStore.findByTxId(txId);
if (existingTx != null) {
return buildResultFromTx(existingTx);
}
// 第三步:执行扣款
Transaction tx = new Transaction();
tx.setTxId(txId);
tx.setStatus("PROCESSING");
txStore.insert(tx); // 先写入事务记录
try {
accountService.debit(
request.getUserId(), request.getAmount()
);
tx.setStatus("SUCCESS");
} catch (Exception e) {
tx.setStatus("FAILED");
tx.setErrorMsg(e.getMessage());
}
txStore.update(tx);
return buildResultFromTx(tx);
} finally {
lock.unlock(lockKey);
}
}
private PayResult buildResultFromTx(Transaction tx) {
PayResult result = new PayResult();
result.setTransactionId(tx.getTxId());
result.setStatus(tx.getStatus());
return result;
}
}6.4 对账系统
微信支付每天需要与银行、第三方支付渠道进行对账,确保每一笔交易的金额和状态一致。对账的基本流程:
- 每天凌晨从各渠道下载交易流水文件
- 将渠道流水与微信内部流水逐笔比对
- 差异部分生成差错记录,人工或自动处理
- 长款(渠道有、微信没有)和短款(微信有、渠道没有)分别处理
七、春节红包的流量洪峰设计
7.1 问题规模
春节红包是微信系统面临的最大流量挑战。2024 年除夕夜的峰值数据:
- 红包收发峰值:每秒数百万个
- 零点前后 1 分钟内的红包量超过平时一整天
- 流量峰值是日常的 10-100 倍
这种极端的脉冲式流量具有以下特征:
- 可预测:时间点明确(零点前后),可以提前准备
- 持续时间短:核心峰值集中在 1-2 分钟内
- 容错要求高:红包涉及资金,不能出错
- 用户体验敏感:全民关注,稍有卡顿就会上热搜
7.2 红包系统的核心架构
红包系统的技术难点在于:抢红包是一个高并发的扣减操作。一个 100 人群里发了一个 10 人份的红包,可能有 100 个人同时点击”抢”,但只有 10 个人能成功。
graph TB
subgraph 客户端
UI[红包 UI]
end
subgraph 接入层
CDN[CDN 静态资源]
APIGateway[API 网关]
end
subgraph 红包服务
OrderSvc[红包订单服务]
GrabSvc[抢红包服务]
AmountCalc[金额拆分服务]
end
subgraph 排队层
Queue[请求排队队列]
end
subgraph 存储层
RedisCluster[Redis 集群]
DBCluster[数据库集群]
end
subgraph 异步处理
AsyncTransfer[异步转账服务]
Reconcile[对账服务]
end
UI --> CDN
UI --> APIGateway
APIGateway --> OrderSvc
APIGateway --> Queue
Queue --> GrabSvc
GrabSvc --> RedisCluster
GrabSvc --> AmountCalc
OrderSvc --> DBCluster
GrabSvc --> AsyncTransfer
AsyncTransfer --> DBCluster
AsyncTransfer --> Reconcile
7.3 红包金额的拆分算法
微信红包采用的是二倍均值法:每次抢红包时,从剩余金额中随机生成一个值,范围是
[0.01, 剩余金额 / 剩余人数 * 2]。
/**
* 红包金额拆分算法——二倍均值法
* 保证每个人抢到的金额期望值相等
*/
public class RedPacketSplitter {
private static final int MIN_AMOUNT = 1; // 最小 1 分钱(单位:分)
/**
* 预拆分红包金额
* @param totalAmount 总金额(单位:分)
* @param count 红包个数
* @return 每个红包的金额列表
*/
public static List<Integer> split(int totalAmount, int count) {
List<Integer> amounts = new ArrayList<>();
int remainAmount = totalAmount;
int remainCount = count;
Random random = new Random();
for (int i = 0; i < count - 1; i++) {
// 二倍均值法
// 最大值 = 剩余金额 / 剩余人数 * 2
int maxAmount = (remainAmount / remainCount) * 2;
// 随机金额范围:[1, maxAmount]
int amount = random.nextInt(maxAmount - MIN_AMOUNT)
+ MIN_AMOUNT;
// 确保剩余金额够分给剩下的人
if (remainAmount - amount < (remainCount - 1) * MIN_AMOUNT) {
amount = remainAmount - (remainCount - 1) * MIN_AMOUNT;
}
amounts.add(amount);
remainAmount -= amount;
remainCount--;
}
// 最后一个人拿剩余所有金额
amounts.add(remainAmount);
return amounts;
}
}7.4 抢红包的并发控制
抢红包的核心是一个库存扣减问题。微信的解决方案分为几个层次:
7.4.1 请求排队
面对瞬间的海量请求,直接打到数据库会导致数据库崩溃。微信在抢红包链路中加入了请求排队层:
// 抢红包请求排队处理
package redpacket
import (
"context"
"fmt"
"sync"
"time"
)
// GrabRequest 抢红包请求
type GrabRequest struct {
PacketID string
UserID string
Result chan GrabResult
}
// GrabResult 抢红包结果
type GrabResult struct {
Success bool
Amount int64
ErrMsg string
}
// QueueProcessor 排队处理器
// 将并发请求转换为串行处理,消除数据库并发冲突
type QueueProcessor struct {
queues map[string]chan *GrabRequest
mu sync.RWMutex
}
func NewQueueProcessor() *QueueProcessor {
return &QueueProcessor{
queues: make(map[string]chan *GrabRequest),
}
}
// Submit 提交抢红包请求
func (p *QueueProcessor) Submit(
ctx context.Context, req *GrabRequest,
) GrabResult {
queue := p.getOrCreateQueue(req.PacketID)
req.Result = make(chan GrabResult, 1)
select {
case queue <- req:
// 成功入队,等待结果
case <-ctx.Done():
return GrabResult{
Success: false,
ErrMsg: "请求超时",
}
}
select {
case result := <-req.Result:
return result
case <-ctx.Done():
return GrabResult{
Success: false,
ErrMsg: "处理超时",
}
}
}
// getOrCreateQueue 为每个红包创建独立的处理队列
func (p *QueueProcessor) getOrCreateQueue(
packetID string,
) chan *GrabRequest {
p.mu.RLock()
q, exists := p.queues[packetID]
p.mu.RUnlock()
if exists {
return q
}
p.mu.Lock()
defer p.mu.Unlock()
// 双重检查
if q, exists = p.queues[packetID]; exists {
return q
}
q = make(chan *GrabRequest, 1000)
p.queues[packetID] = q
// 启动消费者协程
go p.consume(packetID, q)
return q
}
// consume 串行消费队列中的请求
func (p *QueueProcessor) consume(
packetID string, queue chan *GrabRequest,
) {
timeout := time.NewTimer(30 * time.Minute)
defer timeout.Stop()
for {
select {
case req := <-queue:
result := processGrab(packetID, req.UserID)
req.Result <- result
case <-timeout.C:
// 红包超时未抢完,清理队列
p.mu.Lock()
delete(p.queues, packetID)
p.mu.Unlock()
return
}
}
}
func processGrab(packetID, userID string) GrabResult {
// 实际的抢红包逻辑
fmt.Printf("处理抢红包: packet=%s, user=%s\n",
packetID, userID)
return GrabResult{Success: true, Amount: 100}
}通过为每个红包创建独立的队列,将并发请求转换为串行处理。同一个红包的所有抢夺请求在同一个协程中串行执行,消除了数据库层面的锁竞争。
7.4.2 预拆分与 Redis 缓存
红包金额在发红包时就预先拆分好,存储在 Redis 中:
import redis
import json
class RedPacketCache:
"""红包缓存层,使用 Redis 管理红包状态"""
def __init__(self, redis_client: redis.Redis):
self.redis = redis_client
def create_packet(
self, packet_id: str, amounts: list, expire_seconds: int = 86400
):
"""创建红包:将预拆分的金额存入 Redis"""
key = f"rp:{packet_id}"
# 使用 List 存储预拆分的金额
pipe = self.redis.pipeline()
for amount in amounts:
pipe.rpush(key, amount)
pipe.expire(key, expire_seconds)
# 记录已抢用户集合
grab_key = f"rp_grabbed:{packet_id}"
pipe.expire(grab_key, expire_seconds)
pipe.execute()
def try_grab(self, packet_id: str, user_id: str):
"""
尝试抢红包
使用 Redis 原子操作保证并发安全
返回 (success, amount)
"""
grab_key = f"rp_grabbed:{packet_id}"
# 检查是否已抢过(幂等性)
if self.redis.sismember(grab_key, user_id):
return False, 0
# 原子操作:弹出一个金额
key = f"rp:{packet_id}"
amount = self.redis.lpop(key)
if amount is None:
# 红包已抢完
return False, 0
# 记录已抢用户
self.redis.sadd(grab_key, user_id)
return True, int(amount)7.5 异步转账
抢红包成功后,资金的实际转账是异步完成的。这是春节红包能扛住峰值的关键设计:
- 抢红包是同步操作:用户点击后立即返回结果(成功/失败+金额)
- 资金转账是异步操作:将转账请求写入消息队列,由后台服务逐步处理
这种设计将用户体验关键路径(抢到红包的即时反馈)与资金处理路径(实际到账)解耦。用户看到的”已到账”信息可能比实际资金到账早几秒到几分钟,但这个延迟在用户可接受的范围内。
7.6 春节红包的容量规划
| 维度 | 日常 | 春节峰值 | 扩容策略 |
|---|---|---|---|
| 接入层 QPS | 百万级 | 千万级 | 弹性扩容,提前 2 周部署 |
| Redis 集群 | 数百节点 | 数千节点 | 提前扩容,峰值后缩容 |
| 数据库 | 常规分片 | 额外只读副本 | 读写分离,异步写入 |
| 带宽 | 常规 | 10 倍以上 | 运营商专线预留 |
| 人员 | 常规值班 | 全员战备 | 除夕夜全公司待命 |
八、微信的容灾与多活架构
8.1 SET 化架构
微信采用 SET 化(单元化)架构来实现容灾和扩展。SET 化的核心思想是:将用户按照某种规则划分到不同的 SET(单元)中,每个 SET 包含完整的服务和数据,可以独立处理该 SET 内用户的所有请求。
graph TB
subgraph 路由层
Router[全局路由服务]
end
subgraph SET-A 深圳
GW_A[网关 A]
Svc_A[业务服务 A]
DB_A[(数据 A)]
end
subgraph SET-B 上海
GW_B[网关 B]
Svc_B[业务服务 B]
DB_B[(数据 B)]
end
subgraph SET-C 深圳
GW_C[网关 C]
Svc_C[业务服务 C]
DB_C[(数据 C)]
end
Router --> GW_A
Router --> GW_B
Router --> GW_C
GW_A --> Svc_A
Svc_A --> DB_A
GW_B --> Svc_B
Svc_B --> DB_B
GW_C --> Svc_C
Svc_C --> DB_C
用户的 SET 归属由用户 ID 的哈希值决定。路由规则在全局路由服务中维护,所有接入层节点缓存这份路由表。
8.2 SET 化的跨 SET 通信
单聊场景下,发送方和接收方可能属于不同的 SET。微信的处理方式是:
- 消息先在发送方所属的 SET 中处理(写入发送方信箱)
- 通过跨 SET 的消息转发通道,将消息投递到接收方所属的 SET
- 接收方的 SET 负责写入接收方信箱,并通过长连接推送通知
跨 SET 通信使用异步消息队列,保证最终一致性。
8.3 PaxosStore:基于 Paxos 的分布式存储
微信自研的 PaxosStore 是底层数据一致性的基石。它使用 Paxos 共识协议来保证多副本之间的强一致性。
PaxosStore 的关键设计:
- 三副本部署:每份数据存储三个副本,分布在不同的物理机器上
- 写入路径:写入操作通过 Paxos 协议在三个副本之间达成共识,至少两个副本确认才返回成功
- 读取路径:读取操作可以从主副本直接读取(强一致读),也可以从任意副本读取(最终一致读)
- 自动故障转移:某个副本不可用时,剩余两个副本继续提供服务,系统自动补充新副本
/**
* PaxosStore 写入流程的简化示意
*/
public class PaxosWriter {
private final List<ReplicaNode> replicas;
private final int quorum;
public PaxosWriter(List<ReplicaNode> replicas) {
this.replicas = replicas;
// 多数派:3 副本需要 2 个确认
this.quorum = replicas.size() / 2 + 1;
}
public boolean write(String key, byte[] value) {
// Phase 1: Prepare
long proposalId = generateProposalId();
int prepareOk = 0;
for (ReplicaNode replica : replicas) {
try {
boolean accepted = replica.prepare(proposalId);
if (accepted) prepareOk++;
} catch (Exception e) {
// 副本不可用,跳过
}
}
if (prepareOk < quorum) {
return false; // Prepare 未达到多数派
}
// Phase 2: Accept
int acceptOk = 0;
for (ReplicaNode replica : replicas) {
try {
boolean accepted = replica.accept(
proposalId, key, value
);
if (accepted) acceptOk++;
} catch (Exception e) {
// 副本不可用,跳过
}
}
return acceptOk >= quorum;
}
private long generateProposalId() {
// 生成全局递增的提案 ID
// 通常使用时间戳 + 节点 ID 的组合
return System.nanoTime();
}
}8.4 容灾演练
微信定期进行故障演练,验证容灾能力:
- 机房级故障模拟:切断某个机房的网络,验证 SET 切换是否正常
- 服务级故障注入:随机关闭某些服务实例,验证负载均衡和熔断机制
- 存储故障模拟:关闭 PaxosStore 的某个副本,验证多数派读写是否正常
- 全链路压测:在真实流量中注入标记的压测流量,验证系统在高负载下的表现
九、工程案例:春节红包峰值处理
9.1 背景
2017 年春节是微信红包面临的一次重大考验。根据微信团队在公开技术会议上的分享,这一年的目标是:在除夕零点峰值时刻,系统不降级、不限流,所有用户都能正常收发红包。
9.2 面临的挑战
- 峰值流量预估困难:用户行为受电视节目(春晚)影响,峰值时间和峰值大小难以精确预测
- 资金安全要求:红包涉及真金白银,不允许出现资金差错
- 全链路压力:红包涉及消息推送、支付、存储等多个系统的联动
- 准备时间有限:核心优化工作集中在春节前 2-3 个月
9.3 解决方案
微信团队从以下几个方面入手:
9.3.1 资源预备
提前 2 个月开始扩容:
- Redis 集群从日常的数百节点扩展到数千节点
- 接入层服务器翻倍
- 数据库增加只读副本
- 预留运营商带宽
9.3.2 系统优化——请求合并
针对群红包的场景,同一个群里几十甚至上百人同时抢一个红包,会产生大量并发请求。优化方案是请求合并:
/**
* 请求合并器:将短时间内对同一红包的多次请求合并处理
* 减少对下游存储的并发访问
*/
public class RequestMerger {
// 合并窗口:5 毫秒
private static final long MERGE_WINDOW_MS = 5;
// 每个红包的请求缓冲区
private final ConcurrentHashMap<String, RequestBuffer> buffers
= new ConcurrentHashMap<>();
public CompletableFuture<GrabResult> submit(GrabRequest request) {
String packetId = request.getPacketId();
CompletableFuture<GrabResult> future = new CompletableFuture<>();
RequestBuffer buffer = buffers.computeIfAbsent(
packetId,
k -> new RequestBuffer(k, this::processBatch)
);
buffer.add(request, future);
return future;
}
/**
* 批量处理同一个红包的多个抢夺请求
*/
private void processBatch(
String packetId,
List<Pair<GrabRequest, CompletableFuture<GrabResult>>> batch) {
// 一次性从 Redis 获取红包状态
RedPacketState state = redisClient.getState(packetId);
// 已抢完的请求直接返回失败
if (state.getRemainCount() <= 0) {
for (var pair : batch) {
pair.getSecond().complete(
new GrabResult(false, 0, "红包已抢完")
);
}
return;
}
// 过滤已抢过的用户
Set<String> grabbedUsers = redisClient.getGrabbedUsers(packetId);
int successCount = 0;
for (var pair : batch) {
GrabRequest req = pair.getFirst();
CompletableFuture<GrabResult> future = pair.getSecond();
if (grabbedUsers.contains(req.getUserId())) {
future.complete(
new GrabResult(false, 0, "已抢过")
);
continue;
}
if (successCount >= state.getRemainCount()) {
future.complete(
new GrabResult(false, 0, "红包已抢完")
);
continue;
}
// 执行抢红包逻辑
long amount = redisClient.popAmount(packetId);
if (amount > 0) {
redisClient.recordGrab(packetId, req.getUserId());
future.complete(new GrabResult(true, amount, ""));
successCount++;
} else {
future.complete(
new GrabResult(false, 0, "红包已抢完")
);
}
}
}
/**
* 请求缓冲区:在合并窗口内收集请求
*/
static class RequestBuffer {
private final String packetId;
private final BiConsumer<String,
List<Pair<GrabRequest, CompletableFuture<GrabResult>>>>
processor;
private final List<Pair<GrabRequest,
CompletableFuture<GrabResult>>> pending
= new ArrayList<>();
private ScheduledFuture<?> flushTask;
RequestBuffer(String packetId,
BiConsumer<String,
List<Pair<GrabRequest,
CompletableFuture<GrabResult>>>> processor) {
this.packetId = packetId;
this.processor = processor;
}
synchronized void add(GrabRequest request,
CompletableFuture<GrabResult> future) {
pending.add(Pair.of(request, future));
if (flushTask == null) {
flushTask = scheduler.schedule(
this::flush,
MERGE_WINDOW_MS,
TimeUnit.MILLISECONDS
);
}
}
synchronized void flush() {
if (pending.isEmpty()) return;
List<Pair<GrabRequest,
CompletableFuture<GrabResult>>> batch
= new ArrayList<>(pending);
pending.clear();
flushTask = null;
processor.accept(packetId, batch);
}
}
}9.3.3 降级预案
虽然目标是”不降级”,但必须准备降级预案作为最后的保障:
- 柔性降级:红包封面动画简化、减少不必要的附加信息查询
- 非核心功能关闭:暂停朋友圈红包分享等非核心功能的写入
- 限流兜底:如果 QPS 超过系统极限的 120%,对新发红包进行限流(保证已发红包的正常抢夺)
- 熔断保护:对下游依赖(银行接口等)设置熔断,防止级联故障
9.3.4 全链路压测
春节前 1 个月,微信进行了多次全链路压测:
- 影子流量:将线上真实流量复制一份,发送到压测环境
- 流量放大:在影子流量基础上按 10 倍、50 倍、100 倍逐步放大
- 混沌测试:在压测过程中随机注入故障(网络延迟、服务宕机、磁盘满)
- 红线验证:验证系统在极端负载下不会出现资金差错
9.4 结果
2017 年除夕夜,微信红包系统实现了零故障目标:
- 峰值 QPS 达到预估的 110%,系统平稳承受
- 全链路平均响应时间保持在 200 毫秒以内
- 无资金差错
- 无用户可感知的降级
9.5 经验总结
这次实践验证了几个关键原则:
- 异步化是扛峰值的核心手段:将资金转账从用户交互路径中分离,极大降低了峰值压力
- 请求合并显著减少下游压力:同一红包的并发请求合并后,Redis 操作次数减少 60% 以上
- 全链路压测不可替代:单服务压测无法发现跨服务的瓶颈和级联问题
- 降级预案必须提前准备并验证:即使最终没有用到,准备过程本身也会暴露系统中的问题
十、十亿级即时通信的设计哲学
10.1 移动优先的技术选型
微信的技术决策始终围绕一个核心约束:移动端是第一优先级。这影响了几乎所有的架构设计:
- 协议设计优先考虑省流量、省电
- 消息同步使用增量拉取而非全量推送
- 网络库针对弱网场景做了大量优化
- 客户端使用 SQLite 本地存储,减少对网络的依赖
10.2 简单优于复杂
微信在技术选型上倾向于选择更简单的方案:
- 消息同步使用基于 sequence 的增量拉取,而不是更复杂的 CRDT(Conflict-free Replicated Data Type,无冲突复制数据类型)或者操作变换(Operational Transformation,OT)
- 朋友圈使用读扩散而不是写扩散,避免了复杂的异步写入和一致性问题
- 红包金额预拆分,将复杂的实时计算简化为简单的队列弹出
10.3 可预测性优于极致性能
微信更看重系统行为的可预测性:
- PaxosStore 使用 Paxos 协议保证数据一致性,性能不是最优但行为可预测
- 消息同步使用拉模式兜底,即使推送通知丢失也不会丢消息
- 心跳机制使用自适应算法,在不同网络环境下自动找到合适的参数
10.4 渐进演进而非推倒重来
微信的架构是在 13 年的时间里逐步演进的,没有任何一次”大重写”:
- 2011 年的 PHP 业务层至今仍有部分在运行
- 存储层从 MySQL 到自研 KV,再到 PaxosStore,是逐步替换的
- Mars 网络库是从微信客户端内部的网络模块独立出来的,不是从零设计的
这种渐进式演进的好处是:每一步的风险都可控,不会因为一次大规模重构而引入系统性风险。
10.5 不同架构方案的取舍对比
| 设计决策 | 微信的选择 | 替代方案 | 选择原因 |
|---|---|---|---|
| 消息同步 | 基于 Sequence 增量拉取 | CRDT / OT | 实现简单,sequence 天然有序,满足需求 |
| 朋友圈信息流 | 读扩散 | 写扩散 | 好友数上限 5000,避免写入放大 |
| 数据一致性 | Paxos(强一致) | Raft / Gossip | 支付等场景需要强一致保证 |
| 网络协议 | 自研二进制协议 | HTTP/2 / gRPC | 移动端省流量,控制粒度更细 |
| 红包金额分配 | 预拆分 + 队列弹出 | 实时计算 | 避免高并发下的计算竞争 |
| 消息存储 | 信箱模型 | 会话模型 | 天然支持多端同步和离线消息 |
| 日志方案 | mmap + 压缩 | 直接文件写入 | 崩溃安全 + 低延迟 |
10.6 面向未来的思考
微信的架构仍在持续演进。几个值得关注的方向:
- 端到端加密:更多消息类型支持端到端加密,对服务端架构透明
- 边缘计算:将部分计算(如消息解密、图片缩略图生成)下沉到边缘节点
- AI 集成:智能回复、内容审核等 AI 能力的架构集成
- 跨境合规:不同国家和地区的数据驻留和隐私合规要求
这些方向的共同特点是:不会改变核心架构的基本范式,而是在现有架构上叠加新的能力。这也是微信架构”渐进演进”哲学的延续。
参考资料
书
- 许令波,《大型网站系统与 Java 中间件实践》,电子工业出版社,2014。讨论了大规模互联网系统的中间件设计,包括消息队列、分布式存储等核心组件。
- Martin Kleppmann, Designing Data-Intensive Applications, O’Reilly, 2017。分布式系统数据处理的权威参考,涵盖复制、分区、一致性等核心概念,与微信 PaxosStore 的设计决策直接相关。
论文
- Leslie Lamport, Paxos Made Simple, ACM SIGACT News, 2001。Paxos 共识协议的简化描述,微信 PaxosStore 的理论基础。
- Jianjun Zheng et al., PaxosStore: High-availability Storage Made Practical in WeChat, Proceedings of the VLDB Endowment, Vol. 10, No. 12, 2017。微信 PaxosStore 的设计论文,详细描述了基于 Paxos 的分布式存储架构。
技术分享 / 演讲
- 微信技术团队,《微信序列号生成器架构设计及演变》,InfoQ 技术大会,2016。SeqSvr 的设计演进,包括段分配策略和多级缓存。
- 方乐明,《万亿级调用系统:微信序列号生成器架构设计及演变》,ArchSummit 全球架构师峰会,2016。SeqSvr 架构细节和工程实践。
- 微信技术团队,《微信红包后台系统可用性设计实践》,InfoQ,2017。春节红包系统的容量规划、降级方案和全链路压测实践。
- 微信技术团队,《微信朋友圈技术之道》,QCon 全球软件开发大会,2015。朋友圈读扩散架构和缓存策略的技术分享。
开源项目 / 文档
- Tencent/mars, GitHub, https://github.com/Tencent/mars。微信官方开源的跨平台网络库,包含 STN、SDT、Xlog 等核心模块。
- 微信开放平台文档,https://developers.weixin.qq.com/doc/。微信官方开发者文档,包含小程序、支付等 API 的技术规范。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【系统架构设计百科】架构质量属性:不只是"高可用高性能"
需求评审时写下的'高可用、高性能、高并发',到了架构设计阶段几乎无法落地——因为它们不是可执行的需求。本文从 SEI/CMU 的质量属性理论出发,用 stimulus-response 场景模型把模糊需求变成可量化、可验证的架构约束,并拆解属性之间的冲突与联动关系。
【系统架构设计百科】告警策略:如何避免"狼来了"
大多数团队的告警系统都在制造噪声而不是传递信号。阈值告警看似直观,实则产生大量误报和漏报,值班工程师在凌晨三点被叫醒,却发现只是一次无害的毛刺。本文从告警疲劳的工业数据出发,拆解基于 SLO 的多窗口燃烧率告警算法,深入 Alertmanager 的路由、抑制与分组机制,结合 PagerDuty 的告警疲劳研究和真实工程案例,给出一套可落地的告警策略设计方法。
【系统架构设计百科】复杂性管理:架构的核心战场
系统复杂性是架构腐化的根源——本文从 Brooks 的本质复杂性与偶然复杂性划分出发,结合认知负荷理论与 Parnas 的信息隐藏原则,系统阐述复杂性的来源、度量与控制手段,并给出可操作的架构策略
【系统架构设计百科】微服务架构深度审视:优势、代价与适用边界
微服务不是免费的午餐。本文从分布式系统八大谬误出发,拆解微服务真正解决的问题与引入的代价,梳理服务边界划分的工程方法论,还原 Amazon 和 Netflix 从单体到微服务的真实演进时间线,给出微服务适用与不适用的判断框架。