1) 【一句话结论】
百万级移动端即时消息系统,核心采用分布式消息队列(如RocketMQ)作为消息中转,结合按用户分库的MySQL持久化存储(主存储)与Redis缓存(加速),通过消息持久化、幂等处理、重试机制保障可靠性,水平扩展消息队列和存储集群实现可扩展性,并设计已读回执机制确保状态同步,群聊消息通过消息队列分区保证顺序,整体架构兼顾实时性、可靠性与可扩展性。
2) 【原理/概念讲解】
老师解释关键概念:
- 分布式消息队列(如RocketMQ):解耦发送与接收,削峰填谷。类比“快递中转站”——发送方将消息放入中转站,接收方从中转站取,即使一方宕机,中转站保证消息不丢失,支持水平扩展(增加Broker节点)。
- 消息持久化存储(MySQL):将消息写入磁盘,防止内存数据库崩溃导致数据丢失。类比“仓库”,货物存入仓库后不会因系统故障丢失。
- 已读回执机制:客户端收到消息后,向服务器上报已读状态,服务器同步并标记消息为已读。类比“确认收货”——用户确认后,商家更新订单状态。
- 幂等性:确保消息重复处理结果不变。类比“银行转账”——同一个订单号只扣一次款,通过消息ID唯一标识,数据库插入前检查ID或使用Redis分布式锁。
- 重试机制:网络不稳定时,客户端重试发送,服务端消费失败后重试。类比“快递员重新投递”——失败后等待一定时间再尝试,避免无限循环(指数退避策略)。
3) 【对比与适用场景】
以消息队列(RocketMQ)与数据库存储的对比为例:
- 消息队列(RocketMQ):
- 定义:分布式消息系统,支持高吞吐、低延迟、持久化。
- 特性:事务消息、顺序消息、高可用、消费确认(ACK)。
- 使用场景:消息中转、削峰、异步处理。
- 注意点:事务消息有额外开销,顺序消息需额外配置。
- MySQL(持久化存储):
- 定义:关系型数据库,支持ACID事务。
- 特性:数据持久化、事务支持、索引优化。
- 使用场景:主存储,保存消息的元数据(如发送者、接收者、时间、状态)。
- 注意点:需分库分表应对高并发,避免单表性能瓶颈。
4) 【示例】
发送消息并处理已读回执的流程(伪代码):
- 客户端调用API:
POST /api/sendMessage
{
"senderId": "userA",
"receiverId": "userB",
"content": "Hello",
"groupId": "chat123",
"timestamp": 1678888888
}
- 服务端处理步骤:
- 封装消息:将消息写入RocketMQ主题(如
chat_messages),消息持久化(写入磁盘,事务消息确保发送和存储原子性)。
- 消费RocketMQ消息:将消息存入Redis缓存(
user:userB:messages列表,按时间顺序插入),并触发推送服务(FCM/APNs)向客户端发送消息。
- 客户端收到消息后,保存到本地SQLite,并向服务器发送已读请求:
POST /api/markAsRead
{
"messageId": "msg_12345",
"userId": "userB"
}
- 服务器端处理已读请求:更新MySQL中消息的
readStatus字段为“已读”,并同步到Redis缓存(如更新消息列表的元数据)。
- 网络不稳定时的重试(推送失败):
- 推送服务将消息重新放入RocketMQ队列(如
retry_queue),设置延迟消费(如5分钟后)。
- 消费者延迟消费后,再次尝试推送,若仍失败,继续延迟重试(指数退避,如第一次1秒,第二次2秒,第三次4秒)。
5) 【面试口播版答案】
设计百万级移动端即时消息系统,核心架构是“分布式消息队列+分库存储+缓存+推送服务”的分布式方案。具体来说,消息通过RocketMQ(或Kafka)中转,保证消息不丢失且可扩展;队列消费后,消息存入Redis缓存(加速读取)和MySQL数据库(持久化),MySQL按用户ID分库、按消息ID分表应对高并发;通过APNs/FCM等推送服务实时同步到客户端。可靠性方面,消息队列持久化+消费确认(如RocketMQ的ACK机制),避免丢失;幂等处理(消息ID唯一,数据库插入前检查ID)避免重复;网络不稳定时,客户端重试发送(本地队列+指数退避),服务端消费失败后重试(消息重新入队并延迟消费)。扩展性通过水平扩展消息队列节点(增加Broker)和存储集群(增加MySQL实例)实现,实时性通过低延迟消息队列(亚秒级)和高效推送服务(集群部署)保障,群聊消息通过消息队列按群组ID分区(顺序消费)保证顺序。已读回执机制则通过客户端上报状态、服务器同步标记,确保消息状态最终一致。
6) 【追问清单】
- 问:如何保证消息不丢失?
回答要点:消息队列持久化(写入磁盘),消费端确认机制(ACK),事务消息(如RocketMQ事务消息,确保消息发送和存储原子性,失败后回滚)。
- 问:如何处理消息重复?
回答要点:消息ID唯一标识,幂等处理(数据库插入前检查消息ID,或使用Redis分布式锁保证单次处理)。
- 问:网络不稳定时,客户端如何重传?
回答要点:客户端维护消息重试队列,超时后按指数退避策略重试(如第一次1秒,第二次2秒,第三次4秒),避免频繁重试。
- 问:如何保证群聊消息的顺序?
回答要点:消息队列按群组ID分区(顺序消费),或数据库按群组ID分表并顺序插入(如使用MySQL的顺序索引)。
- 问:已读回执如何保证最终一致性?
回答要点:客户端上报已读状态,服务器端更新数据库并同步到缓存,若网络中断,客户端重试上报(指数退避),确保最终状态一致。
7) 【常见坑/雷区】
- 忽略消息持久化,导致内存数据库崩溃后消息丢失。
- 未考虑幂等性,导致重复消息处理(如重复发送通知)。
- 网络不稳定时,未设计重试机制,消息积压导致延迟。
- 存储方案未分库分表,高并发下读写性能瓶颈。
- 未考虑消息顺序性(如群聊消息乱序),影响用户体验。
- 已读回执机制未处理网络中断,导致状态同步失败。