
1) 【一句话结论】
采用“客户端长连接+消息队列(如RocketMQ/Kafka)+按用户ID哈希分片+事务消息保证不丢失+幂等处理避免重复”的架构,支撑百万级用户低延迟消息推送且消息可靠。
2) 【原理/概念讲解】
老师:首先,消息队列是核心解耦组件,它像“消息中转站”,生产者(客户端)发送消息后,队列会缓冲并持久化,消费者(服务端)再消费,避免生产者和消费者直接耦合,还能平滑流量冲击。
类比:就像快递中转站,发件人把包裹放中转站,收件人再取,中转站还能存起来,防止发件人突然发太多包裹时收件人忙不过来。
对于消息分片,是为了把百万用户的消息分散到多个分片(比如100个分片),每个分片处理部分用户的消息,避免单点分片压力过大。比如用户ID哈希到0-99分片,每个分片负责1%用户的消息。
消息不丢失的关键是持久化存储(如Kafka的日志持久化、RocketMQ的CommitLog),确保消息写入磁盘后不会丢失;事务消息(如RocketMQ的事务消息)能保证“发送成功后存储”,即使发送失败也能重试。
消息不重复则通过幂等处理,比如给每条消息一个唯一ID,服务端消费时检查ID是否已处理(或数据库加唯一约束),避免重复处理。
3) 【对比与适用场景】
| 消息分片策略 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|
| 按用户ID哈希 | 将用户ID哈希到固定分片(如0-99) | 负载均衡,避免单点压力 | 用户ID分布均匀的场景 | 可能导致低ID用户集中(热点),需动态扩容 |
| 按时间分片 | 按消息时间戳(如按小时)分片 | 避免时间集中产生的热点 | 消息产生时间突发(如秒级爆发) | 需要动态调整分片,避免冷热不均 |
4) 【示例】
客户端发送消息(伪代码):
def send_message(user_id, message):
# 用户ID哈希到分片(0-99)
shard_id = hash(user_id) % 100
# 发送消息到消息队列,key包含分片信息
producer.send(f"message_queue_{shard_id}",
key=f"user_{user_id}",
value=message)
服务端消费消息(伪代码):
def consume_message():
for shard_id in range(100):
# 消费分片消息
messages = consumer.consume(f"message_queue_{shard_id}")
for msg in messages:
user_id = msg.key.split('_')[-1]
# 处理消息(如推送给客户端)
push_to_client(user_id, msg.value)
5) 【面试口播版答案】
面试官您好,针对百万级用户低延迟消息推送,我设计的系统核心是采用“客户端长连接+消息队列(如RocketMQ)+按用户ID哈希分片+事务消息保证不丢失+幂等处理避免重复”的架构。
具体来说,客户端通过长连接连接到消息队列,发送消息时按用户ID哈希到不同分片,服务端消费后推送给客户端。消息队列持久化存储,确保不丢失;通过消息ID去重或数据库唯一约束处理重复;监控延迟和消息量,动态调整分片。这样既能支撑百万级用户,又能保证低延迟且消息可靠。
6) 【追问清单】
7) 【常见坑/雷区】