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

设计一个支持亿级用户的实时消息系统(如微信聊天),需满足消息实时推送(延迟<1秒)、消息不丢失、高可用(故障时服务不中断)、支持大规模用户并发(如节日红包时峰值流量)。请从系统架构、数据存储、消息队列、推送机制等方面进行设计,并分析如何处理峰值流量。

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

答案

1) 【一句话结论】
亿级用户实时消息系统通过“消息队列(Kafka)解耦并缓冲流量、分布式存储(MySQL+Redis)保障数据持久与低延迟、长连接WebSocket推送实现实时交互”,结合Kafka事务消息(本地事务+全局事务)确保数据最终一致性,通过动态缓存预热、重试机制处理边界场景,满足延迟<1秒、消息不丢失、高可用及峰值流量应对。

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

  • 消息队列(Kafka):作为消息中转站,解耦生产者(用户发送消息)与消费者(消息处理/推送服务),消息持久化到磁盘(保证不丢失且缓冲流量)。配置上,按用户ID哈希分区(如1000分区),消费者组配置为分区数并发因子(如1000分区2=2000消费者,每个分区由多个消费者并行消费,提升处理能力)。
  • MySQL消息表设计:表结构包含id(主键)、from_user、to_user、content、created_at、status(0=待推送,1=已推送)。关键索引:复合索引to_user, created_at(按接收用户和时间排序,便于查询未推送消息)、from_user, created_at(按发送用户和时间排序,便于查询历史消息)。查询优化:使用覆盖索引(索引包含查询所需所有字段),避免回表;批量操作(如批量插入/更新)减少I/O。
  • Redis缓存:存储用户在线状态(user_online:{user_id})、WebSocket连接ID(ws_conn:{user_id})、热点用户信息(hot_users)。动态预热:根据实时流量(如Kafka消息速率、WebSocket连接数)动态调整预热频率,例如当Kafka消息速率超过阈值时,增加Redis预热频率(如每分钟预热1000个热点用户),避免缓存雪崩。
  • 推送机制(WebSocket):客户端与推送服务保持长连接(如每30秒心跳),推送服务通过WebSocket直接推送消息(减少中间环节),延迟控制在1秒内(极端网络拥堵时采用消息分片推送,将大消息拆分为多个小消息,降低单次推送延迟)。
  • 高可用设计:数据库主从复制(主节点写,从节点读,故障时主从切换),消息队列多节点集群(Kafka集群,故障时自动选举新leader),推送服务集群(负载均衡分发请求,故障时自动剔除故障节点)。
  • 消息不丢失:消息先写入Kafka(持久化),消费端确认消息已处理(ack=1,确保消息仅被消费一次),失败后自动重试(最多3次),若重试失败则写入死信队列(DLQ),后续人工处理。结合Kafka事务消息(本地事务+全局事务):本地事务(消息写入Kafka)+全局事务(消息写入MySQL),确保消息在Kafka和数据库中同步,避免数据不一致。

3) 【对比与适用场景】
对比Kafka事务消息与双写事务:

机制定义特性适用场景注意点
Kafka事务消息Kafka内部事务,保证消息在Kafka和消费端(如数据库)的顺序与原子性高吞吐、低延迟,适合大规模消息系统(如亿级用户实时消息)实时消息、日志系统需Kafka 2.1+,消费端支持事务
双写事务(MySQL+Kafka)生产者先执行数据库事务插入,再写入Kafka,用数据库事务保证强一致性,适合对数据一致性要求极高但吞吐稍低场景金融交易、核心业务依赖数据库事务,吞吐受数据库影响

