微信海量通讯架构

2011 年 1 月 21 日,微信发布了 iPhone 1.0 版本。那时候,腾讯已经有 QQ,坐拥数亿用户。但张小龙团队在腾讯内部启动了一个新项目——一个更简单、更私密的通讯工具。

五年后,微信月活用户突破 8 亿,超越 QQ 成为亚洲最大的社交应用。十三年后,微信月活超过 13 亿,成为全球最大的即时通讯(IM)平台之一。每天处理 1000 亿条以上的消息,同时在线用户峰值超过 5 亿

微信的技术挑战与 QQ 不同。QQ 诞生于 PC 时代,用户坐在电脑前,24 小时在线。微信诞生于移动互联网时代,用户随时在线,随时切换设备,网络环境复杂(地铁、电梯、地下室)。理解微信的架构设计,必须理解这两个时代的差异。

公司画像

微信(WeChat)是腾讯旗下社交通讯产品,2011 年由张小龙团队推出,目前覆盖中国及东南亚、欧美等海外市场。

理解微信技术挑战的关键,在于三个数字:

  • 日活跃用户:超过 13 亿(2024 年数据)
  • 消息发送量:每天 1000 亿条以上(文字、图片、视频、语音)
  • 同时在线人数:峰值超过 5 亿

微信的技术挑战与 QQ 有本质不同:

维度QQ微信
时代背景PC 互联网移动互联网
网络环境固定网络,稳定移动网络,频繁切换(4G/5G/WiFi)
设备类型PC 为主手机为主,多设备
用户习惯长时间在线碎片化使用,频繁切换
安全要求中等高(隐私保护、支付安全)

架构演进时间线

时间阶段核心技术解决的核心问题
2011-2013快速迭代期Long Pulling + 轻量协议移动互联网,弱网环境
2013-2016规模扩展期长连接 + 多端同步 + 消息漫游亿级用户,跨设备同步
2016-2019公众号与小程序混合开发 + WebView 隔离生态扩展,平台化
2019-至今全球化与安全多机房 + 端到端加密 + 数据合规全球化运营,隐私合规

海量用户的核心挑战

长连接 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 强关系

朋友圈是微信的「弱关系」社交功能。与消息的「强关系」不同,朋友圈的内容是公开可见范围可配置的

技术差异

维度消息(强关系)朋友圈(弱关系)
内容类型文字、图片、语音、视频图片 + 文字(视频后来加入)
可见性只有接收方能看到好友可见,可配置
互动方式回复(私聊/群聊)评论 + 点赞
刷新方式Push(实时推送)Pull(下拉刷新)
存储量极大(1000 亿条/天)中等(每日数十亿条)

朋友圈 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 的对比:

维度原生 App小程序
安装需要下载安装无需下载,扫码即用
开发成本高(iOS + Android)低(一次开发,多端运行)
性能中等(WebView 限制)
能力完整系统能力受限(通过 JS-SDK 调用)
更新需要用户更新微信内自动更新

公众号:内容平台

公众号(Official Account)是微信的内容平台,允许企业和个人发布文章。技术架构与朋友圈类似,但增加了CMS 管理和运营功能

量化数据

指标2011 年2015 年2019 年2024 年
月活用户100 万5 亿11 亿13 亿
日消息发送量1000 万100 亿500 亿1000 亿+
同时在线峰值100 万1 亿3 亿5 亿+
群聊最大人数205005001000
小程序数量100 万400 万+

架构启示

启示一:长连接是 IM 的基础设施

IM 系统的核心是长连接。微信的成功很大程度上得益于早期就选择了长连接方案。相比短轮询,长连接能实时推送消息,用户体验更好。

建议:如果你的产品需要实时消息推送,优先考虑长连接方案。WebSocket 是标准选择,移动端也可以考虑自研协议(如微信的 SIP 协议)。

启示二:消息必达是最基本的要求

IM 系统的消息送达是端到端保证的。任何环节出问题,都会导致「消息丢了」的用户投诉。

建议:消息持久化是底线,不要为了性能牺牲可靠性。ACK 机制、失败重试、幂等消费,这些设计一个都不能少。

启示三:分层存储是成本与性能的平衡

微信的消息存储分为热、温、冷三层。热数据放 SSD(贵但快),冷数据放对象存储(便宜但慢)。这是互联网存储的经典实践。

建议:不是所有数据都要高性能存储。根据访问频率选择合适的存储介质,可以大幅降低成本。

启示四:朋友圈和消息是不同的架构

朋友圈的 Feed 架构与消息推送完全不同:消息是 Push 模式,朋友圈是 Pull 模式;消息是一对一,朋友圈是一对多且需要隐私过滤。

建议:不要试图用一套架构解决所有问题。不同的业务场景,有不同的技术需求。

术语表

术语类型说明
张小龙(Allen Zhang)人名微信创始人,Foxmail 创始人,腾讯高级副总裁
Long Pulling技术名词长轮询,客户端发起请求后服务器 hold 住直到有新消息
WebSocket技术名词双向通信协议,支持服务端主动推送消息
心跳机制技术名词客户端定时发送小包证明连接活跃,防止 NAT 超时断开
ACK技术名词确认机制,接收方收到消息后回复确认
消息漫游技术名词同一账号在多个设备上登录,消息在所有设备上同步
Feed技术名词信息流,社交应用的内容展示方式
隐私过滤技术名词根据用户设置的可见性规则,过滤不该展示的内容
热数据技术名词频繁访问的数据,存储在高性能介质(如 SSD)
冷数据技术名词很少访问的数据,存储在低成本介质(如对象存储)
SIP 协议技术名词Session Initiation Protocol,微信自研的即时通讯协议
JS-SDK技术名词JavaScript Software Development Kit,小程序调用微信能力的接口

总结

微信的技术演进,始终围绕一个核心命题:如何在 亿级用户规模下,保证每条消息都能可靠、实时、低延迟地送达,同时控制成本

演进脉络

  • 2011-2013:Long Pulling 到长连接,解决移动互联网实时推送问题
  • 2013-2016:多端同步、消息漫游,支撑亿级用户
  • 2016-2019:公众号、小程序,生态扩展
  • 2019-至今:多机房部署、端到端加密、全球化运营

核心技术亮点

  • 长连接与心跳:维持亿级长连接,及时发现断线
  • 消息 ACK 机制:端到端消息必达保证
  • 多层存储:热、温、冷数据分层,平衡性能与成本
  • 朋友圈 Feed 架构:Pull 模式 + 隐私过滤
  • 就近接入:多机房部署,全球用户就近服务

对普通项目的启发

  • IM 系统的消息送达是最高优先级,不要在消息持久化上妥协
  • 长连接是 IM 的基础设施,要做好容量规划
  • 根据访问频率选择存储介质,不要过度投入
  • 朋友圈和消息是完全不同的架构,不要试图用一套系统解决
  • 过载保护要提前设计,不要等产品做大后再加