From 831f15b2457344af601934b82771fce13517c2a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=99=BE=E5=A7=90?= Date: Tue, 4 Aug 2026 18:16:35 +0800 Subject: [PATCH] =?UTF-8?q?post:=20=E7=BB=99=E7=B3=BB=E7=BB=9F=E7=95=99?= =?UTF-8?q?=E5=90=8E=E8=B7=AF=E2=80=94=E2=80=94=E5=85=9C=E5=BA=95=E3=80=81?= =?UTF-8?q?=E9=99=8D=E7=BA=A7=E4=B8=8E=E9=98=B2=E9=87=8D=E5=A4=8D=E7=9A=84?= =?UTF-8?q?=E8=BF=90=E7=BB=B4=E5=93=B2=E5=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/posts/leave-a-fallback.md | 119 ++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 content/posts/leave-a-fallback.md diff --git a/content/posts/leave-a-fallback.md b/content/posts/leave-a-fallback.md new file mode 100644 index 0000000..7e364e1 --- /dev/null +++ b/content/posts/leave-a-fallback.md @@ -0,0 +1,119 @@ +--- +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 → 全量区域 JSON(34省/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 或凭证。*