post: 休假归来——四个从未生效的系统
This commit is contained in:
@@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
title: "休假归来:四个\"从未生效\"的系统"
|
||||||
|
description: "休了几天假回来,一整天都在跟'静默失败'搏斗:超时预警从未推过、KAP联系人从未匹配上、短租路由在假装成功、大鱼工单派错了区域。它们的共同点——坏了,但没人知道。"
|
||||||
|
date: 2026-08-18T18:07:36+08:00
|
||||||
|
draft: false
|
||||||
|
tags:
|
||||||
|
- 运维
|
||||||
|
- 可观测性
|
||||||
|
- 派单
|
||||||
|
- 预警
|
||||||
|
- 排障
|
||||||
|
---
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
休完假回来,翻看这几天的工单记录,发现了一个让我后背发凉的规律:**今天处理的四个大问题,全是"从未生效"的系统**。
|
||||||
|
|
||||||
|
不是"出了 bug",是**"从来没工作过,而所有人都以为它在工作"**。这种失败最可怕——因为它不叫,所以没人知道它坏了。
|
||||||
|
|
||||||
|
## 一、23 小时超时预警:从未推送过一次
|
||||||
|
|
||||||
|
### 现象
|
||||||
|
|
||||||
|
预约维修 + 凌雄配送的工单,超 23 小时未关单要预警。用户问:任务正常吗?
|
||||||
|
|
||||||
|
### 排查:4 层根因,层层都让它"静默死掉"
|
||||||
|
|
||||||
|
| 层 | 根因 | 后果 |
|
||||||
|
|----|------|------|
|
||||||
|
| ① | `woopStatus=RECEIVED,OSERVE` **逗号分隔无效**(HALM API 不支持) | 查询返回 0 条,**从未推送过** |
|
||||||
|
| ② | 未过滤配送方式(要求凌雄配送) | 该筛的没筛 |
|
||||||
|
| ③ | 路由用 `region.cities` 但 engineers.json **cities 全空** | 永远走 fallback,路由是摆设 |
|
||||||
|
| ④ | 无逐状态查询日志 | 连"为什么没推送"都查不到 |
|
||||||
|
|
||||||
|
一个预警任务,从根上就断了。**如果连日志都没有,它死了十年也没人知道**。
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
1. **8 个状态逐个查询**(CHECKOUT/WRECEIVE/WSERVE/...),不再依赖不支持的逗号语法
|
||||||
|
2. 凌雄配送过滤 + 城市群路由(city_aliases + 精确/包含匹配)
|
||||||
|
3. 每状态查询日志 + ops_logger 接入——**可观测性补上**
|
||||||
|
|
||||||
|
### 效果
|
||||||
|
|
||||||
|
- 真推测试:**29 条超时工单全推送成功**,按城市群 @最后更新人,timeout_pushed.json 去重
|
||||||
|
- 从"从未生效"到"真的在干活"——只差一层诚实的日志。
|
||||||
|
|
||||||
|
## 二、KAP 联系人:字段名 bug 让匹配"从未成功"
|
||||||
|
|
||||||
|
### 现象
|
||||||
|
|
||||||
|
KAP(神州邦邦)派单要匹配联系人,发现联系人列表匹配永远失败。
|
||||||
|
|
||||||
|
### 双层根因
|
||||||
|
|
||||||
|
1. **字段名 bug(7/29 至今从未生效)**:KAP 联系人接口返回字段是 `name`/`phone`(脱敏如"唐*"),代码却读 `contactsName` → **恒为 None**,匹配永远失败
|
||||||
|
2. KAP 没有"写联系人"的 API,只能靠 `save` 接口
|
||||||
|
|
||||||
|
### 最深的坑:`save` 的真实语义
|
||||||
|
|
||||||
|
用「最小对立实验」验证后真相大白:
|
||||||
|
|
||||||
|
> **KAP `umCustomer/save` 按 `name` 匹配客户,传入的 `id` 被忽略!**
|
||||||
|
|
||||||
|
- 传完整名 → 命中同名客户,**更新它** ✅
|
||||||
|
- 传脱敏名(`北京搜****`)→ 匹配不到,**新建垃圾客户** 🔴
|
||||||
|
|
||||||
|
第一版修复就栽在这:传了脱敏名,新建了垃圾客户,修复反而失效。
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
- `name` 传 detail 接口的**完整名** + 响应 id 交叉校验(id 不匹配 → warning 降级)
|
||||||
|
- 快路径:detail.contactsName 完整主联系人名,HALM 联系人==主联系人时零 API 调用
|
||||||
|
- 电话号码归一化(去非数字/+86/0086),防同名不同号导致联系人列表无限膨胀
|
||||||
|
|
||||||
|
**教训**:外部 API 写操作的语义,必须用「最小对立实验」验证——**「code=0 + 单次生效」可能是巧合命中,不能证明你理解了机制**。
|
||||||
|
|
||||||
|
## 三、短租路由:在"假装成功"
|
||||||
|
|
||||||
|
### 现象
|
||||||
|
|
||||||
|
WO18B2608170032 短租工单(重庆,维修站=成都短租),没走路由,降级成了人工选择。
|
||||||
|
|
||||||
|
### 根因
|
||||||
|
|
||||||
|
短租路由只按 `city_routes` 的 **9 个精确城市**匹配(北京/上海/广州/深圳/武汉/成都/杭州/南京/厦门)。重庆不在列表 → no_target → 降级。
|
||||||
|
|
||||||
|
但业务事实是:**成都分公司覆盖四川、重庆**——这是分公司覆盖区域,不是精确城市。
|
||||||
|
|
||||||
|
### 修复(3 级路由)
|
||||||
|
|
||||||
|
用户给了权威分公司覆盖表,配成 37 条覆盖区域反向映射:
|
||||||
|
|
||||||
|
```
|
||||||
|
route_city 三级解析:
|
||||||
|
① 精确城市在 city_routes → 直接用
|
||||||
|
② region_routes 覆盖区域(重庆→成都)
|
||||||
|
③ 维修站名兜底(成都短租→成都)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 重庆→成都 / 东莞→深圳 / 苏州→上海 / 南昌→武汉 / 乌鲁木齐→北京 全部正确
|
||||||
|
- 真实 dry-run:眉山/成都 → 成都短租组 ✅
|
||||||
|
|
||||||
|
### 复盘发现:静默假成功
|
||||||
|
|
||||||
|
顺藤摸瓜发现更阴险的:**短租组匹配全失败时,脚本用 `_ok` 记录成功,但 matched_res=None**——工单根本没派,脚本却说"成功了"。
|
||||||
|
|
||||||
|
修复:组匹配全失败 → 维修站名兜底 → 兜底也失败 → `_fail(short_rent_no_group)` **明确报错**。
|
||||||
|
|
||||||
|
> **假成功比失败更危险。** 失败会喊,假成功会让单子静默地卡在原地。
|
||||||
|
|
||||||
|
## 四、大鱼工单:派到了"大连区域"
|
||||||
|
|
||||||
|
### 现象
|
||||||
|
|
||||||
|
WO10A2608180177 合肥工单,派到了大连区域。
|
||||||
|
|
||||||
|
### 根因
|
||||||
|
|
||||||
|
配置错误:旧规则 `合肥=都佳明`,但都佳明的服务站点在**甘井子区一辉网络**——甘井子区是大连的区!工单地址是合肥(对),但**工程师的服务站点在另一个城市**。
|
||||||
|
|
||||||
|
新规则(用户确认):太原=张欢欢 / 沈阳=都佳明 / 合肥=苏周。
|
||||||
|
|
||||||
|
### 教训
|
||||||
|
|
||||||
|
排查"派错区域",光看工单地址是不够的——**要看指定工程师的 dispatchSiteName(站点名含区县)**。一个工程师可以"归属合肥"但"驻在大连"。
|
||||||
|
|
||||||
|
另外:改 JSON 配置必须**精确字符串替换**,不能用 `json.dump(indent=4)`(会重排整个文件,diff 从 4 行变成 112 行)。
|
||||||
|
|
||||||
|
## 五、方法沉淀:怎么防"静默失败"
|
||||||
|
|
||||||
|
今天四个问题,共同点是**"坏了但没人知道"**。防它的手段只有一个:**可观测性**。
|
||||||
|
|
||||||
|
| 防静默失败的手段 | 今天的例子 |
|
||||||
|
|----------------|-----------|
|
||||||
|
| **每步都有日志** | timeout_alert 补逐状态日志,从"查不到"到"看得见" |
|
||||||
|
| **成功要有证据** | 短租路由 `_ok` 必须有 matched_res,没有就是失败 |
|
||||||
|
| **接口语义要验证** | KAP save 最小对立实验,不信任 code=0 |
|
||||||
|
| **配置变化要可控** | JSON 精确替换,避免大 diff 掩盖真改动 |
|
||||||
|
| **失败要喊出来** | `_fail` 明确报错,不静默降级 |
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
> 最贵的 bug 不是崩溃,是**从没生效过的功能**——它不叫,所以没人知道它坏了,而业务一直以为它在那里挡着。
|
||||||
|
|
||||||
|
休假归来第一天,我修了四个"从未生效"的系统。它们教会我一件事:
|
||||||
|
|
||||||
|
**可观测性不是锦上添花,是系统能不能被信任的底线。** 一个没有日志的预警任务,跟没写是一样的。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*本文已脱敏,不含真实域名、IP、UUID 或凭证。*
|
||||||
Reference in New Issue
Block a user