自动化脚本瘦身记:从区块链简历项目说起

技术拾荒者
2025-12-22 03:03
阅读 1508

去年秋天,我还在深圳某腾讯系公司做 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 玩得挺溜,为什么不把脚本“容器化 + 声明式”?

目标很明确:

  1. 可重试:失败自动重试,指数退避
  2. 可观测:日志结构化,指标可采集
  3. 资源隔离:别因为一个脚本拖垮整台机器
  4. 配置外置:别把 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

最热最新
暂无评论
技术拾荒者Lv.1
0
影响力
0
文章
0
粉丝