先把影响范围画出来
一句话总结
进行任何变更之前,都必须能在纸上画出谁会受到影响、什么会受到影响。如果画不出来,就说明还没有准备好执行这次变更。而且这张图不能靠猜测,必须是从日志、套接字、配置和调度中挖掘出来的。
为什么需要它
假设订单服务器响应缓慢,于是把连接池从 10 调到 30。只是一行配置,回滚看上去也很容易。可这台服务器一共运行着四台实例,四台都使用同一个数据库,连接池合计从 40 变成了 120。PostgreSQL 的 max_connections 默认值通常是 100,而且这个值只有重启服务器才能改变。新连接开始被 sorry, too many clients already 拒绝,而最先倒下的不是订单服务器,而是使用同一数据库的结算批处理。架构图上根本没有画出结算批处理。
以“只改一行配置就行”开头,最后却让服务停止的事故之所以反复发生,是因为没有人确认这一行会触及什么。在陌生系统中尤其如此:系统对我们是新的,对客户却可能已经运行了十年,其间积累了许多从未被记录的依赖。
描绘影响范围的四个问题
1. 谁会调用这个组件(上游) 即使只修改一个配置文件,也必须知道哪些系统会向该进程发送请求。访问日志中的源 IP 分布显示过去一段时间的调用方,当前连接着的套接字显示现在的调用方。
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
ss -tn state established '( sport = :8080 )'
第一条命令按请求数从多到少显示各 IP。不要忽视请求很少的 IP,它们可能是每天运行一次的批处理,或只在月底才来的外部机构。第二条命令中,本地端口是服务端口的连接,其对端地址就是上游。
2. 它会调用什么(下游) 变更结果可能把负载集中到下游。延长超时就是典型例子:我们以为只是留出了更多余量,下游连接却会因此被占用更久。连接池大小、重试次数、并发度也会朝同一个方向给下游施压。
grep -nE '[a-z0-9.-]+:[0-9]{2,5}' /etc/app/*.yml
ss -tnp state established '( dport = :5432 )'
从配置中提取所有 host:port 形式的内容,就能得到下游候选;统计当前连向该端口的连接数,就能估算变更后的连接数。如果有多台实例,别忘了要把单台的值乘以台数。
3. 是否共享状态 要确认是否还有其他系统使用同一个数据库、文件系统或缓存。共享状态很少被画进架构图,事故却常常发生在这里。比较不同服务的配置文件中是否出现相同的值,是一个快速的方法。
grep -hE 'db|dsn|path|dir|cache' service-a.yml service-b.yml | sort | uniq -d
uniq -d 只显示出现两次以上的行。这里出现的数据库地址或目录,就是两个服务共同使用的资源。
4. 什么时候安全 要考虑批处理运行时间、截止日期、结算日。即使技术上安全,时间选错也会造成事故。调度并不只在一个地方。
crontab -l; ls /etc/cron.d /etc/cron.daily
systemctl list-timers --all
crontab 的前五栏依次是分、时、日、月、星期。30 23 * * 5 表示“每周五 23 点 30 分”。systemd 定时器会同时显示下次运行时间(NEXT)和上次运行时间(LAST)。不过,这里能看到批处理几点开始,却看不到几点结束。那要查日志或询问客户才能知道。
先写回滚方案
变更计划的第一行不应是变更内容,而应是如何回滚。
변경: /etc/app/config.yml 의 pool_size 10 → 30
백업: cp -p config.yml config.yml.2026-08-20
되돌림: cp -p config.yml.2026-08-20 config.yml && systemctl reload app
확인: curl -s localhost:8080/healthz 가 200, 에러율 5분간 관찰
소요: 되돌림 2분
如果能够明确写出“回滚:2 分钟”,这次变更才可以执行;写不出来,就说明时机未到。写的时候确认三件事。
- 用
cp -p备份。 不加-p复制,备份会归执行复制的账号所有,权限也会按 umask 重新决定。把这样的备份用mv放回原处,配置文件的所有者和权限就变了,造成“回滚了,服务却读不了配置”的第二起事故。-p会一并保留权限、所有者(在有权限时)和修改时间。 - 确认
reload真的会重新读取那个值。systemctl reload是请求服务重新读取它自己的配置,服务不支持 reload 时会失败。即使支持,有些值也只在启动时读取。那种情况下需要重启,而重启会断开已打开的连接,所需时间和影响都会不同。 - 事先把回滚演练一遍。 在副本上修改配置后运行回滚脚本,再用
diff或sha256sum比较结果是否与原件相同。从未运行过的回滚不是计划,而是愿望。
预先确定观察窗口
变更后要观察到什么时候,应提前决定。只看 5 分钟就离开,那么 10 分钟后发生的问题会被排除在原因候选之外;但也不可能无限期守着,因此应明确写成“观察 30 分钟,关注三个指标:错误率、延迟、队列长度”。
另外,先把变更前的值记下来。错误率 0.4% 这个数字,要知道平时是 0.1% 还是 0.5% 才能判断。如果观察窗口内夹着批处理运行的时间,就无法区分那段时间的变化是来自变更还是来自批处理,所以要挪开窗口。
在实际项目中
- 延长超时后,下游数据库连接耗尽 → 没有观察下游。症状首先出现在使用同一数据库的其他服务的连接被拒,而不是我们自己的服务。
- 夜间部署时,结算批处理恰好正在运行 → 没有询问时间窗口。crontab 里只有开始时间,没有写批处理要跑几个小时。
- 想回滚时,没人知道原始配置 → 修改前没有备份。
- 用备份回滚后服务读不了配置 → 把没加
-p做的备份用mv放了回去,所有者和权限变了。 - 漏掉了每天只来一次的调用方 → 只看了一小时的访问日志。寻找上游时,要在日志保留期允许的范围内尽量看宽。
接下来的实验要做什么
你将接手一个既没有架构图、也没有 Wiki 的客户系统。手头只有两个配置文件、访问日志、轮转设置和 crontab。
需要用本节的命令亲自找出上游、下游、共享状态与批处理窗口,并制定一个把回滚方案写在最前面的变更计划。评分器会故意修改配置,再运行你的回滚脚本,确认是否恢复到原始状态,所以提交之前,请按上面说的在副本上先运行一遍。