System Architecture

架构设计

架构的目标不是「先进」,而是让每个模块都能独立演进、每个热点都能被缓存拦住、每条链路都能被观测。本页给出六层分层架构、两条核心链路的完整拆解,以及从单机到多地域的四阶段部署演进。

架构分层
6 层
核心链路
2 条
非功能目标
4 组
部署阶段
4 阶段

六层分层架构

自上而下依次为客户端、接入网关、应用服务、异步调度、数据存储与基础设施。分层的关键原则是「上层依赖下层,同层之间通过事件解耦」。点击任一层可查看该层的组件职责。

系统分层架构图
点击任一层查看说明
客户端层Browser / ClientNuxt 4 SSR 输出服务端直出 HTML,首屏不依赖 JSVue 3 SFC 组件菜单 slug 页面按需动态加载Tailwind CSS 4构建期生成样式,无运行时 CSS-in-JS渐进水合先可读后可交互,弱网也有内容接入与边缘层Nginx / EdgeNginxTLS 终止、HTTP/2、静态与缓存头反向代理路由/ → Nitro(3000),/api/* → Rust(8080)接入限流limit_req 按 IP 与路径分级Let's Encrypt 续期certbot webroot,自动续签应用层ApplicationNuxt Nitro (Node 22)SSR 渲染、server routes 聚合接口Rust API (axum)业务唯一入口,无状态可水平扩展服务端会话HttpOnly Cookie + Redis 会话存储统一错误与校验请求体结构校验、错误码统一映射领域服务层Domain Modules (Rust crate)identity注册、登录、会话、资料、信任等级community社区、菜单树、订阅、版主content帖子、修订、标签、投票帖comment评论树、折叠、排序、提及vote幂等投票、热度分数、防刷search索引同步任务、查询转发 Meilisearchnotify事件扇出、聚合、未读计数moderation举报、审核队列、封禁与申诉数据与索引层MySQL / Redis / MeilisearchMySQL 8.4InnoDB + utf8mb4_0900_ai_ci,唯一事实来源Redis 7会话、热点缓存、ZSet 排行、轻量队列、限流计数Meilisearch全文检索与即时搜索,索引由 outbox 驱动同步本地媒体目录上传文件落盘 + Nginx 直接托管,后续可迁对象存储基础设施层Ubuntu 26.04 LTSsystemd 单元api / web / worker 三类服务托管与自愈防火墙与密钥ufw 仅开放 22/80/443,密钥登录备份与演练mysqldump 全量 + binlog 增量,每日校验日志与监控journald 汇聚,健康检查与磁盘水位告警

核心链路拆解

把最复杂的两个业务链路逐步展开。阅读时请重点关注「哪些步骤是同步的、哪些必须异步」,这是性能设计的核心分界线。

核心链路一 · 发帖到分发

从客户端提交到索引与召回完成,说明为什么重活必须异步。

  1. 1

    表单提交

    Nuxt 页面在服务端渲染表单,提交走 Nitro server route 或直连 Rust API,携带 HttpOnly Cookie 会话。

  2. 2

    鉴权与校验

    Rust 从 Redis 取会话,校验登录态、社区发帖权限与信任等级门槛,并做请求体结构校验。

  3. 3

    事务写主库

    单个 MySQL 事务内写 posts、post_revisions 首版、post_tags 关联,提交后立即返回帖子 ID。

  4. 4

    缓存失效

    删除该社区列表缓存的 Redis key;热度分数初始值写入 Redis ZSet。

  5. 5

    Outbox 落盘

    同一事务内写入 outbox 表(事件 + 状态),保证「数据改了,事件一定发得出」。

  6. 6

    异步消费者

    后台 worker 轮询 outbox:投递 Meilisearch 索引、生成通知、刷新作者统计,成功后标记已处理。

  7. 7

    订阅扇出

    notify 模块按社区订阅者与作者关注者聚合通知,写 notification 表并递增 Redis 未读计数。

  8. 8

    响应与回源

    前端拿到帖子 ID 后跳转详情页;详情页 SSR 直接查库渲染,首屏无需等待索引完成。

核心链路二 · 投票与排序重算

投票看似简单,实际是幂等、一致性、性能三者的博弈。

  1. 1

    点击投票

    前端乐观更新按钮与计数,标记 pending;失败时回滚并提示。

  2. 2

    限流与风控

    Nginx 按 IP 限流,Rust 侧按用户与设备指纹计数,异常频率直接拒绝。

  3. 3

    幂等写入

    votes 表以 (user_id, target_type, target_id) 唯一索引兜底,重复请求返回既有状态而非报错。

  4. 4

    状态机判定

    赞 → 再点赞视为取消;赞 → 踩视为改投。三种状态在服务端单一入口内收敛。

  5. 5

    计数增量

    Redis HINCRBY 维护实时票数,避免每次 COUNT(*);MySQL 中保留权威计数列。

  6. 6

    热度重算

    按 log10(max(|v|,1))×sign(v) + age/45000 重算 hot_score,更新 Redis ZSet 与数据库快照。

  7. 7

    Karma 与权重

    异步更新作者 Karma;低信任等级账号的票按权重系数折算,防小号刷分。

  8. 8

    一致性兜底

    每日离线任务用 MySQL 权威数据校正 Redis 计数与分数,消除长期漂移。

非功能设计目标

性能、容量、安全、可用性四组量化目标。这些目标必须在需求阶段确定,而不是上线后再优化——架构决策的成本在写第一行代码时就已锁定。

性能目标

项目目标值保障手段
列表接口 P95≤ 150msRedis 缓存 + 预计算 hot_score,禁止实时聚合
帖子详情 P95≤ 250ms评论树分层加载,首屏只取前 N 条顶层评论
SSR 首字节 TTFB≤ 400msNitro 就近查库 + Redis 会话,避免串联调用
搜索响应 P95≤ 80msMeilisearch 内存索引 + 限定字段与分页深度
首屏可交互 LCP≤ 1.8sSSR 直出 + 关键 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 ≤ 30minmysqldump 全量 + binlog 增量,每日恢复演练
搜索降级Meilisearch 挂掉不影响主流程搜索失败回落 MySQL LIKE,并给出提示
缓存降级Redis 挂掉可直连 MySQL连接失败快速失败,不做无限重试
可观测核心链路全埋点请求 ID 贯穿 Nginx → Nitro → axum,日志可按 ID 追踪

部署拓扑演进

不要在起步阶段就追求复杂架构。下面是四个阶段的演进路径,每个阶段的架构都与其承载的用户规模相匹配。

Stage 10 – 5 千日活

单机 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 抢内存把整机拖垮。

Stage 25 千 – 5 万日活

前后端分机

阶段二

  • Web 节点(Nginx + Nuxt SSR)× 2 台,Nginx 做上游负载
  • API 节点(Rust axum)× 2 台,无状态可随时扩缩
  • MySQL 独立主机 + 只读副本承担列表查询
  • Redis 独立部署并开启持久化
  • Meilisearch 独立主机,索引不走主库磁盘

把计算与存储分开,先解决「一个进程把整机 IO 吃满」的问题;引入每日自动备份异地留存。

Stage 35 万 – 50 万日活

服务化与分片

阶段三

  • 按领域拆分 Rust 服务:内容 / 评论 / 投票 / 通知 / 搜索
  • Redis 承担跨服务事件队列(Stream)或引入专用消息队列
  • MySQL 按 community_id 分库分表,历史分区归档
  • Meilisearch 多节点 + 索引分片
  • 引入 CDN 托管静态资源与媒体

投票与通知独立扩容;Nginx 层统一鉴权前置与限流策略。

Stage 450 万日活以上

多地域

阶段四

  • 多地域部署 + DNS 就近接入
  • MySQL 单主多从跨地域复制,冲突在应用层解决
  • 多级缓存:边缘 → 区域 → 中心
  • 全链路灰度发布与故障演练

一致性与延迟需要显式权衡;跨地域写入坚持单主,避免引入分布式事务复杂度。