视频平台的消息推送架构:从长连接到离线推送的高可用方案
视频平台的消息推送架构从长连接到离线推送的高可用方案一、背景与问题定义视频平台的消息推送场景远比即时通讯复杂。用户可能收到互动通知评论、点赞、关注、系统通知审核结果、活动推送、以及实时消息直播开播提醒。这些场景对时效性和可靠性的要求各不相同——直播开播提醒需要在 5 秒内触达而点赞通知可以接受 30 秒的延迟。更棘手的是连接管理千万 DAU 意味着同时维护百万级的 WebSocket 长连接连接断开、重连、App 切后台、设备网络切换——这些行为导致的连接状态变化必须在系统层面可靠处理否则消息丢失率会直线上升。本文复盘一套支持千万级设备的消息推送架构涵盖长连接管理、在线/离线分发策略、APNs/FCM 通道管理和推送到达率监控。二、整体推送架构长连接网关设计3.1 连接管理长连接网关使用 Netty 实现每个网关节点维护 5~10 万条 WebSocket 连接。核心组件Component public class WebSocketGateway { // 本节点维护的连接channelId → Channel private final ConcurrentHashMapString, Channel localConnections new ConcurrentHashMap(); // 全局路由表userId → gatewayNodeId存储在 Redis private final StringRedisTemplate redisTemplate; private static final String ROUTE_KEY_PREFIX ws:route:; EventListener public void onConnectionEstablished(ConnectionEstablishedEvent event) { Channel channel event.getChannel(); String userId event.getUserId(); String deviceId event.getDeviceId(); String connectionId userId : deviceId; // 记录本节点连接 localConnections.put(connectionId, channel); // 写入全局路由表Redis Hash String routeKey ROUTE_KEY_PREFIX userId; redisTemplate.opsForHash().put(routeKey, deviceId, getLocalNodeId()); redisTemplate.expire(routeKey, Duration.ofHours(2)); // 上报连接数指标 metricsCollector.gauge(ws.connections.active, localConnections.size()); } EventListener public void onConnectionClosed(ConnectionClosedEvent event) { String connectionId event.getUserId() : event.getDeviceId(); localConnections.remove(connectionId); // 检查用户是否还有其他设备在线 String routeKey ROUTE_KEY_PREFIX event.getUserId(); redisTemplate.opsForHash().delete(routeKey, event.getDeviceId()); if (Boolean.FALSE.equals(redisTemplate.hasKey(routeKey)) || redisTemplate.opsForHash().size(routeKey) 0) { // 用户所有设备都离线标记离线状态 redisTemplate.delete(routeKey); userStatusService.markOffline(event.getUserId()); } } }3.2 心跳与断线检测WebSocket 的心跳设计遵循客户端主动、服务端监控的原则。客户端每 30 秒发送 PING 帧服务端在 90 秒内未收到任何帧则主动断开连接。public class HeartbeatHandler extends ChannelInboundHandlerAdapter { private static final int READ_IDLE_SECONDS 90; private long lastReadTime System.currentTimeMillis(); Override public void channelRead(ChannelHandlerContext ctx, Object msg) { if (msg instanceof PingWebSocketFrame) { // 响应 PONG ctx.writeAndFlush(new PongWebSocketFrame()); lastReadTime System.currentTimeMillis(); return; } lastReadTime System.currentTimeMillis(); ctx.fireChannelRead(msg); } // 定时任务每 15 秒检查所有连接 Scheduled(fixedRate 15000) public void checkIdleConnections() { long now System.currentTimeMillis(); long idleThreshold READ_IDLE_SECONDS * 1000L; localConnections.forEach((connectionId, channel) - { Long lastRead channel.attr(LAST_READ_TIME_KEY).get(); if (lastRead ! null now - lastRead idleThreshold) { log.warn(Closing idle connection: {}, connectionId); channel.close(); } }); } }3.3 连接路由与在线推送当用户在线时推送流程是Dispatcher → 查 Redis 路由表 → 找到目标 Gateway 节点 → 通过内部 RPC 转发消息 → Gateway 找到本地 Channel → 写入 WebSocket 帧。Service public class OnlinePushService { public PushResult pushToOnlineUser(String userId, PushMessage message) { String routeKey ROUTE_KEY_PREFIX userId; MapObject, Object routes redisTemplate.opsForHash() .entries(routeKey); if (routes.isEmpty()) { return PushResult.OFFLINE; } int successCount 0; for (Object deviceId : routes.keySet()) { String gatewayNodeId (String) routes.get(deviceId); try { // 通过 gRPC 转发到目标 Gateway 节点 PushForwardRequest request PushForwardRequest.newBuilder() .setUserId(userId) .setDeviceId((String) deviceId) .setConnectionId(userId : deviceId) .setPayload(message.toJson()) .build(); PushForwardResponse response gatewayRpcClient.forward(gatewayNodeId, request); if (response.getSuccess()) successCount; } catch (Exception e) { log.warn(Failed to push to device {}: {}, deviceId, e.getMessage()); // 路由可能已过期清理 redisTemplate.opsForHash().delete(routeKey, deviceId); } } return successCount 0 ? PushResult.SUCCESS : PushResult.FAILED; } }三、离线推送通道4.1 APNs/FCM 通道管理离线用户通过 APNsiOS或 FCMAndroid推送。通道管理的核心关注点是证书/密钥轮换和到达率监控Service public class OfflinePushService { private final MapString, ApnsClient apnsClients new ConcurrentHashMap(); private final MapString, FcmClient fcmClients new ConcurrentHashMap(); PostConstruct public void init() { // 按 App Bundle ID 初始化客户端 apnsClients.put(com.example.ios, buildApnsClient(prod, /certs/apns_prod.p8, TEAM_ID, KEY_ID)); fcmClients.put(com.example.android, buildFcmClient(/certs/fcm_service_account.json)); // 启动证书过期监控 scheduleCertRotationCheck(); } public PushResult pushOffline(long userId, String deviceToken, Platform platform, PushMessage message) { return switch (platform) { case IOS - pushViaApns(deviceToken, message); case ANDROID - pushViaFcm(deviceToken, message); }; } private PushResult pushViaApns(String deviceToken, PushMessage message) { SimpleApnsPushBuilder builder apnsClient.push(deviceToken) .alertTitle(message.getTitle()) .alertBody(message.getBody()) .sound(default) .badge(message.getBadgeCount()) .category(message.getCategory()) .expiration(Duration.ofHours(1)); // 自定义数据 builder.customField(type, message.getType()); builder.customField(targetId, message.getTargetId()); try { PushNotificationResponseSimpleApnsPushBuilder response builder.send().get(5, TimeUnit.SECONDS); if (response.isAccepted()) { return PushResult.SUCCESS; } else { String rejectionReason response.getRejectionReason(); if (Unregistered.equals(rejectionReason) || BadDeviceToken.equals(rejectionReason)) { // Token 失效标记为无效 deviceTokenService.markTokenInvalid(deviceToken); } return PushResult.TOKEN_INVALID; } } catch (Exception e) { return PushResult.FAILED; } } }4.2 消息在线/离线分流策略Service public class PushDispatcher { public void dispatch(PushMessage message) { // 1. 获取用户所有设备的在线状态 ListDeviceInfo devices userDeviceService.getUserDevices( message.getUserId()); ListDeviceInfo onlineDevices new ArrayList(); ListDeviceInfo offlineDevices new ArrayList(); for (DeviceInfo device : devices) { if (isDeviceOnline(message.getUserId(), device.getDeviceId())) { onlineDevices.add(device); } else { offlineDevices.add(device); } } // 2. 在线设备WebSocket 实时推送 if (!onlineDevices.isEmpty()) { onlinePushService.pushToOnlineUser(message.getUserId(), message); } // 3. 离线设备APNs/FCM 推送 for (DeviceInfo device : offlineDevices) { offlinePushService.pushOffline( message.getUserId(), device.getPushToken(), device.getPlatform(), message); } } }四、推送到达率监控推送到达率是衡量推送系统质量的终极指标。计算公式到达率 客户端收到的消息数 / 服务端发送的消息数监控体系分为三层层级采集点监控内容发送层Dispatcher消息发送总量、在线/离线分流比例通道层APNs/FCM 回调通道投递成功/失败数、Token 失效数客户端层SDK 打点实际收到数、点击打开数三层的漏斗数据每日对账差距超过 5% 即触发排查。常见的到达率下降根因APNs 证书过期忘记轮换、FCM 在大陆的连通率波动需要做国内厂商通道的降级、Token 批量失效App 卸载/重装导致。五、总结消息推送系统的设计哲学是永远假设连接不可靠。长连接会断、Token 会失效、APNs 偶尔丢消息——这些不是异常而是常态。架构上通过在线 WebSocket 离线 APNs/FCM双通道覆盖所有场景路由表存储在 Redis 实现 Gateway 节点的无状态水平扩展三层到达率监控确保问题能在 5 分钟内被发现。后续方向引入国内厂商推送通道华为/小米/OPPO/vivo作为 FCM 在大陆的降级方案利用机器学习预测用户的最佳推送时机提高点击率以及构建推送策略引擎——根据消息类型、用户活跃度和时段动态选择推送通道和频率。

相关新闻

少儿编程入门:从C++基础语法到第一个交互程序实战

少儿编程入门:从C++基础语法到第一个交互程序实战

1. 项目概述:为什么从C开始?如果你正在为孩子寻找一门编程入门语言,或者你自己就是一位希望引导孩子进入编程世界的家长、老师,面对Python、Scratch、C这些选项,可能会有些犹豫。Scratch图形化,上手快&…

2026/10/18 7:30:47 阅读更多 →
AI技术如何重塑现代教育:学生主导的变革

AI技术如何重塑现代教育:学生主导的变革

1. 教育变革的新浪潮:当学生成为技术革命的主角 三年前我在某高校做技术分享时,台下学生还在用纸笔记录;去年再去,满眼都是学生用AI工具实时转录、思维导图同步的场景。这种肉眼可见的变化正在全球校园里加速发生——不同于以往由…

2026/10/18 5:22:07 阅读更多 →
TM4C1299NCZAD CAN控制器原理与实战:从帧结构到消息对象配置

TM4C1299NCZAD CAN控制器原理与实战:从帧结构到消息对象配置

1. CAN控制器核心原理与帧结构深度解析控制器局域网,也就是我们常说的CAN总线,在汽车电子和工业控制领域几乎是“基础设施”一样的存在。它不像我们熟悉的UART或I2C那样简单直接,其设计哲学从一开始就瞄准了高可靠性、实时性和多节点竞争的场…

2026/10/16 5:48:06 阅读更多 →

最新新闻

Typora 1.14.x 最新破解方法!亲测可用

Typora 1.14.x 最新破解方法!亲测可用

经常用Markdown的伙伴们肯定知道,Typora是一款“所见即所得”的Markdown语言编辑器,深受大家喜爱。不过,这款编辑器是以89人人民币/次/3台设备进行一次性费用收取的。作为一名资深白嫖党,在经过一周多的研究后,我成功找…

2026/10/18 7:31:04 阅读更多 →
2026年国内API聚合平台综合评测:版本时效、协议兼容与企业治理避坑指南

2026年国内API聚合平台综合评测:版本时效、协议兼容与企业治理避坑指南

很多开发者选API聚合平台时,眼睛盯着价格,实际踩坑最多的却是另外三件事:模型版本滞后、协议兼容性不过关、企业治理能力缺位。这三件事平日不起眼,一旦进入生产环境就会集中爆发。本文围绕模型覆盖广度、协议支持完整度、稳定性、…

2026/10/18 7:31:04 阅读更多 →
2026年国内Claude聚合平台实测:安全合规与稳定调用深度解析

2026年国内Claude聚合平台实测:安全合规与稳定调用深度解析

Claude 系列模型在国内开发者群体中的热度持续走高,Opus、Sonnet、Haiku 全系都被大量用于代码生成、文档处理与企业知识库场景。本次横评覆盖国内多家主流 Claude 聚合平台,验证场景包括国内网络直连、接口兼容与数据隐私等真实生产环境,从稳…

2026/10/18 7:31:04 阅读更多 →
复试机试二刷复盘:从错题到决策规则,把刷题经验变成提分能力

复试机试二刷复盘:从错题到决策规则,把刷题经验变成提分能力

临近复试的那几周,我几乎把所有空余时间都砸在了 OJ 上。要说最煎熬的,不是第一轮刷题时对着题目发呆的茫然,而是第二轮重刷时发现——当初 AC 过的题,现在连思路都想不起来。这篇复盘笔记,就是在这种被打击感里攒出来…

2026/10/18 7:31:04 阅读更多 →
AI可复现性挑战:原因、排查与实操流程

AI可复现性挑战:原因、排查与实操流程

做AI这行时间久了,会发现一个特别让人崩溃的现象:你照着论文的框架把代码写下来,跑出来的结果却跟论文里漂亮到不行的曲线差了十万八千里。这不是因为你太菜,而是因为AI研究的可复现性挑战,本身就是目前整个行业最头疼…

2026/10/18 7:31:04 阅读更多 →
ESP32分区表完全指南:从partitions.csv到OTA与Flash规划

ESP32分区表完全指南:从partitions.csv到OTA与Flash规划

第一次打开ESP32工程目录,看到partitions.csv这个文件时,我压根没当回事,以为就是个自动生成的临时配置。直到有一次编译报错提醒我“app partition is too small”,我才被迫去搞懂它。搞懂之后回头看,分区表几乎决定了…

2026/10/18 7:30:04 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →