微信海量通讯架构
2011 年 1 月 21 日,微信发布了 iPhone 1.0 版本。那时候,腾讯已经有 QQ,坐拥数亿用户。但张小龙团队在腾讯内部启动了一个新项目——一个更简单、更私密的通讯工具。
五年后,微信月活用户突破 8 亿,超越 QQ 成为亚洲最大的社交应用。十三年后,微信月活超过 13 亿,成为全球最大的即时通讯(IM)平台之一。每天处理 1000 亿条以上的消息,同时在线用户峰值超过 5 亿。
微信的技术挑战与 QQ 不同。QQ 诞生于 PC 时代,用户坐在电脑前,24 小时在线。微信诞生于移动互联网时代,用户随时在线,随时切换设备,网络环境复杂(地铁、电梯、地下室)。理解微信的架构设计,必须理解这两个时代的差异。
公司画像
微信(WeChat)是腾讯旗下社交通讯产品,2011 年由张小龙团队推出,目前覆盖中国及东南亚、欧美等海外市场。
理解微信技术挑战的关键,在于三个数字:
- 日活跃用户:超过 13 亿(2024 年数据)
- 消息发送量:每天 1000 亿条以上(文字、图片、视频、语音)
- 同时在线人数:峰值超过 5 亿
微信的技术挑战与 QQ 有本质不同:
架构演进时间线
海量用户的核心挑战
长连接 vs 短连接
早期微信使用 HTTP Long Pulling(长轮询)实现消息推送。客户端发起 HTTP 请求,服务器如果没有新消息就 hold 住请求,等到超时或有新消息才返回。
长轮询的问题是效率低:每个请求都要建立完整的 HTTP 连接(TCP 三次握手 + TLS 握手),消耗服务器资源。对于消息量巨大的微信来说,这个开销不可接受。
后来微信切换到 WebSocket 长连接:
// WebSocket 长连接:消息推送
public class WeChatPushService {
// 维护用户长连接
private ConcurrentHashMap<Long, WebSocketSession> userConnections = new ConcurrentHashMap<>();
// 用户上线:建立长连接
public void onUserOnline(Long userId, WebSocketSession session) {
userConnections.put(userId, session);
// 更新用户在线状态
onlineStatusService.setOnline(userId);
// 同步未读消息
syncUnreadMessages(userId);
}
// 用户离线:关闭连接
public void onUserOffline(Long userId) {
userConnections.remove(userId);
// 更新用户在线状态
onlineStatusService.setOffline(userId);
// 存储离线消息
offlineMessageService.storeOffline(userId);
}
// 推送消息到用户
public void pushMessage(Long userId, Message message) {
WebSocketSession session = userConnections.get(userId);
if (session != null && session.isOpen()) {
// 在线:直接推送
session.sendMessage(new TextMessage(toJson(message)));
} else {
// 离线:存储离线消息
offlineMessageService.storeOffline(userId, message);
}
}
}
长连接的优势:一次建立,持续使用。连接建立后,服务器可以随时向客户端推送消息,不需要客户端反复请求。
心跳机制
移动网络的 NAT 超时时间通常只有几分钟。如果客户端长时间不发送数据,NAT 会认为连接失效,断开映射。
微信使用心跳机制维持连接活跃:
// 心跳服务
public class HeartbeatService {
private static final int HEARTBEAT_INTERVAL_SECONDS = 30;
private static final int HEARTBEAT_TIMEOUT_SECONDS = 60;
// 客户端定时发送心跳
@Scheduled(fixedRate = HEARTBEAT_INTERVAL_SECONDS * 1000)
public void sendHeartbeat() {
WebSocketSession session = getCurrentSession();
if (session.isOpen()) {
HeartbeatMessage heartbeat = new HeartbeatMessage();
heartbeat.setUserId(getCurrentUserId());
heartbeat.setTimestamp(System.currentTimeMillis());
heartbeat.setClientVersion(BuildConfig.VERSION_NAME);
session.sendMessage(new TextMessage(toJson(heartbeat)));
}
}
// 服务器检测心跳超时
@Scheduled(fixedRate = HEARTBEAT_TIMEOUT_SECONDS * 1000)
public void checkHeartbeatTimeout() {
long timeoutThreshold = System.currentTimeMillis()
- HEARTBEAT_TIMEOUT_SECONDS * 1000L;
for (Long userId : connectionManager.getOnlineUsers()) {
long lastHeartbeat = connectionManager.getLastHeartbeat(userId);
if (lastHeartbeat < timeoutThreshold) {
// 心跳超时:标记为离线
connectionManager.markOffline(userId);
log.info("User {} heartbeat timeout, marking offline", userId);
// 触发离线事件
eventBus.publish(new UserOfflineEvent(userId));
}
}
}
}
心跳频率的选择是性能和省电的权衡:
- 心跳太频繁:耗电量大,影响手机续航
- 心跳太稀疏:连接容易断开,用户体验差
- 微信的策略:正常情况 30 秒心跳,网络切换时增加频率
单聊与群聊的架构差异
单聊:消息路由
单聊是最简单的场景:A 发消息给 B,消息最终到达 B 的所有在线设备。
// 单聊消息路由
public class SingleChatRouter {
@Autowired private MessageStore messageStore;
@Autowired private MultiDeviceSyncService multiDeviceSync;
// 发送单聊消息
public SendResult sendMessage(Long senderId, Long receiverId, MessageContent content) {
// 1. 生成全局唯一消息 ID
String msgId = snowflakeIdGenerator.nextId();
// 2. 构造消息
Message message = new Message();
message.setMsgId(msgId);
message.setSenderId(senderId);
message.setReceiverId(receiverId);
message.setContent(content);
message.setTimestamp(System.currentTimeMillis());
message.setStatus(MessageStatus.SENDING);
// 3. 持久化消息
messageStore.save(message);
// 4. 检查接收方是否在线
if (onlineStatusService.isOnline(receiverId)) {
// 在线:直接推送
pushService.pushToUser(receiverId, message);
} else {
// 离线:存入离线消息队列
offlineMessageService.store(receiverId, message);
}
// 5. 同步到发送方的所有设备
multiDeviceSync.syncToSenderDevices(senderId, message);
return SendResult.success(msgId);
}
}
群聊:消息扩散 vs 消息路由
群聊比单聊复杂得多。当 A 在群里发消息,需要复制消息给所有群成员。
有两种策略:
- 消息扩散(Push 模型):消息发送到服务器,服务器把消息推送给每个群成员。实现简单,但群成员多时服务器压力大
- 消息路由(Pull 模型):消息只存储一份,群成员自己来拉取。服务器压力小,但拉取延迟高
// 群聊消息处理
public class GroupChatService {
@Autowired private GroupMemberService groupMemberService;
@Autowired private MessageStore messageStore;
@Autowired private PushService pushService;
// 发送群聊消息
public SendResult sendGroupMessage(Long senderId, Long groupId, MessageContent content) {
// 1. 获取群成员列表
List<Long> memberIds = groupMemberService.getActiveMembers(groupId);
// 2. 构造消息
String msgId = snowflakeIdGenerator.nextId();
Message message = new Message();
message.setMsgId(msgId);
message.setSenderId(senderId);
message.setGroupId(groupId);
message.setContent(content);
message.setTimestamp(System.currentTimeMillis());
// 3. 持久化消息(只存一份)
messageStore.saveGroupMessage(groupId, message);
// 4. 统计未读:检查每个成员上次拉取的位置
Map<Long, Long> unreadCounts = new HashMap<>();
for (Long memberId : memberIds) {
long lastReadMsgId = readPositionService.getLastRead(memberId, groupId);
long unreadCount = messageStore.countAfter(groupId, lastReadMsgId);
unreadCounts.put(memberId, unreadCount);
}
// 5. 推送消息给在线成员
for (Long memberId : memberIds) {
if (onlineStatusService.isOnline(memberId)) {
// 在线成员:推送消息
pushService.pushToUser(memberId, message);
}
// 离线成员:通过未读数提醒,下次上线拉取
}
return SendResult.success(msgId);
}
}
微信的群聊策略是混合模型:
- 小群(
<= 100 人):消息扩散,在线成员直接推送
- 大群(
> 100 人):消息路由,只存储一份,成员主动拉取
消息存储设计
为什么要持久化
与 QQ 一样,微信的消息必须持久化。但微信的存储挑战更严峻:
- 13 亿用户:每个用户的消息都要存储
- 1000 亿条/天:写入量极大
- 永久保存:用户可能随时翻阅几年前的消息
- 多端同步:同一消息在手机、平板、电脑上都要能看到
分层存储:热、温、冷
微信采用分层存储策略,根据消息的「热度」选择不同的存储介质:
// 消息存储分层
public class MessageStorageService {
// 热度判断规则
// 热数据:最近 7 天,访问频繁 -> SSD 存储
// 温数据:7-90 天,偶尔访问 -> HDD 存储
// 冷数据:90 天以上,极少访问 -> 低成本存储(如对象存储)
// 写入消息
public void saveMessage(Message message) {
// 写入热数据层(SSD)
hotStorage.save(message);
// 异步归档到温数据层
scheduleService.schedule(
() -> archiveToWarmStorage(message),
message.getTimestamp() + TimeUnit.DAYS.toMillis(7)
);
}
// 查询消息
public List<Message> queryMessages(Long userId, long startTime, long endTime) {
// 1. 查询热数据层
List<Message> hotMessages = hotStorage.query(userId, startTime, endTime);
// 2. 查询温数据层
List<Message> warmMessages = warmStorage.query(userId, startTime, endTime);
// 3. 查询冷数据层(如需要)
if (endTime - startTime > TimeUnit.DAYS.toMillis(90)) {
List<Message> coldMessages = coldStorage.query(userId, startTime, endTime);
return mergeAndSort(hotMessages, warmMessages, coldMessages);
}
return mergeAndSort(hotMessages, warmMessages);
}
// 归档到温数据层
private void archiveToWarmStorage(Message message) {
warmStorage.save(message);
hotStorage.delete(message.getMsgId());
}
}
消息同步协议
用户切换设备时,需要把历史消息同步到新设备。微信的同步协议需要解决两个问题:
- 增量同步:只同步新消息,不重复同步
- 多端一致:所有设备看到的消息顺序一致
// 消息同步协议
public class MessageSyncService {
// 同步请求:客户端告诉服务器「我已经同步到哪里了」
public SyncResponse syncMessages(SyncRequest request) {
Long userId = request.getUserId();
long syncKey = request.getSyncKey(); // 客户端已同步的最大消息 ID
// 1. 查询新消息
List<Message> newMessages = messageStore.queryAfter(userId, syncKey);
// 2. 生成本次同步的 syncKey
long newSyncKey = newMessages.isEmpty()
? syncKey
: newMessages.get(newMessages.size() - 1).getMsgId();
// 3. 返回新消息和新的 syncKey
return SyncResponse.builder()
.messages(newMessages)
.syncKey(newSyncKey)
.hasMore(newMessages.size() >= MAX_BATCH_SIZE)
.build();
}
// 客户端收到同步响应后:
// 1. 显示新消息
// 2. 保存新的 syncKey 到本地
// 3. 如果 hasMore=true,继续请求下一批
}
朋友圈:弱关系 vs 强关系
朋友圈是微信的「弱关系」社交功能。与消息的「强关系」不同,朋友圈的内容是公开可见范围可配置的。
技术差异
朋友圈 Feed 架构
朋友圈的 Feed(信息流)架构与 Twitter 类似,但增加了隐私过滤层:
// 朋友圈 Feed 拉取
public class MomentsFeedService {
@Autowired private PrivacyService privacyService;
@Autowired private ContentService contentService;
// 拉取朋友圈 Feed
public List<FeedItem> pullFeed(Long userId, long sinceId, int limit) {
// 1. 获取好友列表
List<Long> friendIds = friendService.getFriends(userId);
// 2. 查询好友最近发布的内容
List<FeedItem> candidateFeeds = contentService.queryFeeds(
friendIds, sinceId, limit * 2 // 多查一些,过滤后可能不够
);
// 3. 隐私过滤:移除用户无权看到的内容
List<FeedItem> filteredFeeds = candidateFeeds.stream()
.filter(feed -> privacyService.canView(userId, feed))
.limit(limit)
.collect(Collectors.toList());
// 4. 排序(按发布时间倒序)
filteredFeeds.sort((a, b) -> b.getTimestamp().compareTo(a.getTimestamp()));
return filteredFeeds;
}
// 发布朋友圈
public void publishMoment(Long userId, MomentContent content, PrivacySetting privacy) {
// 1. 保存内容
Moment moment = new Moment();
moment.setId(snowflakeIdGenerator.nextId());
moment.setUserId(userId);
moment.setContent(content);
moment.setPrivacy(privacy);
moment.setCreatedAt(System.currentTimeMillis());
momentRepository.save(moment);
// 2. 通知好友有新内容(异步)
List<Long> friendIds = friendService.getFriends(userId);
for (Long friendId : friendIds) {
// 推送到好友的未读计数
unreadCountService.increment(friendId, UnreadType.MOMENTS);
}
}
}
朋友圈的可见性规则比消息复杂得多:
- 公开:所有好友可见
- 私密:仅自己可见
- 部分可见:指定好友可见
- 不给谁看:排除指定好友
- 朋友圈可见时间范围:最近 N 天
过载保护策略
微信面临的流量峰值,远超大多数互联网产品。例如:除夕夜零点,用户发送的新年祝福可能达到平时的 10 倍。
削峰策略
// 过载保护:削峰策略
public class OverloadProtectionService {
private static final int MAX_QPS = 500000; // 系统最大处理能力
// 消息发送限流
public SendResult trySendMessage(Long userId, MessageContent content) {
// 1. 检查用户发送频率
long userQps = rateLimiter.getQps("user:" + userId);
if (userQps > MAX_USER_QPS) {
return SendResult.fail("发送太频繁,请稍后再试");
}
// 2. 检查全局 QPS
long globalQps = rateLimiter.getQps("global");
if (globalQps > MAX_QPS) {
// 进入排队模式
return enqueueForLater(userId, content);
}
// 3. 正常发送
return doSendMessage(userId, content);
}
// 异步排队
private SendResult enqueueForLater(Long userId, MessageContent content) {
// 放入延迟队列,1-3 秒后再试
delayQueue.offer(new DelayedMessage(userId, content, 2000));
return SendResult.successWithDelay("消息已排队,稍后发送");
}
}
降级策略
// 降级策略:当系统压力大时,关闭非核心功能
public class DegradationService {
// 检查是否需要降级
public boolean shouldDegrade(String feature) {
SystemLoad load = monitorService.getSystemLoad();
// 负载超过 80%:关闭非核心功能
if (load.getCpuUsage() > 80) {
return degradationRules.get(feature).isDegradable();
}
return false;
}
// 朋友圈降级:图片质量下降
public ImageQuality degradeImageQuality() {
if (shouldDegrade("moments_image")) {
// 高清 -> 普清
return ImageQuality.STANDARD;
}
return ImageQuality.HIGH;
}
// 消息降级:语音转文字暂时关闭
public boolean isSpeech2TextEnabled() {
return !shouldDegrade("speech2text");
}
}
地域化部署
微信的全球用户分布极广,跨地域访问延迟问题严重。腾讯在全球部署了多个接入机房,用户就近接入。
就近接入
// 就近接入:DNS 解析 + IP 库
public class AccessRouter {
// 用户发起请求时,DNS 返回最近的接入机房 IP
public InetSocketAddress resolveAccessPoint(Long userId) {
// 1. 获取用户地理位置(通过 IP 库)
String clientIp = getClientIp();
GeoLocation location = ipGeoLocator.locate(clientIp);
// 2. 根据地理位置选择最近的接入机房
// 优先级:同机房 > 同城 > 同区域 > 跨区域
AccessPoint accessPoint = accessPointSelector.select(
location,
Metric.RESPONSE_TIME // 按响应时间选
);
return new InetSocketAddress(
accessPoint.getPublicIp(),
accessPoint.getPort()
);
}
// 跨机房消息路由
public void routeMessage(Message message) {
Long receiverId = message.getReceiverId();
if (isLocalUser(receiverId)) {
// 本机房用户:直接投递
deliverLocally(message);
} else {
// 跨机房用户:通过专线投递
String targetDataCenter = getUserDataCenter(receiverId);
dcRouter.sendToDataCenter(targetDataCenter, message);
}
}
}
多机房数据同步
跨机房部署的最大挑战是数据一致性。如果用户数据在北京和上海都有副本,用户修改数据后,需要同步到所有机房。
// 多机房数据同步
public class CrossDataCenterSync {
// 用户数据变更时,同步到其他机房
public void syncUserDataChange(UserDataChange change) {
// 1. 在本机房更新
localDataStore.update(change);
// 2. 异步同步到其他机房
List<String> otherDataCenters = getOtherDataCenters();
for (String dc : otherDataCenters) {
// 通过消息队列异步同步
mqTemplate.send("dcsync:" + dc, change);
}
}
// 处理同步消息
@MQListener(topic = "dcsync:*")
public void onSyncMessage(UserDataChange change) {
// 应用变更到本地存储
// 可能存在短暂不一致,但最终一致
localDataStore.applyChange(change);
}
}
小程序与公众号架构
小程序:WebView 隔离
微信小程序(Mini Program)是微信在 2017 年推出的轻量级应用。用户无需下载,在微信内直接使用。
小程序的技术架构是 WebView 隔离:
// 小程序引擎
public class MiniProgramEngine {
// 小程序运行在独立的 WebView 中
// 与朋友圈、公众号等原生界面隔离
// 小程序生命周期
public void onLaunch(String appId) {
// 1. 加载小程序包
WxaPackage pkg = packageManager.loadPackage(appId);
// 2. 初始化 WebView
WebView webView = createIsolatedWebView();
// 3. 加载小程序代码
webView.loadUrl(pkg.getEntryUrl());
// 4. 注册微信 JS-SDK
registerWechatJSApi(webView);
}
// 微信 JS-SDK 提供的能力
// - 微信支付
// - 获取用户信息
// - 分享
// - 地理位置
// - 扫码
// - 蓝牙/NFC
private void registerWechatJSApi(WebView webView) {
JsInterface interface = new JsInterface() {
@JavascriptInterface
public String invoke(String api, String params) {
return wechatApiBridge.handle(api, params);
}
};
webView.addJavascriptInterface(interface, "WechatJSBridge");
}
}
小程序与原生 App 的对比:
公众号:内容平台
公众号(Official Account)是微信的内容平台,允许企业和个人发布文章。技术架构与朋友圈类似,但增加了CMS 管理和运营功能。
量化数据
架构启示
启示一:长连接是 IM 的基础设施
IM 系统的核心是长连接。微信的成功很大程度上得益于早期就选择了长连接方案。相比短轮询,长连接能实时推送消息,用户体验更好。
建议:如果你的产品需要实时消息推送,优先考虑长连接方案。WebSocket 是标准选择,移动端也可以考虑自研协议(如微信的 SIP 协议)。
启示二:消息必达是最基本的要求
IM 系统的消息送达是端到端保证的。任何环节出问题,都会导致「消息丢了」的用户投诉。
建议:消息持久化是底线,不要为了性能牺牲可靠性。ACK 机制、失败重试、幂等消费,这些设计一个都不能少。
启示三:分层存储是成本与性能的平衡
微信的消息存储分为热、温、冷三层。热数据放 SSD(贵但快),冷数据放对象存储(便宜但慢)。这是互联网存储的经典实践。
建议:不是所有数据都要高性能存储。根据访问频率选择合适的存储介质,可以大幅降低成本。
启示四:朋友圈和消息是不同的架构
朋友圈的 Feed 架构与消息推送完全不同:消息是 Push 模式,朋友圈是 Pull 模式;消息是一对一,朋友圈是一对多且需要隐私过滤。
建议:不要试图用一套架构解决所有问题。不同的业务场景,有不同的技术需求。
术语表
总结
微信的技术演进,始终围绕一个核心命题:如何在 亿级用户规模下,保证每条消息都能可靠、实时、低延迟地送达,同时控制成本。
演进脉络:
- 2011-2013:Long Pulling 到长连接,解决移动互联网实时推送问题
- 2013-2016:多端同步、消息漫游,支撑亿级用户
- 2016-2019:公众号、小程序,生态扩展
- 2019-至今:多机房部署、端到端加密、全球化运营
核心技术亮点:
- 长连接与心跳:维持亿级长连接,及时发现断线
- 消息 ACK 机制:端到端消息必达保证
- 多层存储:热、温、冷数据分层,平衡性能与成本
- 朋友圈 Feed 架构:Pull 模式 + 隐私过滤
- 就近接入:多机房部署,全球用户就近服务
对普通项目的启发:
- IM 系统的消息送达是最高优先级,不要在消息持久化上妥协
- 长连接是 IM 的基础设施,要做好容量规划
- 根据访问频率选择存储介质,不要过度投入
- 朋友圈和消息是完全不同的架构,不要试图用一套系统解决
- 过载保护要提前设计,不要等产品做大后再加