4) 【示例】

  • MySQL消息表索引设计示例:
    CREATE TABLE messages (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        from_user BIGINT NOT NULL,
        to_user BIGINT NOT NULL,
        content TEXT NOT NULL,
        created_at DATETIME NOT NULL,
        status TINYINT DEFAULT 0 COMMENT '0=待推送,1=已推送',
        INDEX idx_to_user_created_at (to_user, created_at),
        INDEX idx_from_user_created_at (from_user, created_at)
    );
    
  • Kafka消费者组配置示例:
    # Kafka消费者配置
    consumer:
      group-id: chat-message-consumer
      partitions: 1000  # 按用户ID哈希分区
      concurrency: 2    # 每分区2个消费者,总消费者数=1000*2=2000
      auto-offset-reset: earliest
    
  • 事务消息流程(本地事务+全局事务):
    # 生产者发送消息(事务消息)
    def send_message(to_user_id, content):
        # 启动Kafka事务
        kafka_producer.begin_transaction()
        try:
            # 1. 写入Kafka(本地事务)
            kafka_producer.send(
                topic="chat_message",
                key=f"user_{to_user_id}",
                value=json.dumps({"from": "user_A", "to": to_user_id, "content": content})
            )
            # 2. 写入MySQL(全局事务)
            db.execute_transaction(
                f"INSERT INTO messages (from_user, to_user, content, created_at) "
                f"VALUES ('user_A', {to_user_id}, '{content}', NOW())"
            )
            # 提交事务
            kafka_producer.commit_transaction()
        except Exception as e:
            # 回滚事务
            kafka_producer.abort_transaction()
            raise e
    
  • 动态Redis预热示例(基于实时流量):
    def dynamic_preheat():
        # 获取当前Kafka消息速率(每秒消息数)
        kafka_rate = get_kafka_message_rate()
        # 获取当前WebSocket连接数
        ws_conn_count = get_ws_connection_count()
        # 动态调整预热频率(如消息速率>10000/s时,预热频率增加)
        if kafka_rate > 10000:
            hot_users = db.query_hot_users(limit=1000)  # 查询活跃用户
            for user in hot_users:
                redis.set(f"user_{user.id}", user.to_dict(), ex=3600)  # 缓存1小时
    

5) 【面试口播版答案】
面试官您好,针对亿级用户实时消息系统,核心设计是构建一个高可用、低延迟的分布式架构。系统分为消息生产、处理、存储和推送四大模块。消息生产端通过WebSocket长连接接收用户输入,发送到消息队列(如Kafka),保证消息不丢失且解耦。消息处理服务从队列消费消息,通过Kafka事务消息(本地事务+全局事务)确保消息先写入Kafka再写入MySQL,实现数据最终一致性。同时,将用户信息缓存到Redis,提升读取速度。推送服务通过长连接WebSocket与客户端保持连接,实时推送消息。为了应对峰值流量,消息队列作为缓冲层,设置容量上限(如10万条/秒)并配合限流(令牌桶算法),同时部署多副本服务。高可用方面,数据库主从复制(主节点写,从节点读,故障自动切换),消息队列多节点集群(Kafka),推送服务集群(负载均衡),故障时自动切换,确保服务不中断。延迟控制通过异步处理和直接推送,确保延迟通常控制在1秒内(极端网络拥堵时采用消息分片推送,降低单次延迟)。边界场景如WebSocket断开,系统会通过心跳检测自动重连;Redis缓存击穿时,根据实时流量动态预热(如消息速率高时增加预热频率),提前加载热点用户数据。总结来说,通过解耦、缓存、分布式存储和负载均衡,满足亿级用户实时消息的需求。

6) 【追问清单】

  • 问题1:如何保证消息不丢失?
    回答要点:消息先写入Kafka(持久化到磁盘),消费端确认消息已处理(ack=1,确保消息仅被消费一次),失败后自动重试(最多3次),若重试失败则写入死信队列(DLQ),后续人工处理。结合Kafka事务消息(本地事务+全局事务),确保消息在Kafka和数据库中同步,避免数据不一致。
  • 问题2:峰值流量时如何处理?
    回答要点:消息队列设置容量上限(如10万条/秒),采用限流策略(令牌桶算法,控制消息写入速率),缓存预热(提前加载热点用户数据),负载均衡分发请求(如Nginx+LVS),推送服务集群(多节点并行处理)。
  • 问题3:事务消息的流程是怎样的?
    回答要点:生产者启动Kafka事务,先写入Kafka(本地事务),再执行数据库插入(全局事务),提交事务确保两者同步;若任一操作失败,回滚事务,保证数据一致性。

7) 【常见坑/雷区】

  • 消息队列选择不当:如用RabbitMQ处理高吞吐消息,可能导致延迟高、消息积压,应选择Kafka等高吞吐消息队列。
  • 存储选型错误:仅用Redis缓存消息,消息丢失风险高,需结合MySQL持久化,保证数据可靠性。
  • 推送机制错误:用短连接轮询获取消息,延迟高(秒级),应采用长连接WebSocket,实现毫秒级推送。
  • 峰值处理不足:未考虑消息队列的容量限制,导致峰值时消息积压,应设置队列大小、重试机制,并配合限流策略。
  • 高可用设计不完善:单点故障(如数据库主节点故障),未设置自动切换,应部署主从复制、多副本,并实现故障检测与自动切换。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1