13 分钟
项目全解析

本产品的回滚与数据备份演练

发版出事怎么止血:PM2 切版、JSON 备份恢复、App 端配合

  • 记住发版出事时的第一动作是回滚止血
  • 理解 PM2 下如何切回上一版本(概念)
  • 会写带时间戳的 JSON 数据备份与恢复
  • 理解 Capacitor 强制更新与服务端回滚如何配合
💡高中搭桥:像写文章先存稿

写长文前你会先存一份备份,写错了能一键撤回、回到上一版。发版也一样:每次上线前先把当前代码和数据存好,新版本出问题,立刻切回上一版,而不是现场抓瞎。

发版出事怎么办

新版本上线后 5xx 飙升、用户投诉刷进来,第一反应不是登录服务器逐行查日志,更不是急着再发一个修复版——而是先回滚到上一个能用的版本止血,把影响面压住,再慢慢查原因。

服务端回滚:PM2 + 零依赖 server.js

本产品后端就是一个 node server.js,零依赖。回滚本质很简单:每个版本一个目录,用软链接指向当前版本,出问题把链接切回旧目录再重启进程即可。

Python
# 服务端回滚概念(零依赖 server.js)
print("""
# 目录结构:每个版本独立存放
/releases
  /v2.3.0/server.js
  /v2.2.0/server.js
/current  -> 指向当前在跑的版本

# 回滚 = 切回上一版再重启进程
ln -sfn /releases/v2.2.0 /current
pm2 restart pygood

# 30 秒后看 /health 是否恢复,没恢复再查日志
""")

数据备份与恢复

代码能从 git 回滚,用户数据不行。本产品数据是 JSON 文件(或 SQLite),必须定时备份,且备份文件名带时间戳,恢复时才知道哪个是最新。下面用内存数据模拟「备份→写坏→恢复」全过程。

示例代码(可运行)

Capacitor 端:强制更新与服务端回滚配合

服务端回滚后,已经装了新 App 版本的用户可能还在打已经回退的接口。这时服务端可以下发「最低支持版本」,App 启动时发现自己版本太低就弹窗强制升级(或提示回退)。版本号策略以各平台官方最新规范为准。

🐍把灰度/回滚/备份对到本产品

灰度:本产品零依赖,先在局域网或小批用户上开新版本观察回滚:代码用软链接切回 /releases 里的旧目录,pm2 restart备份:用户数据是 JSON 文件,定时复制加时间戳留存,保留最近 N 份

备份策略:全量 / 增量 / 差异

备份不是「把文件复制一份」这么简单,三个概念要分清。全量备份:每次把整个数据库完整复制一份,恢复最简单,但慢、占空间。增量备份:只备份「上次备份之后变了的部分」,跑得快省空间,但恢复麻烦——要先恢复全量、再依次把每段增量重放。差异备份:备份「上次全量之后变了的部分」,介于两者之间。对本产品这种小 JSON 数据,每天全量一次就够,别上增量把自己绕进去。

💡三种备份怎么选

全量:每次完整复制,恢复最简单,适合小数据增量:只存变化块,最省空间但恢复要串多份差异:存上次全量以来的变化,恢复只需全量+一份差异本产品:数据量小,每天全量一份、保留最近 7 份即可

RTO / RPO:两个必须背下来的数

出事后跟老板对 SLA,就靠这两个缩写。RTO(Recovery Time Objective,恢复时间目标):系统最多能忍多久不可用?是 5 分钟还是 4 小时?RTO 决定你要不要做自动故障切换——5 分钟以内你根本来不及手动登录服务器切版本。RPO(Recovery Point Objective,恢复点目标):最多能容忍丢多久的数据?是 5 分钟还是 1 天?RPO 决定你备份频率——RPO 是 1 小时,你就至少每小时备一次。RTO 管「多久能恢复」,RPO 管「最多丢多少数据」。

⚠️别把 RTO/RPO 搞混

RTO = 恢复时间目标(多久恢复,管可用性)RPO = 恢复点目标(丢多少数据,管数据持久性)举个例子:RTO=30分钟、RPO=5分钟,意味着出事你要在30分钟内恢复、且最多丢5分钟内的数据——这要求几乎实时的备份/主从同步。

数据库备份要停服吗

很多人以为备份数据库必须停服,怕备份过程中数据被写一半。现代数据库(PostgreSQL、MySQL、SQLite)都支持在线热备份:在服务正常运行时做一致性快照,不用停服。SQLite 用 .backup 命令就能在线复制一份一致性快照。本产品数据量小,凌晨低峰期用 SQLite 在线备份命令复制一份即可,用户完全无感知。只有极老的、不支持热备的数据库才需要停服窗口。

回滚时数据不兼容怎么办

最疼的回滚不是代码回滚,是数据库回滚。新版代码给 users 表加了一列 phone,跑了一天;现在要回滚到旧版,旧代码不认识 phone 这一列——这通常不致命(多一列旧代码不理会)。但反过来:新版把 phone 列删了、或者把「手机号」从一列拆成「国家码+号码」两列,回滚到旧版就直接崩。这就是前向/后向兼容问题。

🚫迁移要做前后向兼容

规则一:加列、加表是安全的(旧代码不理新列)。规则二:删列、改列含义必须分两步发——先发「新代码兼容旧数据」,稳定后再发「清理旧字段」。规则三:每次 schema 变更都要写一份回滚脚本,发版前演练过,别等出事现写。

选择题

新版本上线后发现严重故障,第一步应该做什么?

选择题

公司要求 RPO=5 分钟,意味着什么?

填空题填写空白处的代码
# 备份文件名加时间戳 # users.json + 20260101_120000 → users.20260101_120000..json
💡本节是本产品的回滚备份演练,发布策略理论归 s15-m7-l1

这里练的是本零依赖 server.js:软链接切版 + pm2 restart + JSON 时间戳备份;s15-m7-l1 系统讲灰度发布策略(金丝雀/蓝绿/特性开关)、限流幂等与备份恢复演练的通用方法。这里管「本产品怎么切回去」,那边管「大团队怎么灰度、为什么」。

资深工程师加餐

底层原理 · 大厂视角 · 工程经验,点卡片展开

移动网络不稳定,离线优先的思路是:本地是唯一可信数据源,UI 先读本地立即响应,后台再静默与服务器同步,冲突按策略合并。它带来秒开、可离线使用的体验,代价是要处理同步状态、冲突解决和幂等。本项目把课程与 Python 运行时全部内置、开箱即用,正是这一思想的体现——把「没有网络也能完整学习」当成核心能力而非降级方案。

挑战任务

备份命名与回滚阈值

简单+50 XP

实现 backup_name(data_file, ts):把 users.json 和时间戳 20260101_120000 拼成 users.20260101_120000.bak.json;再实现 should_rollback(error_rate, latency_ms):错误率≥0.05 或 延迟≥2000ms 返回 rollback,否则返回 watch。

备份命名与回滚阈值
1 个测试用例

课后作业

场景分析+估算:RTO/RPO 与备份策略

中等+25 XP

下面 3 个场景,选合适的 RTO/RPO 并判断做法对错,答案写进列表。 场景①:内部员工打卡系统,挂 2 小时大家也能接受,数据丢一天可接受 → RTO=___分钟, RPO=___分钟 场景②:支付系统,多扣一分钱都不行,挂 1 分钟都被骂 → RTO 应该更(短/长),备份频率更(高/低) 场景③:新版给 users 表加了一列 nickname,要回滚到旧版 → 这次回滚(安全/危险),因为加列是___向兼容

场景分析+估算:RTO/RPO 与备份策略
1 个测试用例