从“点赞”到“私信”手把手设计一个高可用的站内信系统当用户在你的平台上点赞了一篇帖子或是收到一条私信时如何确保通知能实时、可靠地送达站内信系统作为用户互动的核心枢纽直接影响着产品的用户体验和留存率。本文将带你从产品视角出发设计一套涵盖公告、提醒、私信等多种通知类型的完整解决方案。1. 站内信系统的核心要素任何优秀的站内信系统都需要解决三个基本问题通知分类、实时性保障和用户体验控制。我们先从最基础的通知类型划分开始。1.1 通知类型的三层架构现代社区平台的通知通常分为三个层级系统公告平台向全体或特定用户群发送的广播消息行为触发通知由用户互动如点赞、评论产生的个性化提醒私信对话用户之间的点对点通信每种类型在存储结构、推送逻辑和用户权限控制上都有显著差异。下面是一个典型的类型对比表类型触发方式存储要求实时性要求用户控制权系统公告管理员手动触发永久存储中等可退订类别行为通知用户行为自动触发短期存储30天高可关闭特定类型私信用户主动发送永久存储极高可屏蔽特定用户1.2 用户行为的事件化建模将用户互动抽象为标准化事件是设计灵活通知系统的关键。每个事件应包含以下核心属性CREATE TABLE notify_event ( id bigint NOT NULL AUTO_INCREMENT, actor_id bigint NOT NULL COMMENT 触发用户ID, action varchar(32) NOT NULL COMMENT 行为类型like/comment/share, object_type varchar(32) NOT NULL COMMENT 对象类型post/user/comment, object_id bigint NOT NULL COMMENT 对象ID, target_user_id bigint NOT NULL COMMENT 目标用户ID, created_at datetime NOT NULL, PRIMARY KEY (id), KEY idx_target_user (target_user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;提示使用utf8mb4字符集确保支持emoji等特殊符号2. 实时推送的技术实现当事件产生后如何高效地将其转化为用户可见的通知这需要一套可靠的消息管道。2.1 消息管道的三层缓冲为避免高并发下的系统过载建议采用三级缓冲架构应用层队列使用RabbitMQ或Kafka接收原始事件内存缓存Redis存储待推送通知持久化存储MySQL最终落库# 伪代码示例事件处理流程 def process_event(event): # 1. 写入消息队列 mq.publish(notify_queue, event) # 2. 消费者处理 if user_is_online(event.target_user_id): # 实时推送 ws_server.push(event.target_user_id, render_notification(event)) else: # 存入待推送列表 redis.sadd(fpending:{event.target_user_id}, event.id) # 3. 持久化存储 db.insert(notifications, user_idevent.target_user_id, contentrender_content(event), is_readFalse)2.2 WebSocket的优化实践保持长连接的稳定性是实时系统的难点以下是几个关键优化点心跳机制客户端每50秒发送ping帧断线重连采用指数退避算法1s, 2s, 4s...多路复用单个连接承载所有通知类型前端实现示例class NotificationSocket { constructor() { this.reconnectDelay 1000; this.connect(); } connect() { this.ws new WebSocket(wss://api.example.com/notify); this.ws.onopen () { this.reconnectDelay 1000; // 重置重连延迟 this.startHeartbeat(); }; this.ws.onmessage (event) { this.handleNotification(JSON.parse(event.data)); }; this.ws.onclose () { setTimeout(() this.connect(), this.reconnectDelay); this.reconnectDelay Math.min(this.reconnectDelay * 2, 30000); }; } startHeartbeat() { this.heartbeatInterval setInterval(() { this.ws.send(ping); }, 50000); } }3. 用户偏好与权限控制不是所有用户都希望收到所有类型的通知完善的权限体系必不可少。3.1 通知设置的数据模型用户应该能够全局关闭特定类型的通知屏蔽特定用户的私信设置免打扰时段CREATE TABLE notification_settings ( user_id bigint NOT NULL, channel varchar(32) NOT NULL COMMENT push/email/sms, type varchar(32) NOT NULL COMMENT like/comment/mention, enabled tinyint(1) NOT NULL DEFAULT 1, PRIMARY KEY (user_id,channel,type) ); CREATE TABLE user_blocks ( user_id bigint NOT NULL, blocked_user_id bigint NOT NULL, PRIMARY KEY (user_id,blocked_user_id) );3.2 权限校验的拦截逻辑在消息进入管道前进行权限检查def should_deliver_notification(sender_id, receiver_id, action): # 检查全局设置 setting db.get_settings(receiver_id, action) if not setting.enabled: return False # 检查屏蔽关系 if db.exists(user_blocks, user_idreceiver_id, blocked_user_idsender_id): return False # 检查免打扰时段 if is_quiet_time(receiver_id): return False return True4. 性能优化与扩展策略随着用户量增长系统需要应对新的挑战。4.1 分库分表策略通知数据通常按用户ID进行分片notifications_0 notifications_1 ... notifications_9分片路由规则public String determineTableShard(long userId) { int shard (int) (userId % 10); return notifications_ shard; }4.2 冷热数据分离热数据最近3天的未读通知存储在Redis温数据30天内的通知MySQL主库冷数据历史归档MySQL从库或对象存储4.3 推送降级策略当系统压力过大时可依次降级关闭WebSocket实时推送改为轮询合并同类通知如3个新点赞延迟非关键通知如每周摘要5. 监控与故障排查完善的监控体系能帮助快速定位问题。5.1 关键监控指标指标报警阈值检查项WebSocket连接数 80% 最大负载是否需要扩容未送达消息堆积量 10,000消费者是否异常通知延迟P99 3s队列处理能力5.2 常见问题排查指南问题用户反映收不到点赞通知排查步骤检查事件是否生成SELECT * FROM notify_event WHERE actionlike AND target_user_id?;检查用户设置SELECT * FROM notification_settings WHERE user_id? AND typelike;检查推送日志grep user_id123 /var/log/push-service.log问题WebSocket连接频繁断开检查项Nginx超时配置proxy_read_timeout 600s; proxy_connect_timeout 60s;客户端心跳是否正常防火墙会话超时设置在实际项目中我们曾遇到因TCP keepalive设置不当导致的连接中断问题。通过调整内核参数解决# 调整TCP keepalive参数 sysctl -w net.ipv4.tcp_keepalive_time300 sysctl -w net.ipv4.tcp_keepalive_intvl60 sysctl -w net.ipv4.tcp_keepalive_probes5