前言
市面上大部分实时聊天教程仅单机 Demo,存在无法集群部署、断线丢失消息、无离线消息、跨服务用户无法互通、无心跳保活、消息重复乱序等致命问题,不能直接上线。 本文提供一套中小企业通用成熟 IM 架构,基于SpringBoot + Socket.IO + Redis Pub/Sub + RabbitMQ + MySQL + Nginx,完整实现私聊、群聊、在线状态、离线消息、已读回执、断线重连、集群高可用、消息可靠投递,所有配置、表结构、核心代码均可直接复制用于项目开发,覆盖开发、测试、云服务器部署全流程。
一、技术栈选型与选型理由
整体技术栈
- 前端:Vue3 + Vite + socket.io-client
- 后端网关:SpringBoot 2.7 + socket.io-spring-boot-starter
- 消息分发中间件:Redis(Pub/Sub 跨节点广播、在线状态缓存、心跳管理)
- 异步削峰中间件:RabbitMQ(消息持久化、异步入库、死信兜底)
- 持久化存储:MySQL 8.0(聊天记录、好友、群组、用户基础数据)
- 接入层:Nginx(负载均衡、WebSocket 协议升级、HTTPS、限流)
- 部署环境:CentOS7/8 云服务器、Docker 一键部署
各组件选型原因
- Socket.IO 替代原生 WebSocket 原生 WebSocket 无自动断线重连、无心跳检测、无房间分组、网络弱网无降级方案,自研容错逻辑成本极高;Socket.IO 内置心跳、重连、房间、ACK 回执,前端后端 API 统一,大幅降低开发成本。
- Redis Pub/Sub 解决集群互通核心痛点 多台 IM 服务实例维护独立长连接,不同服务下用户无法互相推送消息;Redis 发布订阅实现全服务消息广播,任意节点消息同步至全部实例。
- RabbitMQ 保证消息不丢失 聊天消息先投递 MQ 异步入库,避免同步写 MySQL 阻塞长连接;支持消息持久化、重试、死信队列,防止数据库宕机丢失聊天记录。
- 分层存储设计 MySQL 永久存储全量聊天记录;Redis 缓存在线状态、未读消息、临时会话,高频查询走内存,降低 DB 压力。
- Nginx 负载均衡 统一域名入口,完成 WebSocket 协议升级,分发长连接至多台 IM 服务,单机上限 3-5 万在线用户,集群可横向扩容支撑十万级在线。
不推荐方案对比
- 轮询 / 长轮询:延迟高、服务器带宽消耗大,淘汰;
- SSE:仅服务端单向推送,无法双向聊天;
- MQTT:多用于物联网设备,缺少 IM 房间、已读、离线消息配套能力;
- 第三方 IM SDK(环信 / 融云):私有化部署成本高,业务逻辑无法自定义,数据存在第三方风险。
二、系统整体分层架构
四层架构(生产标准分层)
- 接入层 Nginx
- SSL 证书统一处理,80 端口跳转 HTTPS
- WebSocket 协议升级配置,支持长连接转发
- 负载均衡分发用户连接至多 IM 服务节点
- IP 限流、防盗链、恶意请求拦截
- 网关层 IM Socket 服务集群(多实例)
- 维护客户端长连接,心跳检测、断线重连处理
- 房间管理、私聊 / 群聊消息接收、参数校验、敏感词过滤
- Redis 读写:在线状态、未读消息、Pub/Sub 消息广播订阅
- 消息投递至 RabbitMQ 异步队列
- 异步消息层 RabbitMQ
- 消息持久化,削峰填谷,高并发聊天不阻塞网关服务
- 消费消息写入 MySQL,处理离线消息存储
- 死信队列存储处理失败消息,定时重试
- 数据持久层 Redis + MySQL
- Redis:在线用户映射、心跳过期键、群成员缓存、未读消息列表
- MySQL:用户、好友关系、群组、群成员、全量聊天记录
消息流转流程(一对一私聊)
- 用户 A 前端建立 Socket 连接,携带 token 登录认证;
- 后端校验 token,Redis 写入在线记录
user:online:{userId} = 当前服务实例ID,设置 30s 过期; - A 发送私聊消息,携带唯一 requestId、接收人 userId、消息内容;
- 后端校验消息长度、敏感词、限流规则;
- 封装完整消息体,投递 RabbitMQ 消息队列做持久化兜底;
- 消息通过 Redis Pub/Sub 发布至全局聊天频道;
- 所有 IM 服务实例订阅频道,匹配接收人在线状态;
- 若接收人 B 连接在当前实例,直接 Socket 推送消息至前端;
- B 前端收到消息,返回
ack回执给服务端; - 服务端收到回执,更新 MySQL 消息为已读;
- 若 B 离线,消息存入 Redis 离线未读列表,用户上线一次性拉取。
群聊消息流转流程
- 用户发送群消息,携带 groupId;
- 查询该群全部成员 userId 列表;
- 消息投递 RabbitMQ,Redis Pub/Sub 广播;
- 各服务实例遍历本地在线用户,匹配群成员推送;
- 离线群成员存入离线消息池,上线统一拉取。
三、数据库表完整设计(可直接执行建表语句)
1. 用户聊天消息表 chat_message(核心表)
存储所有私聊、群聊消息,区分单聊 / 群聊,记录已读状态
sql
CREATE TABLE `chat_message` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键消息ID',
`request_id` varchar(64) NOT NULL COMMENT '客户端唯一请求ID,幂等防重复',
`from_user_id` bigint NOT NULL COMMENT '发送人ID',
`to_user_id` bigint DEFAULT NULL COMMENT '私聊接收人,群聊为null',
`group_id` bigint DEFAULT NULL COMMENT '群聊ID,私聊为null',
`msg_type` tinyint NOT NULL DEFAULT 1 COMMENT '1文本 2图片 3文件 4语音',
`content` text NOT NULL COMMENT '消息内容',
`is_read` tinyint NOT NULL DEFAULT 0 COMMENT '0未读 1已读',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
PRIMARY KEY (`id`),
UNIQUE KEY uk_request_id (`request_id`),
INDEX idx_from_user (`from_user_id`),
INDEX idx_to_user (`to_user_id`),
INDEX idx_group (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '聊天消息记录表';
2. 好友关系表 user_friend
sql
CREATE TABLE `user_friend` (
`id` bigint AUTO_INCREMENT PRIMARY KEY,
`user_id` bigint NOT NULL COMMENT '用户ID',
`friend_id` bigint NOT NULL COMMENT '好友ID',
`remark` varchar(50) DEFAULT '' COMMENT '好友备注',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_friend (`user_id`,`friend_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 群组表 chat_group
sql
CREATE TABLE `chat_group` (
`id` bigint AUTO_INCREMENT PRIMARY KEY,
`group_name` varchar(100) NOT NULL COMMENT '群名称',
`owner_id` bigint NOT NULL COMMENT '群主ID',
`avatar` varchar(255) DEFAULT '' COMMENT '群头像',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4. 群成员表 group_member
sql
CREATE TABLE `group_member` (
`id` bigint AUTO_INCREMENT PRIMARY KEY,
`group_id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`member_remark` varchar(50) DEFAULT '' COMMENT '群内昵称',
`join_time` datetime DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_group_user (`group_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
四、Redis Key 设计规范(缓存核心逻辑)
- 在线用户状态
key: user:online:{userId}value: 当前 IM 服务实例 ID 过期时间 30s,客户端 25s 发送心跳自动续期,超时自动判定离线。 - 用户离线未读消息集合
key: user:offline:msg:{userId}Set 集合,存储 messageId 用户上线批量查询,拉取完整消息后清空集合。 - 群成员在线缓存
key: group:online:{groupId}Set,缓存群内在线用户 ID,减少 DB 查询。 - 全局消息广播频道 频道名:chat_global_channel,所有 IM 服务统一订阅。
- 限流计数器
key: limit:msg:{userId}计数器,限制单用户每秒发送消息上限。
五、核心功能实现细节与代码片段
5.1 前端 Socket 连接、断线重连封装(Vue3)
安装依赖
shell
npm install socket.io-client
封装 socket 工具类
javascript
运行
import { io } from 'socket.io-client'
let socket = null
const SOCKET_URL = import.meta.env.VITE_SOCKET_URL
export function initSocket(token) {
// 自动重连、重连间隔、最大重试次数
socket = io(SOCKET_URL, {
auth: { token },
autoConnect: false,
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 2000,
transports: ['websocket']
})
// 手动连接
socket.connect()
// 连接成功
socket.on('connect', () => {
console.log('聊天连接成功')
})
// 断线重连
socket.on('disconnect', (reason) => {
console.log('连接断开', reason)
})
// 接收私聊消息
socket.on('private_msg', (msg) => {
// 渲染聊天列表,存储本地
})
// 接收群消息
socket.on('group_msg', (msg) => {
})
// 上线拉取离线消息
socket.on('offline_msg_list', (list) => {
})
// 心跳响应
socket.on('pong', () => {})
return socket
}
// 发送私聊消息
export function sendPrivateMsg(toUserId, content, requestId) {
socket.emit('send_private', {
toUserId,
content,
requestId,
msgType: 1
}, (ack) => {
// 后端回执,确认发送成功
})
}
// 心跳保活,25s一次
export function startHeartBeat() {
setInterval(() => {
socket.emit('ping')
}, 25000)
}
5.2 SpringBoot 后端 Socket.IO 核心配置
引入 Maven 依赖
xml
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
Socket 服务配置类
java
运行
@Configuration
public class SocketIoConfig {
@Bean
public SocketIOServer socketIOServer() {
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
config.setHostname("0.0.0.0");
config.setPort(9090);
// 允许跨域
config.setAllowCustomRequestsOrigin(true);
config.setOrigin("*");
SocketIOServer server = new SocketIOServer(config);
return server;
}
}
5.3 登录认证、在线状态、心跳逻辑
java
运行
@Component
public class SocketAuthHandler {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private SocketIOServer socketIOServer;
// 客户端连接认证
@PostConstruct
public void initAuth() {
socketIOServer.addConnectListener(client -> {
String token = client.getHandshakeData().getAuthToken().get("token").toString();
// 校验token,获取userId
Long userId = getUserIdByToken(token);
if(userId == null) {
client.disconnect();
return;
}
// 绑定用户与客户端连接
client.set("userId", userId);
// 写入在线缓存,30秒过期
String onlineKey = "user:online:" + userId;
redisTemplate.opsForValue().set(onlineKey, ServerUtil.getServerId(), 30, TimeUnit.SECONDS);
});
// 心跳ping事件
socketIOServer.addEventListener("ping", Void.class, (client, data, ackSender) -> {
Long userId = client.get("userId");
String onlineKey = "user:online:" + userId;
// 续期30秒
redisTemplate.expire(onlineKey, 30, TimeUnit.SECONDS);
client.sendEvent("pong");
});
// 客户端断开连接
socketIOServer.addDisconnectListener(client -> {
Long userId = client.get("userId");
if(userId != null) {
redisTemplate.delete("user:online:" + userId);
}
});
}
}
5.4 Redis Pub/Sub 跨节点消息广播实现
发布消息工具类
java
运行
@Service
public class ChatPubService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void publishChatMsg(ChatMsgDTO dto) {
redisTemplate.convertAndSend("chat_global_channel", dto);
}
}
全局消息订阅监听
java
运行
@Component
public class ChatSubListener {
@Autowired
private SocketIOServer socketIOServer;
@Autowired
private OfflineMsgService offlineMsgService;
@RedisListener(channel = "chat_global_channel")
public void receiveMsg(ChatMsgDTO dto) {
// 私聊消息处理
if(dto.getToUserId() != null) {
boolean online = redisTemplate.hasKey("user:online:" + dto.getToUserId());
if(online) {
// 推送在线用户
socketIOServer.getClientOperations().sendEvent("private_msg", dto.getToUserId().toString(), dto);
} else {
// 存入离线消息
offlineMsgService.saveOfflineMsg(dto.getToUserId(), dto.getId());
}
}
// 群聊逻辑省略
}
}
六、Nginx 完整配置(WebSocket 负载均衡 + HTTPS)
6.1 http 基础配置(nginx.conf http 块)
nginx
http {
server_tokens off;
# 长连接缓存
upstream chat_server_cluster {
server 127.0.0.1:9090 weight=1;
server 127.0.0.1:9091 weight=1;
ip_hash; # 同一用户固定分发同一服务,避免频繁切换连接
}
# 全局开启gzip
gzip on;
gzip_min_length 1k;
gzip_types text/plain application/json application/javascript;
}
6.2 HTTPS 站点配置 conf.d/chat.conf
nginx
# http 80跳转https
server {
listen 80;
server_name chat.xxx.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name chat.xxx.com;
ssl_certificate /etc/nginx/ssl/chat.pem;
ssl_certificate_key /etc/nginx/ssl/chat.key;
ssl_protocols TLSv1.2 TLSv1.3;
# WebSocket升级核心配置
location /socket.io/ {
proxy_pass http://chat_server_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 300s;
proxy_read_timeout 300s; # 长连接超时时间,必须大于心跳周期
}
}
关键说明:proxy_read_timeout 设置 300 秒,避免无数据传输时 Nginx 主动断开长连接。
七、RabbitMQ 消息队列配置(可靠投递)
交换机与队列规划
- 普通聊天交换机:chat_exchange
- 消息持久化队列:chat_msg_queue
- 死信队列:chat_msg_dlq(处理失败消息重试)
消息生产消费核心规则
- 消息投递开启持久化,服务重启不丢失;
- 消费者手动 ACK,处理完成才确认删除消息;
- 入库失败消息转发死信队列,定时任务重试;
- 高并发峰值自动削峰,不会阻塞 Socket 长连接网关。
八、生产环境核心问题解决方案
8.1 跨服务用户无法互通
解决方案:Redis Pub/Sub 全局广播,所有服务实例统一订阅频道,消息全集群同步。
8.2 断线丢失消息
- 客户端发送消息等待后端 ACK 回执,无回执自动重发;
- 消息先存入 MQ 持久化,再推送用户;
- 离线消息存入 Redis,上线批量拉取。
8.3 消息重复推送、重复入库
每条消息携带唯一requestId,数据库设置唯一索引,重复插入直接忽略;消费 MQ 时判断消息是否已处理。
8.4 在线状态不准确、假在线
心跳 25s 上报,Redis30s 过期,客户端异常断开、服务宕机自动清除在线 key,无残留脏数据。
8.5 单服务在线连接上限
单机支撑 3-5 万在线用户,新增服务实例加入 Nginx 集群,横向扩容无上限。
8.6 前端刷新页面丢失会话
前端本地缓存 token,页面刷新自动重连 Socket,重连成功自动拉取离线消息、恢复房间。
8.7 高并发刷屏打垮数据库
- Redis 限流,单用户每秒最多 3 条消息;
- 消息异步 MQ 入库,削峰;
- 聊天记录分表,按月分表降低单表数据量。
九、云服务器完整部署流程
- 云服务器安全组放行端口:22 (ssh)、80、443、9090;
- 安装 Docker、Redis、RabbitMQ、MySQL;
- 前端打包 dist 部署 Nginx 静态页面;
- 后端 SpringBoot 打包 Jar,多端口启动多实例(9090、9091);
- 配置 Nginx 负载均衡与 WebSocket 转发;
- 域名解析至服务器公网 IP,上传 SSL 证书;
- 校验 Nginx 配置
nginx -t,重启 Nginx; - 启动 IM 服务集群,测试私聊、群聊、断线重连、离线消息。
十、项目扩展优化方向
- 文件 / 图片上传:对接 OSS 对象存储,消息仅存储文件 URL;
- 音视频通话:集成 WebRTC,基于现有 Socket 做信令传输;
- 消息撤回、已读、多端同步;
- 敏感词过滤:接入词库,发送消息前拦截违规内容;
- 消息归档:定时任务迁移历史消息至归档分表,优化主表查询速度;
- 监控告警:接入 Prometheus 监控在线人数、消息 TPS、MQ 堆积、服务宕机告警。
结语
本套架构是中小企业 IM 项目标准落地方案,完全规避单机 Demo 各类缺陷,具备高并发、高可用、消息可靠、集群扩容能力。开发顺序建议:先完成 Socket 连接与心跳在线状态 → 实现 Redis Pub/Sub 跨节点通信 → MQ 异步消息持久化 → 离线消息处理 → Nginx 集群部署。所有代码、SQL、Nginx 配置可直接复制投入项目开发,适配私有化部署、小程序、Web 后台多端聊天场景。