跳到主要内容

设计通知推送系统

问题

如何设计一个支持多渠道推送的通知系统?

答案

架构设计

核心实现

use async_trait::async_trait;

/// 通知渠道 Trait
#[async_trait]
pub trait NotificationChannel: Send + Sync {
async fn send(&self, to: &str, content: &str) -> Result<(), Box<dyn std::error::Error>>;
fn channel_type(&self) -> &str;
}

/// 邮件渠道
struct EmailChannel { /* SMTP 客户端 */ }

#[async_trait]
impl NotificationChannel for EmailChannel {
async fn send(&self, to: &str, content: &str) -> Result<(), Box<dyn std::error::Error>> {
println!("Sending email to {}: {}", to, content);
// lettre::send_email(...)
Ok(())
}
fn channel_type(&self) -> &str { "email" }
}

/// 通知分发器
pub struct NotificationDispatcher {
channels: Vec<Box<dyn NotificationChannel>>,
}

impl NotificationDispatcher {
pub fn new() -> Self {
Self { channels: Vec::new() }
}

pub fn add_channel(&mut self, channel: Box<dyn NotificationChannel>) {
self.channels.push(channel);
}

/// 按渠道类型发送
pub async fn dispatch(
&self,
channel_type: &str,
to: &str,
content: &str,
) -> Result<(), String> {
let channel = self.channels.iter()
.find(|c| c.channel_type() == channel_type)
.ok_or_else(|| format!("Channel {} not found", channel_type))?;

channel.send(to, content).await
.map_err(|e| e.to_string())
}

/// 多渠道同时发送
pub async fn broadcast(&self, to: &str, content: &str) {
let futures: Vec<_> = self.channels.iter()
.map(|c| c.send(to, content))
.collect();
futures::future::join_all(futures).await;
}
}

模板引擎

use std::collections::HashMap;

pub struct TemplateEngine;

impl TemplateEngine {
/// 简单的变量替换模板
pub fn render(template: &str, vars: &HashMap<String, String>) -> String {
let mut result = template.to_string();
for (key, value) in vars {
result = result.replace(&format!("{{{{{}}}}}", key), value);
}
result
}
}

// 使用
// let mut vars = HashMap::new();
// vars.insert("name".into(), "Alice".into());
// vars.insert("code".into(), "123456".into());
// let content = TemplateEngine::render(
// "Hi {{name}}, your verification code is {{code}}",
// &vars
// );

关键设计要素

要素说明
多渠道Trait 抽象渠道,新增渠道不改核心代码
模板消息内容与渠道解耦
频率控制防止骚扰用户(同类消息最多 N 次/天)
优先级紧急通知优先发送
幂等相同通知不重复发送
失败重试指数退避重试

常见面试问题

Q1: 通知系统如何保证消息不丢失?

答案

  1. 持久化队列:通知请求先入消息队列(Kafka/RabbitMQ)
  2. ACK 机制:渠道发送成功后才 ACK
  3. 重试:发送失败的消息进入重试队列
  4. 死信队列:多次重试仍失败的进入死信队列,人工处理
  5. 状态追踪:数据库记录每条通知的状态

相关链接