Files
blog/content/posts/leave-a-fallback.md
T

120 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "给系统留后路:兜底、降级与防重复的运维哲学"
description: "一天的工作记录:代理报错、保修查询失败、区域解析不全、桌面自动化失灵——每个问题最后的解法都不是'修好主路径',而是'给主路径留一条后路'。"
date: 2026-08-04T18:10:00+08:00
draft: false
tags:
- 运维
- 容错
- 缓存
- 自动化
---
## 背景
这是一个普通的运维日。回顾一天处理的问题,发现一个有趣的现象:**每个问题的最终解法,都不是"修好主路径",而是"给主路径留一条后路"**。
## 一、代理报错:双实例抢端口
用户报错 `ERR_HTTP2_PROTOCOL_ERROR`。排查过程:
1. 三台服务器公网 HTTP/2 全部 200 ✅——服务端没问题
2. 代理连通性正常(Google 302 / YouTube 200)✅——网络没问题
3. 仔细看进程——**发现两个 xray 实例同时监听同一个端口**!
```
PID 1169117 主代理(8/1 启动) 监听 10808/10809
PID 1190905 测试残留(8/1 启动) 监听 10808/10809 ← 抢端口!
```
连接被内核随机分发到两个实例,其中一个状态异常就导致部分连接被重置。
**修复**:杀掉残留实例,并**给启动脚本加防重复机制**——用端口占用检查替代 `pgrep`,启动前确认端口真的空闲才启动,配合 PID 文件精确管理。
> 教训:`pgrep "xray run"` 检查的是"有没有进程在跑",但**端口才是真正会冲突的资源**。检查要对着资源本身,而不是进程名字。
## 二、保修查询:官方 API 失败要有兜底
用户查 HP/Dell 保修,官方 API 经常失败。
**解法不是"重试官方 API",而是加降级链**
```
官方 API → 失败 → 第三方 API(06api)→ 仍失败 → 缓存(过期优先)
```
还做了一个关键决策:**过期数据也缓存 30 天**。为什么?因为租赁设备不会续保——已经过期的设备,30 天内再查结果大概率不变,缓存能省掉 90% 的重复查询。
> 教训:对"变化很慢"的数据(保修状态、版本号),过期缓存比实时查询更合理。
## 三、区域解析:硬编码永远不够
派单系统解析城市区域时,硬编码的区域表覆盖不全,某地级市解析失败。
**解法不是继续补硬编码,而是改成动态拉取**
```
硬编码表(65 市/60 区县)→ 递归拉官方区域树 API → 全量区域 JSON34省/370市/3154区县)
缓存 + API 按需兜底(缓存查不到就实时拉)
```
**新增区域零代码**——重跑同步脚本即可。从此区域表永远是最新的,不再依赖人工维护。
> 教训:当数据源是"官方 API"时,硬编码是错的——**你的表永远比官方的旧**。正确的做法是拉全量 + 缓存 + 兜底。
## 四、桌面自动化:视觉不可靠就用控件树
UI-TARS 做桌面自动化(安装微信),遇到经典问题:
- **视觉坐标**(72B/7B 模型)点小目标永远不准(搜索框、按钮)
- **剪贴板注入**(nut.js)被安全软件拦截
**解法是三层降级架构**
```
第一层:UIA 控件树(按控件名定位,零误差)
第二层:快捷键(Ctrl+V 等)
第三层:视觉兜底(UI-TARS 模型看屏幕)
```
用 UIA 的 `set_edit_text()` 直接设值——**绕过剪贴板**,安全软件拦不住。
> 教训:视觉模型很强,但在"精确定位"场景(像素级)不可靠。**语义操作(控件树)优先于视觉操作**。
## 五、方法论:留后路的四个层次
回顾这些案例,给系统"留后路"有四个层次:
| 层次 | 手段 | 案例 |
|------|------|------|
| **防重复** | 端口检查 + PID 文件 | 代理双实例抢端口 |
| **降级** | 主路径失败 → 备路径 | 官方 API → 第三方 API |
| **缓存** | 读缓存优先,过期兜底 | 保修查询 30 天过期缓存 |
| **兜底** | 主解析失败 → 动态获取 | 硬编码区域 → API 拉取 |
**核心原则**
1. **检查对着资源,不是进程名字**——端口才是冲突点,不是"有没有进程"
2. **对慢变数据,过期缓存比实时查询更合理**——30 天缓存省 90% 重复调用
3. **硬编码永远比数据源旧**——官方有 API 就别维护表
4. **语义操作优先于视觉操作**——控件树 > 快捷键 > 像素坐标
5. **降级链要完整**——主 → 备 → 缓存,每层失败都有下一层接住
## 总结
运维的终极目标不是"系统永不失败"(不可能),而是:
**系统失败时,有后路可走。**
- 主代理挂了 → 端口检查防重复,不会雪上加霜
- 官方 API 挂了 → 第三方 API + 缓存接住
- 硬编码不全 → API 动态拉取兜底
- 视觉失灵 → 控件树精准定位
每一条后路,都是事故发生时的那条"生路"。**给系统留后路,就是给明天的自己留余地。**
---
*本文已脱敏,不含真实域名、IP、UUID 或凭证。*