51mee - AI智能招聘平台Logo
模拟面试题目大全招聘中心会员专区

设计一个支持百万级用户同时在线的即时通讯(IM)消息系统,要求消息实时性(延迟<100ms)、消息不丢失、支持消息撤回、消息历史查询。请描述系统架构、核心组件、数据存储方案及关键优化点。

Tencent软件开发-后台开发方向难度:困难

答案

1) 【一句话结论】
采用“消息队列解耦+多级缓存加速+数据库持久化+实时推送”的分布式架构,通过事务消息、状态同步、缓存预热等工程优化,确保百万级并发下消息延迟<100ms、不丢失,支持消息撤回与历史查询。

2) 【原理/概念讲解】
老师解释,支撑百万级并发,核心是解耦与削峰。

  • 消息队列(如RocketMQ/Kafka):用于异步处理消息,避免用户发送时直接阻塞数据库,支持高吞吐。用户A发消息给B,消息先写入队列,消费端(消息服务)消费后持久化到数据库(消息表),确保不丢失。
  • 多级缓存:Redis缓存消息列表和未读数,因为Redis读写延迟低(毫秒级),能降低数据库压力,支持实时查询。用户B登录时,从Redis获取A发给他的未读消息。
  • 数据库:消息表(存储消息内容、发送者、接收者、时间等)用于持久化,历史表(按天分片)用于支持历史查询(如查看过去30天消息)。
  • 推送服务:通过WebSocket/长连接实时推送消息,保证延迟<100ms。

类比:消息队列是消息中转站(解耦),缓存是高速缓存(加速查询),数据库是持久化仓库(不丢失),推送是实时快递员(送达)。

3) 【对比与适用场景】

组件定义特性使用场景注意点
Redis(缓存)内存数据库,支持高速读写读写延迟低(毫秒级),支持高并发查询,数据易丢失(需持久化)未读消息列表、消息预加载、实时状态(如已读标记)需配合持久化(如RDB/AOF),避免重启丢失
MySQL(数据库)关系型数据库,持久化存储数据持久化,支持复杂事务与查询,读写延迟较高(需优化)消息持久化、历史查询、状态管理(如消息状态、用户关系)需优化索引、分库分表,降低延迟
消息队列(如RocketMQ)分布式消息系统高吞吐、低延迟、支持事务、持久化解耦发送与处理,确保消息不丢失,支持批量消费需部署多实例,负载均衡

4) 【示例】
伪代码展示用户A发送消息给B,撤回的流程:
用户A发送消息给用户B:

  1. 应用层调用消息队列生产者:producer.send("userA_to_userB", "hello");
  2. 消费端(消息服务)消费:
    • 持久化到MySQL消息表:
      INSERT INTO message (sender, receiver, content, send_time) 
      VALUES ('userA', 'userB', 'hello', NOW())
      
    • 更新Redis缓存:
      RPush userB_messages:latest "hello"
      EXPIRE userB_messages:latest 300  # 5分钟过期
      INCR userB_unread_count
      
  3. 推送服务监听消息表变化(如通过CDC),推送消息给用户B:
    • WebSocket发送:
      client.send("new_message", {sender: "userA", content: "hello"})
      
  4. 消息撤回:
    • 用户A调用撤回接口:
      • 事务消息删除数据库记录:
        BEGIN TRANSACTION;
        DELETE FROM message WHERE id = ? AND sender = ? AND receiver = ?;
        COMMIT;  # 事务消息确认,确保原子性
        
      • 更新Redis缓存:
        LREM userB_messages:latest 0 "hello"
        DECR userB_unread_count
        
      • 推送撤回通知给所有已接收客户端:
        client.send("message_retracted", {message_id: ?, sender: "userA"})
        

5) 【面试口播版答案】
“要设计百万级并发的即时通讯消息系统,核心架构是消息队列(如RocketMQ)解耦发送与处理,多级缓存(Redis)加速查询,数据库(MySQL)持久化,实时推送(WebSocket)保证延迟。消息队列避免用户发送时阻塞数据库,消费端将消息持久化到MySQL,确保不丢失。Redis缓存消息列表和未读数,支持毫秒级查询。推送服务通过WebSocket实时推送,延迟控制在100ms内。历史查询通过按天分片存储(如day_20240101),按时间范围高效查询。关键优化点包括:消息队列部署多实例,通过负载均衡器(如Nginx)分发消息,消费端多线程并行处理;Redis设置5分钟TTL并实现缓存预热(预存热门用户的消息列表);历史表按时间分片+时间索引(时间、用户ID),避免全表扫描;消息撤回时,先通过事务消息删除数据库记录,再更新Redis并通知所有客户端删除本地消息,确保状态一致性。”

6) 【追问清单】

  • 消息撤回的实现?
    回答要点:撤回时,先通过事务消息(如RocketMQ的事务消息)确保数据库删除消息后,再更新Redis并推送撤回通知,保证原子性,避免不一致。
  • 历史查询的效率?
    回答要点:历史表按天分片(如按消息发送时间取模分片),查询时按时间范围分片查询,结合时间索引(时间列作为主键或索引),避免全表扫描,提升查询速度(如查询过去30天消息,只需查询对应分片)。
  • 缓存雪崩的解决方案?
    回答要点:Redis设置过期时间(如5分钟),并实现缓存预热(预存热门用户的消息列表,如登录时加载);消息表更新时,先更新缓存,再删除原缓存(避免雪崩),同时用互斥锁(如Redis的SETNX)防并发更新。
  • 消息队列的负载均衡?
    回答要点:消息队列部署多实例,通过负载均衡器(如Nginx)分发生产者发送的消息,消费端多线程并行处理消息,结合消息堆积策略(如限流,当队列积压超过阈值时,暂停消费或丢弃旧消息),避免队列积压导致延迟。
  • 消息状态同步?
    回答要点:消息状态(如已读/未读)通过数据库事务或消息队列确认机制同步,比如用户读消息时,更新消息表状态(如标记为已读)并同步Redis(更新消息列表为已读状态),确保状态一致性。

7) 【常见坑/雷区】

  • 消息丢失:仅存缓存未持久化,重启后消息丢失。需将消息持久化到数据库,并设置消息确认机制(如消息队列的ACK机制)。
  • 缓存与数据库不一致:未实现事务或消息队列,导致消息已读但缓存未更新。需用事务或消息队列确保一致性,比如用户读消息时,先更新数据库状态,再更新缓存。
  • 历史查询性能差:未分片或索引不足,导致查询慢。需按时间分片+时间索引优化,避免全表扫描。
  • 消息撤回不一致:仅更新数据库未同步缓存,导致客户端仍显示消息。需同步所有副本(数据库+缓存+客户端),比如撤回时,先删除数据库记录,再更新缓存并推送通知。
  • 延迟过高:消息队列消费延迟或网络延迟导致推送延迟超过100ms。需优化消费端性能(如增加线程数、优化数据库操作),减少网络延迟(如使用WebSocket长连接,减少连接建立时间)。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1