
即时通讯系统看起来只是“发送一条消息再推送给接收方”但真正进入多端、集群和高并发环境后消息链路会迅速变得复杂。同一名用户可能同时登录安卓、iOS、H5和PC端消息类型也不再局限于文字还包括图片、文件、自定义表情、红包、名片、引用回复、合并转发、音视频邀请等内容。此时系统需要处理的不只是实时推送还包括消息是否重复、顺序是否一致、离线后能否补回、多个终端状态是否同步以及节点扩容后如何找到用户连接。基于Java Spring Boot、Redis、MySQL和WebSocket构建多端即时通讯系统中的可靠消息链路。01 从一条消息开始拆解IM系统先看一个普通的群聊场景。用户通过安卓端向群聊发送一条消息张明 请查看刚刚上传的项目文件这条消息背后可能同时包含文本内容群成员文件引用发送人信息群会话标识客户端消息编号服务端消息编号会话序列号发送时间消息状态。如果接收方正在使用PC端消息需要立即推送到Vue2页面如果另一名成员处于离线状态消息还要进入离线同步范围。发送者本人如果同时登录H5端H5也需要同步显示刚发送的内容。因此即时通讯系统中的一条消息至少要完成四件事接收、存储、路由、同步。任何一个环节处理不完整都会出现消息丢失、重复、乱序或多端状态不一致。02 多端统一的消息结构安卓、苹果和H5端可以通过uni-app开发PC端使用Vue2后端采用Spring Boot。不同客户端虽然界面实现不同但必须使用统一的消息模型。一个基础消息体可以设计为{ clientMessageId: android_10086_1720589100123, messageId: 20260710000192384, conversationId: group_30001, senderId: user_10086, receiverId: group_30001, messageType: 1, sequence: 98231, sendTime: 1783645200123, content: { text: 请查看刚刚上传的项目文件, atUserIds: [user_20001], replyMessageId: 20260710000191001 } }其中几个字段承担着不同职责。字段主要用途clientMessageId客户端生成用于消息幂等messageId服务端生成的全局消息编号conversationId标识单聊或群聊会话messageType区分文本、文件、红包、名片等类型sequence维护会话内消息顺序atUserIds记录群聊中被的成员replyMessageId关联被引用回复的消息这套结构不只适用于文本消息。文件、红包、名片分享、自定义表情、语音通话邀请和多人会议通知都可以通过扩展messageType和content完成。例如文件消息可以保存文件名称 文件大小 文件格式 资源地址 缩略图地址 上传状态消息表中只保留文件元数据不直接保存文件二进制内容从而避免数据库记录过大。03 WebSocket负责实时但不负责全部可靠性WebSocket能够建立长连接适合单聊、群聊、已读回执、消息撤回等实时事件。但WebSocket连接成功并不意味着消息一定可靠。一次消息发送过程中可能出现以下异常情况一客户端已发送服务端未落库网络请求已经到达服务端但数据库写入失败。此时如果直接向客户端返回成功消息会永久丢失。情况二服务端已经保存确认包没有返回客户端没有收到发送成功通知于是重新发送服务端可能插入两条重复消息。情况三接收端突然掉线消息已经生成并持久化但接收方连接中断实时推送失败需要等待重新登录后补拉。情况四多个终端状态不一致手机端已经阅读消息PC端却仍显示未读数量或者手机端撤回消息后H5端仍显示原内容。所以WebSocket只是消息传输通道可靠性还需要持久化、幂等、序列号和状态同步共同完成。04 一条可靠消息的完整处理链路可以把消息发送过程拆成三个阶段。第一阶段客户端提交客户端生成临时消息编号在本地先显示“发送中”状态再通过Socket或WebSocket发送到服务端。第二阶段服务端确认服务端依次执行身份认证 ↓ 会话权限校验 ↓ 重复消息校验 ↓ 生成服务端消息ID ↓ 分配会话序列号 ↓ 写入持久化存储 ↓ 返回发送确认客户端只有收到服务端确认后才能把消息状态调整为“已发送”。第三阶段消息分发消息落库完成后服务端查询接收方在线节点接收方在线 → 推送到对应连接节点 接收方离线 → 保留在离线同步范围 发送者多端在线 → 同步到其他登录设备这套流程能够保证即使实时推送失败消息仍然可以依靠历史记录和离线同步恢复。05 Redis幂等控制避免重复消息移动网络不稳定时客户端通常会设置超时重试。比如用户发送文件消息后五秒内没有收到确认客户端再次提交同一请求。如果服务端没有幂等控制聊天窗口会出现两条完全相同的消息。解决方式是由客户端生成clientMessageId服务端使用发送人ID和客户端消息编号组成唯一键。Service public class MessageIdempotentService { private final StringRedisTemplate redisTemplate; public MessageIdempotentService( StringRedisTemplate redisTemplate) { this.redisTemplate redisTemplate; } public boolean tryAccept( String senderId, String clientMessageId) { String key im:message:idempotent: senderId : clientMessageId; Boolean success redisTemplate.opsForValue() .setIfAbsent( key, 1, Duration.ofMinutes(10) ); return Boolean.TRUE.equals(success); } }第一次请求进入时Redis中不存在该键写入成功。客户端再次重试时setIfAbsent返回失败服务端便可以识别为重复消息。这里不能使用消息正文判断重复。因为用户连续发送两次“收到”可能是合法操作。只有客户端消息编号才能明确区分请求是否相同。涉及钱包和红包时需要双重幂等红包、钱包余额、付费礼物等业务不能只依赖Redis。Redis缓存可能被清除也可能在节点故障时出现异常因此数据库层还应增加唯一约束。ALTER TABLE im_wallet_record ADD UNIQUE KEY uk_business_no (business_no);当同一业务流水重复写入时MySQL唯一索引能够阻止二次入账。可以将处理逻辑分为两层Redis快速拦截重复请求 MySQL保证最终业务唯一这种方式同样适用于重复领取红包、重复提交转账和重复确认支付。06 用会话序列号处理乱序和断线补偿消息时间不能完全依赖客户端生成。原因很简单不同设备的系统时间可能不一致网络延迟也可能导致后发送的消息先到达服务端。因此每个会话都应维护独立、连续递增的序列号。例如group_30001 消息A sequence 98231 消息B sequence 98232 消息C sequence 98233客户端按sequence排序而不是只依赖本地时间。Redis中的原子自增适合生成序列号public long nextSequence(String conversationId) { String key im:conversation:sequence: conversationId; Long sequence redisTemplate.opsForValue() .increment(key); if (sequence null) { throw new IllegalStateException( 会话序列号生成失败 ); } return sequence; }序列号还可以解决断线后的消息恢复。假设客户端最后保存的序列号是98220服务端当前序列号是98233那么客户端缺少的消息范围就是98221—98233重新连接后只需要按序列区间拉取缺失记录不必重新同步整个会话。序列号还能支撑哪些功能序列号不只是排序字段还能参与多个模块离线消息补拉历史消息漫游已读未读计算跨端同步撤回消息定位引用回复定位消息缺口检测。当用户在PC端登录新设备时也可以根据服务端会话游标逐步恢复聊天记录。07 Redis如何完成集群节点路由单机部署时所有用户连接都存在同一个Java进程中。服务端收到消息后可以直接从内存连接表中找到接收方。但进入多机部署后情况会变成用户A → gateway-01 用户B → gateway-03 用户C → gateway-05用户A向用户B发送消息时gateway-01并没有用户B的本地连接。因此需要在Redis中记录用户和节点之间的映射关系。im:online:user_1001:android → gateway-01 im:online:user_1001:pc → gateway-02 im:online:user_2001:ios → gateway-03消息服务收到请求后先查询接收方位于哪个节点再将消息推送给对应网关。如果用户同时登录多个终端则需要返回多个节点和设备记录。多端同步不能只推送给接收方用户在安卓端发送消息时自己的PC端和H5端也应同步显示。所以消息路由对象通常包括两部分接收方所有在线终端 发送方除当前设备外的其他在线终端这样才能保证手机、网页和桌面端聊天记录一致。类似的跨端同步事件还包括消息已读消息撤回会话置顶消息收藏好友关系变化群成员变化红包领取状态未读数更新。08 群聊消息不能同步遍历全部成员小群聊只有十几人时发送消息后逐个推送似乎没有明显问题。但当群成员数量扩大后每发送一条消息都同步遍历成员会产生明显压力。问题主要集中在三个方面业务线程被长期占用成员查询次数增加在线推送与离线处理混在一起。更合适的群聊消息流程是消息落库 ↓ 生成群消息事件 ↓ 拆分群成员或节点分片 ↓ 异步推送在线用户 ↓ 离线用户后续补拉群聊发送接口只负责完成核心校验和消息保存不等待全部成员推送成功才返回。群管理权限应优先读取缓存群聊消息发送前需要判断发送人是否仍在群内是否开启全员禁言当前成员是否被单独禁言是否允许普通成员发文件是否具备全体成员权限名片分享是否符合群权限。群管理员、禁言状态和群成员热点信息可以缓存在Redis中。MySQL负责保存最终数据Redis负责高频权限读取。当管理员移除群成员时应同时执行更新MySQL群成员关系 清理Redis群成员缓存 通知成员所在在线节点 广播群系统消息否则被移除的成员可能因为旧缓存仍然继续发送消息。09 MySQL保存事实数据Redis保存实时状态Redis速度快但不适合永久承担全部消息存储。比较清晰的职责划分是MySQL保存消息正文会话记录好友关系群成员关系红包流水钱包流水收藏记录朋友圈内容历史文件记录。Redis保存在线状态用户所在网关会话序列号幂等键未读计数缓存群权限热点数据临时令牌限流数据。消息表可以包含message_id conversation_id sequence sender_id receiver_id message_type message_content send_time message_status当消息量不断增长时可以根据会话ID进行分表。例如im_message_00 im_message_01 im_message_02 ... im_message_31通过conversationId计算哈希值将同一会话尽量写入固定分片。这样查询历史记录时可以直接定位到对应消息表。10 已读未读不要逐条更新很多即时通讯系统初期会为每条消息保存一个“已读”字段。单聊消息量较少时还能运行但群聊和多端同步场景下逐条更新会产生大量数据库操作。更轻量的做法是记录用户在某个会话中的最大已读序列号。例如用户当前已读到6532{ conversationId: single_1001_1002, readSequence: 6532 }那么序列号小于或等于6532的消息都可以视为已读。未读范围则是用户最大已读序列号之后 到 会话最新序列号之间这种方式不需要为每条消息单独执行更新。群聊已读需要控制数据规模群聊中如果为每条消息保存每个成员的已读记录数据量会快速增加。可以根据实际需求采用不同策略场景建议方式普通群聊保存成员最大已读序列大型群聊只维护未读数量群公告保存详细已读人员消息单独记录提醒状态重要通知保存送达和已读回执手机端阅读消息后服务端还要把新的readSequence同步给PC和H5端使其他设备及时清除未读标识。11 消息撤回、引用回复和合并转发这三类功能看起来都作用于历史消息但实现方式不同。消息撤回撤回操作不应直接删除数据库原记录。更适合的方式是修改状态NORMAL → REVOKED随后向所有在线终端广播撤回事件。离线客户端重新拉取历史消息时读取到撤回状态再显示“该消息已被撤回”。引用回复引用回复可以在新消息中保存replyMessageId 被引用人 原消息类型 原内容摘要即使原消息位于很早的聊天记录中客户端也能根据消息ID快速定位。如果原消息已撤回引用区域可以保留位置但不继续展示完整内容。合并转发合并转发不是把多条消息直接拼成长文本。更合适的消息体是转发标题 消息数量 消息摘要列表 原消息ID集合 参与人信息当内容包含文件、语音、图片或名片时客户端根据原始消息类型组合展示。这种设计能够避免单条消息体过大也便于后续扩展预览页面。12 Socket、WebSocket和HTTP如何共存不同终端对通信协议的要求不同。安卓和苹果可以使用自定义Socket协议H5和PC浏览器适合WebSocket文件上传、朋友圈加载、群管理接口适合HTTP。但协议层不应该分别实现一套业务。可以将整体结构拆分为Socket接入层 ─────┐ │ WebSocket接入层 ──┼→ 统一消息命令 → 消息业务服务 │ HTTP接口层 ───────┘接入层只处理连接建立心跳检测数据解码身份认证协议适配。完成解码后所有请求转换为内部统一命令再进入Spring Boot业务服务。这样好友、单聊、群聊、红包、撤回、收藏等逻辑不会与某一种网络协议绑定。一个端口识别多种协议当一个端口支持多协议接入时可以在连接建立阶段判断数据特征。例如包含HTTP Upgrade头 → WebSocket 包含自定义协议魔数 → Socket协议 普通HTTP请求 → HTTP接口如果后续需要分别扩容也可以把三种协议拆成独立节点。浏览器连接增加时单独扩容WebSocket服务文件上传增长时单独扩容HTTP节点。13 音视频通话不应传输在普通消息通道中语音通话、视频通话和多人音视频会议会产生大量媒体流。这些数据不适合直接通过普通聊天WebSocket传输否则容易影响文本消息、红包通知和文件状态等实时事件。IM系统在音视频场景中主要负责信令发起邀请 接听或拒绝 进入房间 成员加入 成员退出 开启或关闭麦克风 开启或关闭摄像头 主持人操作 会议结束真正的音频和视频数据交给专门的实时音视频服务。会议邀请仍然可以使用统一消息模型多人音视频会议邀请可以定义为特殊消息{ messageType: 3001, conversationId: group_30001, content: { roomId: meeting_98231, callType: video, status: inviting, memberIds: [ user_1002, user_1003 ] } }成员接听后进入对应房间。会议结束时再发送结束事件并记录通话时长和参与成员。这样聊天记录中仍然能够展示会议邀请、未接听、已拒绝和已结束状态。14 SSL/TLS加密与身份校验聊天消息中可能包含文件、名片、红包、钱包状态和好友关系因此传输层需要启用SSL/TLS。不同协议对应的加密方式包括WebSocket → WSS HTTP → HTTPS 自定义Socket → TLS加密通道建立连接后客户端必须携带有效身份令牌。服务端不能因为客户端上传了userId就直接认定连接属于该用户。还需要校验Token是否有效账号是否被禁用设备是否允许登录会话是否具备发送权限用户是否仍在群聊中当前是否处于禁言状态文件大小与格式是否合法消息撤回是否超时。扫一扫不能直接暴露用户ID扫一扫添加好友或加入群聊时二维码中不建议直接写入原始用户ID或群ID。可以生成短期有效的随机凭证二维码随机Token ↓ 服务端查询对应用户或群聊 ↓ 校验有效期和使用权限 ↓ 执行添加好友或加入群聊这样能够降低二维码长期传播和参数被修改带来的风险。15 从单机部署扩展到多机集群初期即时通讯系统可以使用单机部署客户端 ↓ Spring Boot服务 ↓ Redis MySQL但随着在线人数增长连接层、业务层和存储层需要逐步拆分。一种常见结构是安卓 / iOS / H5 / PC ↓ 负载均衡 ↓ Socket与WebSocket网关 ↓ 消息业务服务 ↓ Redis集群 / MySQL数据库连接网关负责保存长连接。消息业务服务负责消息校验消息持久化好友关系群权限红包和钱包收藏与朋友圈在线路由查询。长连接扩容与普通HTTP不同HTTP请求执行完成后连接可以释放而IM长连接需要长期保留。因此用户连接到某个网关后必须把节点关系写入Redis。当网关节点发生故障时客户端执行自动重连连接断开 ↓ 切换其他可用网关 ↓ 重新完成身份认证 ↓ 上报本地最大序列号 ↓ 补拉缺失消息这套流程使节点故障不会直接造成历史消息丢失。16 高并发连接需要关注的并不只是Java代码“单机支持多少在线连接”不能只看服务器内存。实际连接能力还会受到以下因素影响操作系统文件句柄网络缓冲区心跳频率每条消息大小是否使用阻塞线程TLS加密开销消息持久化速度Redis访问频率网关节点硬件配置。高并发连接层通常采用事件驱动模型而不是为每个用户创建一个独立线程。Spring Boot可以负责业务组织、接口管理和模块编排长连接层则使用异步网络模型处理大量Socket或WebSocket连接。当压力继续增加时可以按模块独立扩容Socket网关独立扩容 WebSocket网关独立扩容 HTTP接口独立扩容 消息服务独立扩容 文件服务独立扩容所有节点通过Redis共享在线路由和实时状态通过MySQL保存最终业务数据。17 从底层链路看功能模块如何接入当消息模型、序列号、在线路由和持久化链路稳定后业务功能可以逐步接入而不需要反复修改通信核心。例如好友功能 → 建立单聊会话关系 群聊与群管理 → 复用群成员权限和群消息链路 红包与钱包 → 复用消息通知增加资金流水幂等 表情与自定义表情 → 扩展消息类型和资源地址 文件发送 → 上传文件后发送文件消息 收藏 → 记录用户与原消息之间的关联 朋友圈 → 复用用户关系、评论通知和资源存储 语音视频通话 → 使用IM传输邀请和状态信令 多人会议 → 使用群成员关系生成会议邀请范围这些模块表面上差异很大但最终都依赖几项基础能力身份认证、消息编号、会话关系、状态同步和可靠存储。底层通信链路保持统一后安卓、苹果、H5和PC端只需根据消息类型完成不同界面展示服务端也能在保持轻量结构的同时继续扩展集群部署、独立部署和后续业务模块。技术实现思路的简要归纳说明宠友(IM即时通讯)app,支持语音、文件、图片、视频等多种类型消息的发送,安全可靠,交流轻松,私有化部署,快速开发极简部署支持群聊管理、语音视频通话...功能https://chongyou.info/1/product/im.html