“架构演进”这个词很容易让人想到大动作:拆服务、上容器、引入消息队列。但我的站点从一开始就只有一台机器,到现在也还是一台。真正变化的不是机器数量,而是我对“哪些东西应该放在一起、哪些应该分开”的理解。
这篇文章记录这个过程。它不壮观,甚至有点琐碎,但每一步都对应一个具体的问题。
起点:所有东西挤在一起
最早的部署很简单:一个目录,Nginx 指向它,旁边跑一个 Node 小服务,SQLite 文件放在服务目录里。发布方式是登录服务器,git pull,重启服务。
这套东西能跑,但问题很快出现:
- 改一篇文章要重新构建整个站点,构建失败时线上可能处于半更新状态;
- 改接口代码要重启服务,重启期间博客的动态部分不可用;
- 证书、Nginx 配置、定时任务散落在不同地方,换机器时要靠回忆拼起来;
- 出故障时说不清是静态文件的问题、后端的问题,还是 Nginx 的问题。
这些问题不是“性能问题”,而是“边界问题”。所有东西共享一个目录、一个进程生命周期、一次发布动作,任何一个改动都可能影响其他部分。
第一次分层:把构建和运行分开
第一个改动最小:构建在本地或 CI 完成,服务器只接收构建产物。
以前是服务器上 git pull && hexo generate,现在变成:
1 | # 本地或 CI |
服务器上不再装构建工具,也不需要在生产环境跑 Node 构建。发布变成“替换一个目录”,失败时把上一版目录换回来即可。
为了让替换是原子的,我用了两个目录加一个软链接:
1 | /srv/axis/releases/2026-03-01/ |
Nginx 指向 current,切换软链接就是一次发布。回滚同理,指回上一个目录。
这一步之后,“构建失败影响线上”的问题基本消失。
第二次分层:静态与动态分开
站点里有一部分是动态的:评论代理、搜索接口、订阅统计。它们和静态文件放在同一个 Nginx 配置里,但生命周期完全不同。
我按路径把它们分开:
1 | location / { |
静态部分只读文件,动态部分交给独立进程。这样后端重启不会影响页面浏览,静态发布也不会碰后端。两边的日志也分开了,排查时能直接定位到哪一层。
动态服务用 systemd 管理,配了自动重启和内存上限。它不是“高可用”,但至少不会因为一次崩溃把整站拖下水。
第三次分层:数据从代码目录里搬出去
以前 SQLite 文件、上传的图片、日志都在服务目录里。发布时替换目录,很容易误删数据。虽然我一直很小心,但这种“数据和代码混在一起”的结构本身就是隐患。
后来我把数据统一搬到 /srv/axis/data:
data/db/:SQLite 文件和每日导出;data/uploads/:用户上传的图片;data/logs/:应用日志,交给logrotate。
代码目录只放代码和构建产物,数据目录单独备份。发布脚本不碰数据目录,备份脚本只读数据目录。边界清楚之后,误删的概率下降了很多。
没有做的事
这个过程里,我明确放弃了几件事:
- 没有拆成多个仓库。 一个人维护,拆得越细,版本对齐越麻烦。
- 没有上容器编排。 一台机器、三个进程,systemd 足够,Kubernetes 带来的复杂度远超收益。
- 没有引入消息队列。 没有异步任务需要削峰,加了只是多一个要监控的东西。
- 没有做多机高可用。 站点允许几十分钟的不可用,做高可用的成本不划算。
这些决定不是“反对先进技术”,而是按自己的故障容忍度和维护能力算账。
分层带来的真正变化
现在我的站点大概分成三层:
- 接入层:DNS、Nginx、证书,负责把请求接进来;
- 应用层:静态站点和一个小 API,各自独立发布、独立重启;
- 数据层:SQLite、上传文件、日志,单独备份、单独恢复。
它仍然跑在一台机器上,仍然没有高可用。但每个层的边界是清楚的:改静态不会动后端,换证书不会碰数据,恢复数据不需要重新构建站点。
架构演进的价值,不在于用了多少新名词,而在于出了问题时,我能快速回答“这是哪一层的事”。对我来说,这就是分层最大的收益。