在前面的文章中,我们已经基于 WebSocket 实现了即时聊天应用的实时通信能力,并完成了群聊和私聊功能。但在真实的聊天系统中,仅有实时转发是不够的,消息存储与消息推送是两个必不可少的能力。
消息存储用于支持聊天记录查询和离线消息,而消息推送则保证用户即使暂时不在线,也不会错过重要信息。本文将围绕这两个关键能力,介绍在 Node.js 中的常见实现思路。
一、为什么需要消息存储
在最简单的聊天示例中,消息只存在于内存中,一旦用户断线或服务重启,消息就会丢失。这在真实场景中是不可接受的。
消息存储主要解决以下问题:
- 用户可以查看历史聊天记录
- 支持用户离线后重新上线查看消息
- 支持多端登录时的数据同步
- 为后续搜索和统计提供基础
因此,聊天系统必须将消息持久化存储。
二、消息数据模型设计
在开始编码之前,需要先设计消息的基本结构。
1. 消息核心字段
一条聊天消息通常包含:
- 消息 ID
- 发送者 ID
- 接收者 ID 或群 ID
- 消息类型(私聊 / 群聊)
- 消息内容
- 发送时间
- 消息状态(已读 / 未读)
这些字段可以满足绝大多数聊天场景。
2. 数据库存储方案选择
常见的存储方式包括:
- 关系型数据库(MySQL / PostgreSQL)
- 文档数据库(MongoDB)
- 缓存数据库(Redis,用于未读消息)
在 Node.js 项目中,常见组合是:
- 数据库负责长期存储
- Redis 负责临时缓存和推送辅助
三、消息入库流程设计
当服务器收到一条聊天消息时,通常执行以下流程:
- 校验消息合法性
- 写入数据库
- 判断接收方是否在线
- 在线则实时推送
- 不在线则标记为未读
这种设计可以保证消息不丢失。
四、消息存储实现思路
1. 私聊消息存储
私聊消息通常存储为一条独立记录,包含发送者和接收者信息。
示例逻辑:
async function savePrivateMessage(message) {
await Message.create({
from: message.from,
to: message.to,
content: message.content,
type: 'private',
created_at: new Date()
});
}
数据库中可通过 from / to 字段快速查询历史消息。
2. 群聊消息存储
群聊消息通常只存一条记录,但关联群 ID。
async function saveGroupMessage(message) {
await Message.create({
from: message.from,
room_id: message.roomId,
content: message.content,
type: 'group',
created_at: new Date()
});
}
查询群聊记录时,根据 room_id 过滤即可。
五、历史消息查询
为了支持聊天记录展示,通常需要提供 HTTP API 查询历史消息。
常见设计方式:
- 私聊:根据两个用户 ID 查询
- 群聊:根据群 ID 查询
- 支持分页,避免一次返回大量数据
这种接口通常由 HTTP 服务提供,而不是 WebSocket。
六、离线消息处理
1. 离线消息的判定
服务器可以通过在线用户映射判断:
- 接收方在线 → 直接推送
- 接收方离线 → 存储为未读消息
未读状态通常需要在数据库或 Redis 中标记。
2. 离线消息推送时机
当用户重新上线时:
- 查询未读消息
- 按时间顺序推送给客户端
- 更新消息状态为已读
这种方式保证消息不会丢失。
七、消息推送机制设计
1. WebSocket 实时推送
对于在线用户,消息通过 WebSocket 即时推送:
- 延迟低
- 体验好
- 适合聊天场景
这是聊天系统的主要推送方式。
2. 与 HTTP 接口配合
WebSocket 负责实时消息,而 HTTP 接口负责:
- 历史消息查询
- 未读消息统计
- 消息状态更新
二者配合,可以让系统结构更加清晰。
八、性能与扩展性考虑
当用户规模增大后,需要考虑以下问题:
- 消息表数据量快速增长
- 查询性能下降
- 单节点 WebSocket 服务压力增大
常见优化方向包括:
- 消息表按时间或用户分表
- 使用 Redis 缓存最近消息
- WebSocket 服务集群化
- 使用消息队列进行跨节点广播
这些问题在系统早期设计阶段就应有所预期。
九、总结
消息存储与推送是即时聊天系统中不可或缺的核心能力。通过合理的数据模型设计、清晰的消息流程和 WebSocket + HTTP 的协作方式,可以构建一个稳定、可扩展的聊天系统。
在《Node.js 编程实战》系列中,即时聊天项目不仅展示了 WebSocket 的使用方式,也涵盖了真实系统中常见的消息持久化和推送场景。