通知推送系统设计
问题
如何设计一个多渠道消息通知系统?
答案
架构设计
核心设计
通知服务 —— 策略模式多渠道
public interface NotificationChannel {
void send(NotificationMessage message);
String getType(); // SMS / EMAIL / PUSH / WEBSOCKET
}
@Service
public class NotificationService {
private final Map<String, NotificationChannel> channelMap;
// Spring 自动收集所有渠道实现
public NotificationService(List<NotificationChannel> channels) {
this.channelMap = channels.stream()
.collect(Collectors.toMap(NotificationChannel::getType, Function.identity()));
}
public void send(NotificationRequest request) {
// 1. 渲染模板
String content = templateEngine.render(request.getTemplateId(), request.getParams());
// 2. 查询用户通知偏好
List<String> channels = userPreferenceService.getChannels(request.getUserId());
// 3. 多渠道发送
for (String channel : channels) {
NotificationChannel sender = channelMap.get(channel);
if (sender != null) {
sender.send(new NotificationMessage(request.getUserId(), content));
}
}
// 4. 记录发送日志
notificationLogMapper.insert(new NotificationLog(request));
}
}
消息模板
模板引擎
// 模板:尊敬的 ${username},您的订单 ${orderNo} 已发货。
public String render(String templateId, Map<String, String> params) {
String template = templateMapper.findById(templateId).getContent();
for (Map.Entry<String, String> entry : params.entrySet()) {
template = template.replace("${" + entry.getKey() + "}", entry.getValue());
}
return template;
}
防重复发送与限频
Redis 去重 + 用户限频
public boolean shouldSend(String userId, String templateId) {
// 1. 消息去重(同一条消息 10 分钟内不重复发)
String dedupeKey = "notify:dedupe:" + userId + ":" + templateId;
Boolean isNew = redisTemplate.opsForValue()
.setIfAbsent(dedupeKey, "1", 10, TimeUnit.MINUTES);
if (!Boolean.TRUE.equals(isNew)) return false;
// 2. 用户限频(每天最多 10 条短信)
String limitKey = "notify:limit:sms:" + userId;
Long count = redisTemplate.opsForValue().increment(limitKey);
if (count == 1) redisTemplate.expire(limitKey, 1, TimeUnit.DAYS);
return count <= 10;
}
常见面试问题
Q1: 如何保证消息不丢?
答案:
- MQ 持久化 + 消费确认
- 发送失败重试(指数退避)
- 发送记录入库,定时扫描补偿
Q2: 高并发下如何避免通知风暴?
答案:
- 用户限频:每用户每天 / 每小时限制条数
- 消息合并:同类通知合并为一条(如"您有 3 条新消息")
- 免打扰:夜间消息延迟到早上发送