跳到主要内容

通知推送系统设计

问题

如何设计一个多渠道消息通知系统?

答案

架构设计

核心设计

通知服务 —— 策略模式多渠道
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 条新消息")
  • 免打扰:夜间消息延迟到早上发送

相关链接