
1) 【一句话结论】采用消息队列(如RabbitMQ/Kafka)实现用户报名与通知的异步解耦,通过持久化存储、消费端确认机制保证消息可靠传递,结合延迟队列处理1小时后的通知,确保消息不丢失且延迟准确。
2) 【原理/概念讲解】老师口吻解释:消息队列的核心是解耦用户报名服务(生产者)与通知服务(消费者),实现异步处理。可靠传递需满足两点:一是消息持久化(存储至磁盘,避免内存丢失),二是消费端需发送ACK确认,若消费失败则消息保留重试;延迟消息处理则通过消息头设置延迟时间(如1小时),队列按时间排序消费。简单类比:消息队列像快递中转站,生产者把包裹(消息)放进去,消费者(通知服务)按顺序取。若消费者没取(消费失败),包裹留到第二天(重试),若一直取不到,送退件中心(死信队列)。延迟包裹则放在特定货架(延迟队列),过时间后取出。
3) 【对比与适用场景】
| 维度 | 可靠传递(核心机制) | 延迟消息处理(关键机制) |
|---|---|---|
| 定义 | 保证消息不丢失,处理失败后重试 | 消息延迟一定时间后消费 |
| 核心技术 | 持久化队列 + 消费端ACK确认 | 延迟交换机(RabbitMQ)或延迟主题(Kafka) + TTL |
| 处理逻辑 | 消费失败,消息保留,重试或死信 | 消息头设置延迟时间,队列按时间排序,过时间后消费 |
| 适用场景 | 用户报名后需立即/稍后发送通知,且不能丢失 | 用户报名后1小时发送通知(如课程开课前提醒) |
4) 【示例】(伪代码,以RabbitMQ为例,含幂等性处理)
生产者(用户报名服务):
// 发送消息到队列,设置延迟1小时(3600秒)
channel.basicPublish("registration-queue", "registration",
MessageProperties.PERSISTENT_TEXT_PLAIN,
("user_id:" + user.getId() + ",course_id:" + course.getId() + ",type:" + type).getBytes());
// 检查幂等性:数据库记录用户是否已发送通知
if (isNotificationSent(userId, courseId, type)) {
return; // 跳过重复处理
}
消费者(通知服务):
public void processMessage(Message message) {
String body = new String(message.getBody(), StandardCharsets.UTF_8);
String[] parts = body.split(",");
String userId = parts[0].split(":")[1];
String courseId = parts[1].split(":")[1];
String type = parts[2].split(":")[1];
// 检查幂等性:查询数据库,用户是否已收到通知
if (isNotificationSent(userId, courseId, type)) {
return; // 跳过,避免重复
}
// 处理消息
if (type.equals("SMS")) {
sendSMS(userId, courseId);
} else if (type.equals("EMAIL")) {
sendEmail(userId, courseId);
}
// 确认消息已处理
channel.basicAck(message.getDeliveryTag(), false);
}
5) 【面试口播版答案】
面试官您好,课程通知系统需要用户报名后异步发送短信或邮件,为保障消息可靠传递并处理延迟通知,我会采用消息队列(如RabbitMQ或Kafka)实现解耦。具体来说,用户报名服务作为生产者,将包含用户、课程和通知类型的消息发送到队列,并设置延迟时间(比如1小时)。消息队列通过持久化存储和消费端确认机制保证消息不丢失,如果消费者处理失败,消息会重试或进入死信队列。对于延迟消息,队列会根据消息头中的延迟时间排序,过时间后由消费者处理,确保用户在报名后1小时收到通知。同时,通过数据库记录或分布式锁实现消息幂等性,避免重复发送通知,提升用户体验。这样既解耦了报名和通知流程,又保证了消息可靠性和延迟处理的准确性。
6) 【追问清单】
7) 【常见坑/雷区】