自动化脚本瘦身记:从区块链简历项目说起
去年秋天,我还在深圳某腾讯系公司做 Android 开发,天天和 Gradle、ProGuard、APK 体积斗智斗勇。后来团队决定搞跨平台,领导一句“Flutter 性能好、热重载爽、一套代码跑三端”,我就被迫转岗了(其实是自己偷偷学了几个月,想跳槽但没敢走)。现在主力写 Flutter,Vim 配置比 IDE 还复杂,CI/CD 流水线倒背如流,连 K8s 的 YAML 文件都能盲打。
上周五晚上十点,我正用 Vim 调一个 Bloc 状态管理的边界 case,突然钉钉弹出一条消息:“老张,你那个自动化脚本又挂了!双11前夜你让我怎么睡?”
我叹了口气——这破脚本,是我三个月前为了应付一个“区块链+求职”内部创新项目临时写的。项目本身有点中二:让用户上传简历,系统自动解析技能标签,再映射到某个“去中心化职业图谱”上,最后生成一个 NFT 形式的“数字简历”。听起来很 Web3,其实后端就是个普通 Spring Boot + IPFS 存储,前端用 Flutter 写了个壳子。
但问题出在自动化流程上。
脚本初版:糙快猛的典型代表
最初需求很简单:每天凌晨 2 点,从数据库捞出新注册用户,调用简历解析服务(Python 写的),结果存回 DB,并触发一个通知。
我随手写了段 Bash 脚本:
#!/bin/bash
echo "Starting resume parser job..."
cd /opt/resume-service
source venv/bin/activate
python main.py --batch-mode --date $(date -d "yesterday" +%Y-%m-%d)
if [ $? -ne 0 ]; then
curl -X POST "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx" \
-H "Content-Type: application/json" \
-d '{"msgtype": "text", "text": {"content": "简历解析失败!"}}'
exit 1
fi
echo "Job finished."
上线一周,问题来了:
- 某天 Python 依赖升级,虚拟环境崩了,脚本报错
ModuleNotFoundError,但监控没告警(因为 exit code 是 0?别问,问就是 shell 逻辑写错了) - 有次 IPFS 节点临时不可用,脚本卡住 40 分钟,阻塞了后续所有定时任务
- 更离谱的是,测试同学误操作把测试数据导入生产库,脚本一口气处理了 10 万份“简历”,其中 9 万份是 “Java, Spring, Blockchain, Solidity, CEO of Mars” —— 对,产品经理真写了这种技能组合进测试数据
当时我真的想砸电脑。但转念一想:这不正是优化的好机会?
重构思路:云原生思维介入
既然我平时玩 K8s 玩得挺溜,为什么不把脚本“容器化 + 声明式”?
目标很明确:
- 可重试:失败自动重试,指数退避
- 可观测:日志结构化,指标可采集
- 资源隔离:别因为一个脚本拖垮整台机器
- 配置外置:别把 webhook URL 写死在脚本里
于是,我干了三件事:
第一步:脚本改造成独立微服务
用 Dart(对,就是 Flutter 那个 Dart)重写了核心逻辑。原因很简单:团队 Flutter 工程师多,Dart 比 Python 更可控,而且我能复用 http、dio、logging 这些熟悉的包。
关键代码片段:
// lib/job.dart
Future<void> processResumes(DateTime date) async {
final logger = Logger('ResumeJob');
try {
final resumes = await db.fetchNewResumes(date);
for (final resume in resumes) {
// 跳过明显 fake 的数据,比如包含 "Blockchain CEO"
if (_isSuspicious(resume.skills)) {
logger.warning('Skipping suspicious resume: ${resume.id}');
continue;
}
final parsed = await ResumeParser.parse(resume.rawText);
await ipfsClient.store(parsed.toJson());
await db.updateStatus(resume.id, Status.done);
}
} on HttpException catch (e) {
logger.severe('IPFS call failed', e);
throw JobRetryableException(e.message); // 自定义可重试异常
} catch (e) {
logger.severe('Unexpected error', e);
throw JobFatalException();
}
}
注意那个 _isSuspicious 函数——专门过滤“区块链神童”简历。现实教会我:自动化之前,先防人类。
第二步:用 CronJob 跑在 K8s 上
把脚本打包成 Docker 镜像,推到私有仓库,然后写了个 CronJob:
# cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: resume-parser
spec:
schedule: "0 2 * * *" # UTC 时间,对应北京时间 10 点
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: parser
image: registry.internal/resume-parser:v1.2
envFrom:
- configMapRef:
name: resume-parser-config
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
restartPolicy: OnFailure
配合 ConfigMap 管理配置:
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: resume-parser-config
data:
WEBHOOK_URL: "https://qyapi.weixin.qq.com/..."
MAX_RETRY: "3"
IPFS_TIMEOUT_SEC: "30"
第三步:加监控 & 告警
- 所有日志输出 JSON 格式,接入 ELK
- 在脚本里埋了 Prometheus metrics:
final _jobDuration = Summary(
'resume_job_duration_seconds',
'Time spent processing resumes',
);
final _failedCount = Counter(
'resume_job_failures_total',
'Number of failed jobs',
);
- 用 Alertmanager 配规则:如果连续两次失败,钉钉告警
效果对比:从“救火”到“躺平”
| 指标 | 旧脚本 | 新方案 |
|---|---|---|
| 平均执行时间 | 12 分钟 | 4.5 分钟 |
| 失败率 | ~15% | <1% |
| 故障恢复时间 | 手动介入(平均 2h) | 自动重试(5 分钟内) |
| 资源占用 | 不可控(常占满 CPU) | 严格限制(CPU < 0.2 core) |
| 可维护性 | “谁碰谁死” | 团队新人也能改 |
最爽的是,现在脚本挂了,我手机会收到钉钉消息,但不用立刻爬起来——因为大概率它自己就 retry 成功了。上周二凌晨 3 点 IPFS 节点抖了一下,脚本第一次失败,第二次成功,全程无人干预。
产品经理看到报表后还夸我:“你们技术最近稳得很啊!” 我心里苦笑:哪是稳,是把人肉运维换成了云原生自动化罢了。
一点反思:自动化不是写完就完事
这次优化让我意识到:很多团队(包括我以前)把“自动化”等同于“写个脚本”,但真正的自动化工程应该包含:
- 幂等性:重复执行不能产生副作用(比如重复发通知)
- 可观测性:你不知道它挂了,等于没自动化
- 防御性:输入数据可能很脏,外部依赖可能很脆
- 成本意识:一个脚本跑在 8C32G 的机器上,不如买瓶茅台祭天
另外,那个“区块链简历”项目,后来因为业务方向调整被砍了。但脚本留了下来,现在被复用在用户行为分析、日志归档等多个场景。有时候,项目的遗产不是功能,而是基础设施。
给同行的建议
如果你也在搞自动化脚本,不妨自问几个问题:
- 这个脚本能无脑重跑十次而不炸吗?
- 如果半夜挂了,我能靠监控知道,而不是靠老板打电话?
- 它会不会因为某条异常数据(比如简历写着“精通量子计算和区块链”)而雪崩?
别小看这些“脏活”。在大厂,能写漂亮架构的人不少,但能把自动化做得稳如老狗的,才是真·生产力担当。
顺便说一句,我这份“优化自动化脚本”的经历,已经写进新简历的“项目经验”里了。虽然没提区块链(毕竟项目黄了),但面试官看到“K8s CronJob + Dart 微服务 + 自愈能力”,眼睛都亮了。
毕竟,在这个 AI 写代码的时代,会修水管的人,永远有饭吃。

评论 0