单机 LTS
阶段一
- 一台 Ubuntu 26.04 LTS 服务器(东京)
- Nginx + Nuxt SSR + Rust API + worker 同机
- MySQL 8.4 + Redis 7 + Meilisearch 同机本地端口
- systemd 托管三类服务,开机自启
- certbot 自动续期证书,ufw 仅开 22/80/443
优先验证产品逻辑,成本最低。关键动作是给 MySQL 与 Redis 设好内存上限,避免 Meilisearch 抢内存把整机拖垮。
System Architecture
架构的目标不是「先进」,而是让每个模块都能独立演进、每个热点都能被缓存拦住、每条链路都能被观测。本页给出六层分层架构、两条核心链路的完整拆解,以及从单机到多地域的四阶段部署演进。
自上而下依次为客户端、接入网关、应用服务、异步调度、数据存储与基础设施。分层的关键原则是「上层依赖下层,同层之间通过事件解耦」。点击任一层可查看该层的组件职责。
把最复杂的两个业务链路逐步展开。阅读时请重点关注「哪些步骤是同步的、哪些必须异步」,这是性能设计的核心分界线。
从客户端提交到索引与召回完成,说明为什么重活必须异步。
表单提交
Nuxt 页面在服务端渲染表单,提交走 Nitro server route 或直连 Rust API,携带 HttpOnly Cookie 会话。
鉴权与校验
Rust 从 Redis 取会话,校验登录态、社区发帖权限与信任等级门槛,并做请求体结构校验。
事务写主库
单个 MySQL 事务内写 posts、post_revisions 首版、post_tags 关联,提交后立即返回帖子 ID。
缓存失效
删除该社区列表缓存的 Redis key;热度分数初始值写入 Redis ZSet。
Outbox 落盘
同一事务内写入 outbox 表(事件 + 状态),保证「数据改了,事件一定发得出」。
异步消费者
后台 worker 轮询 outbox:投递 Meilisearch 索引、生成通知、刷新作者统计,成功后标记已处理。
订阅扇出
notify 模块按社区订阅者与作者关注者聚合通知,写 notification 表并递增 Redis 未读计数。
响应与回源
前端拿到帖子 ID 后跳转详情页;详情页 SSR 直接查库渲染,首屏无需等待索引完成。
投票看似简单,实际是幂等、一致性、性能三者的博弈。
点击投票
前端乐观更新按钮与计数,标记 pending;失败时回滚并提示。
限流与风控
Nginx 按 IP 限流,Rust 侧按用户与设备指纹计数,异常频率直接拒绝。
幂等写入
votes 表以 (user_id, target_type, target_id) 唯一索引兜底,重复请求返回既有状态而非报错。
状态机判定
赞 → 再点赞视为取消;赞 → 踩视为改投。三种状态在服务端单一入口内收敛。
计数增量
Redis HINCRBY 维护实时票数,避免每次 COUNT(*);MySQL 中保留权威计数列。
热度重算
按 log10(max(|v|,1))×sign(v) + age/45000 重算 hot_score,更新 Redis ZSet 与数据库快照。
Karma 与权重
异步更新作者 Karma;低信任等级账号的票按权重系数折算,防小号刷分。
一致性兜底
每日离线任务用 MySQL 权威数据校正 Redis 计数与分数,消除长期漂移。
性能、容量、安全、可用性四组量化目标。这些目标必须在需求阶段确定,而不是上线后再优化——架构决策的成本在写第一行代码时就已锁定。
| 项目 | 目标值 | 保障手段 |
|---|---|---|
| 列表接口 P95 | ≤ 150ms | Redis 缓存 + 预计算 hot_score,禁止实时聚合 |
| 帖子详情 P95 | ≤ 250ms | 评论树分层加载,首屏只取前 N 条顶层评论 |
| SSR 首字节 TTFB | ≤ 400ms | Nitro 就近查库 + Redis 会话,避免串联调用 |
| 搜索响应 P95 | ≤ 80ms | Meilisearch 内存索引 + 限定字段与分页深度 |
| 首屏可交互 LCP | ≤ 1.8s | SSR 直出 + 关键 CSS 内联 + 图片懒加载 |
| 项目 | 目标值 | 保障手段 |
|---|---|---|
| 单机承载 | 8 核 / 16GB 支撑 5k 日活 | 连接池限流 + 缓存命中率 ≥ 85% |
| 菜单深度 | 理论无上限,实测 ≥ 6 级 | 菜单树按社区整体缓存,前端递归渲染不查库 |
| 评论树深度 | 无上限,实测 ≥ 50 层 | 邻接表 + root_id 取整棵树,深层折叠懒加载 |
| MySQL 单表规模 | ≤ 2000 万行 | 按时间分区预留,超阈值再分表 |
| Meilisearch 索引量 | ≤ 500 万文档 | 只索引必要字段,评论按需二级索引 |
| 项目 | 目标值 | 保障手段 |
|---|---|---|
| 密码存储 | argon2id | 绝不使用 md5/sha1;参数随硬件调整 |
| 会话 | HttpOnly + SameSite=Lax | 不把令牌放 localStorage;Redis 存储会话并支持一键吊销 |
| 越权防护 | 100% 接口覆盖 | 统一中间件做资源级所有权与角色校验 |
| SQL 注入 | 零拼接 | sqlx 编译期校验 + 参数化查询 |
| XSS | 服务端净化 + 前端转义 | Markdown 白名单渲染,禁止 innerHTML 注入原始内容 |
| CSRF | 写操作全防护 | SameSite Cookie + 自定义请求头校验 |
| 数据合规 | 支持注销与导出 | 软删除 + 定期物理清理 + 导出任务走 outbox |
| 项目 | 目标值 | 保障手段 |
|---|---|---|
| 核心接口可用性 | 99.9% | systemd 自动重启 + 健康检查 + 优雅停机 |
| 数据可靠性 | RPO ≤ 5min / RTO ≤ 30min | mysqldump 全量 + binlog 增量,每日恢复演练 |
| 搜索降级 | Meilisearch 挂掉不影响主流程 | 搜索失败回落 MySQL LIKE,并给出提示 |
| 缓存降级 | Redis 挂掉可直连 MySQL | 连接失败快速失败,不做无限重试 |
| 可观测 | 核心链路全埋点 | 请求 ID 贯穿 Nginx → Nitro → axum,日志可按 ID 追踪 |
不要在起步阶段就追求复杂架构。下面是四个阶段的演进路径,每个阶段的架构都与其承载的用户规模相匹配。
阶段一
优先验证产品逻辑,成本最低。关键动作是给 MySQL 与 Redis 设好内存上限,避免 Meilisearch 抢内存把整机拖垮。
阶段二
把计算与存储分开,先解决「一个进程把整机 IO 吃满」的问题;引入每日自动备份异地留存。
阶段三
投票与通知独立扩容;Nginx 层统一鉴权前置与限流策略。
阶段四
一致性与延迟需要显式权衡;跨地域写入坚持单主,避免引入分布式事务复杂度。