设计通知推送系统
问题
如何设计一个支持多渠道推送的通知系统?
答案
架构设计
核心实现
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: 通知系统如何保证消息不丢失?
答案:
- 持久化队列:通知请求先入消息队列(Kafka/RabbitMQ)
- ACK 机制:渠道发送成功后才 ACK
- 重试:发送失败的消息进入重试队列
- 死信队列:多次重试仍失败的进入死信队列,人工处理
- 状态追踪:数据库记录每条通知的状态
相关链接
- lettre - Rust 邮件发送库
- 设计通知推送系统(Go)