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

设计一个支持百万级用户的即时消息系统(类似微信),需要考虑消息的实时性、可靠性、消息队列、消息确认机制等,请描述系统架构和关键组件。

Tencent软件开发-移动客户端开发方向难度:困难

答案

1) 【一句话结论】
百万级即时消息系统以分布式消息队列(如Kafka)为核心,通过消息确认机制(AT LEAST_ONCE为主,结合Exactly Once事务)、分层存储(MySQL持久化+Redis离线缓存)及消息路由负载均衡(一致性哈希),实现高吞吐、低延迟的实时通信,同时通过事务ID/消息ID去重、7天离线消息LRU清理等机制保障可靠性。

2) 【原理/概念讲解】
老师口吻解释关键组件:

  • 分布式消息队列(Kafka):作为消息中转站,解耦发送方与接收方,支持百万级QPS,日志持久化确保数据不丢失,类比“快递中转站”:发送方把包裹(消息)放入中转站,接收方再取,避免直接对接阻塞。
  • 消息确认机制(ACK):发送方发送消息后,等待接收方ACK,超时重发(AT LEAST_ONCE),保证可靠性,类比“快递签收确认”:未签收重发,确保送达。
  • 消息路由:按用户ID或会话ID(如会话ID=用户A+用户B)将消息发送到目标队列,确保消息只发给目标用户,类比“快递分拣”:按地址分拣包裹到对应收件人。
  • 消息重复检测:使用Kafka事务或消息ID(如消息的Message ID)作为唯一标识,消费前检查数据库或Redis中是否存在该ID,避免重复处理,类似“快递单号去重”:同一单号包裹不重复派送。
  • 离线消息清理:离线消息存储在Redis,设置7天TTL,定期通过LRU算法清理过期数据,避免内存溢出,类似“仓库库存清理”:过期商品及时清理,释放空间。
  • 消息路由负载均衡:采用一致性哈希算法分配分区,避免热点分区导致性能瓶颈,类似“快递分拣中心按区域分配任务”:不同区域分拣员处理不同区域包裹,避免某区域过载。

3) 【对比与适用场景】

对比项KafkaRabbitMQ
核心特性主题+分区+副本(高吞吐、持久化、流处理)队列+交换机+绑定(灵活路由、事务支持)
事务支持支持Exactly Once(事务ID,回滚机制)支持事务,但复杂度较高,需手动管理
消息持久化日志文件(磁盘,高可靠性)内存+磁盘(可选,默认内存)
消息幂等性通过事务ID/消息ID检查,确保幂等需额外实现幂等性逻辑(如数据库唯一键)
适合场景高吞吐、持久化、流处理(如消息系统、日志)基于消息的解耦、可靠投递(如订单、通知)
注意点分区管理复杂,消费需手动提交;需考虑消息积压队列模式灵活,但可能存在消息积压;事务处理开销大

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

1. 客户端(A端)调用API:POST /send_message?to_user_id=B&message=Hello&msg_id=uuid-123
2. API网关将请求写入Kafka Topic "chat_messages",分区按user_id哈希(user_id%10),消息包含msg_id
3. 消费者(消息处理服务)消费消息:
   a. 检查MySQL中是否存在该msg_id(消息重复检测):若存在,跳过处理
   b. 执行数据库事务:插入消息到chat_messages表(发送者、接收者、时间、msg_id)
   c. 写入Redis离线消息:key=to_user_id(B),value=消息内容+msg_id
4. 消费者发送ACK给Kafka
5. 若Redis有用户B离线记录,推送服务读取离线消息(按msg_id排序),通过APNs/FCM发送
6. 用户B在线时,从MySQL读取历史消息(按时间倒序),从Redis读取离线消息(按msg_id排序),合并展示

5) 【面试口播版答案】(约90秒)
“面试官您好,设计百万级即时消息系统,核心目标是保证实时性、可靠性和可扩展性。架构上以分布式消息队列(如Kafka)为核心,解耦发送方与接收方,缓冲流量。消息持久化到Kafka,确保服务器重启后恢复。消息确认机制采用AT LEAST_ONCE,通过ACK保证可靠性,若消费者失败则重试。消息路由按用户ID或会话ID,将消息发送到目标队列。存储方面,MySQL持久化历史消息(保证一致性),Redis缓存离线消息(快速推送),推送服务处理离线消息。消息重复检测通过Kafka事务或消息ID去重,离线消息在Redis设置7天TTL并定期LRU清理。消息路由采用一致性哈希避免热点分区,通过增加消费者组数量动态扩容缓解积压。整体支持在线用户实时接收、离线用户后续推送,确保高可用与低延迟。”

6) 【追问清单】

  • 问:如何保证消息不重复投递?
    回答:采用Kafka事务或消息ID(如UUID)作为唯一标识,消费前检查数据库/Redis中是否存在该ID,确保幂等处理。
  • 问:离线消息如何存储与清理?
    回答:存储在Redis,设置7天过期时间,定期通过LRU算法清理过期数据,避免内存溢出。
  • 问:系统如何处理消息队列延迟或积压?
    回答:增加消费者组数量、优化处理逻辑(如批量处理),动态扩容Broker或消费者节点缓解积压。
  • 问:消息路由负载均衡是否会导致热点?
    回答:采用一致性哈希算法分配分区,避免哈希冲突导致热点,确保各分区负载均衡。
  • 问:消息存储数据库选择MySQL和Redis的权衡?
    回答:MySQL用于持久化历史消息(保证数据一致性和可查询性),Redis用于离线消息(快速读写、支持推送,缓解数据库压力)。

7) 【常见坑/雷区】

  • 消息确认机制选择不当:直接用Exactly Once可能导致消息丢失,需根据业务容忍度选AT LEAST_ONCE(允许重发)或Exactly Once(需事务支持)。
  • 消息队列与存储同步问题:消息写入队列后未及时持久化到数据库,需设计事务或补偿机制(如消息队列与数据库双写,失败时重试)。
  • 消息路由热点分区:简单哈希分区(如user_id%N)可能导致热点分区,影响性能,应采用一致性哈希或动态分区分配。
  • 离线消息存储内存问题:Redis内存占用过大,需合理设置过期时间(如7天)并启用LRU,避免OOM(内存溢出)。
  • 高可用设计不足:消息队列或存储单点故障,需部署多节点、配置副本(如Kafka副本因子3),确保故障时数据不丢失。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1