我把 cron 迁到 FunctionGraph 的返工记录
我为什么动 cron
5 月 19 日补丁夜,02:13 值班电话把我叫醒,告警名是 reconcile_lag_gt_15m,Prometheus 里 lag 指标已经到 19m42s。我们订单服务在华为云 ECS,业务机 s6.large.2(2 vCPU / 4 GiB),旁边单独一台 s6.small.1(1 vCPU / 2 GiB)只跑 crond。系统 CentOS 7.9,内核 3.10.0-1160.108.1.el7.x86_64,cronie 1.4.11,脚本解释器 Python 3.9.18。
*/10 * * * * /usr/bin/python3 /srv/jobs/reconcile.py >> /var/log/reconcile.log 2>&1
任务逻辑是固定的,每 10 分钟拉支付网关 CSV,再调内网 /internal/reconcile 写状态。4 月漏跑 3 次,间隔分别是 17 分钟、24 分钟、47 分钟。我把日志和系统事件对了一遍,命令是 journalctl -u crond --since '2026-04-14 03:00:00' --until '2026-04-14 04:30:00',03:20 还有执行记录,03:30 没有新子进程,内核日志同时出现目录索引写满。
Apr 14 03:20:01 crond[2187]: (root) CMD (/usr/bin/python3 /srv/jobs/reconcile.py >> /var/log/reconcile.log 2>&1)
Apr 14 03:21:02 kernel: EXT4-fs warning (device vda1): ext4_dx_add_entry: Directory index full
Apr 14 03:30:01 CROND[2334]: (root) CAN'T OPEN OR CREATE /var/log/reconcile.log: No space left on device
那次我手工补了 612 条流水,先在 MySQL 跑 select channel_txn_id from pay_txn where biz_date='2026-04-14' and status='PENDING',再拿脚本逐条重放,1 小时 20 分才把对账抹平。
这个方案我一开始是拒绝的。
迁到 FunctionGraph 的返工细节和现状
我在 FunctionGraph(华北-北京四)先建了 Python3.9 函数,内存 256 MB,超时 3 秒,触发器填 0 */10 * * * ?。第一次上传只放 index.py,执行 1 分钟就报 Runtime.ImportModuleError: Unable to import module 'index': No module named 'requests'。我图快,把本地 .venv 整个 zip 上去,控制台拒绝,包 126.4MB,平台上限 40MB。
Code package size exceeds limit, current size 126.4MB, max size 40MB
第二次我又翻车,命令写成 zip -r fg-reconcile.zip package,压缩包多了一层 package 目录,入口找不到,报 No module named 'index'。我还做过一次错误尝试,在 Mac 本地打包后直接传,运行时出现 ImportError: /var/task/charset_normalizer/md.cpython-39-darwin.so: invalid ELF header,这个坑我以前见过,二进制轮子架构不对。
我改成在一台 x86 Linux 构建机打包,ECS 规格 c6.large.2,系统 Ubuntu 22.04,pip 23.3.2,步骤拆得很死,避免再靠运气。
python3.9 -m venv .venv
source .venv/bin/activate
mkdir -p package
pip install -r requirements.txt -t package --no-cache-dir --upgrade
# requirements.txt
requests==2.31.0
pymysql==1.1.1
urllib3==2.2.1
charset-normalizer==3.3.2
cp index.py package/
cd package && zip -r ../fg-reconcile.zip .
这次包大小 9.6MB,上传 37 秒,发布版本 6 后函数能进 handler,但日志还是一片红,核心报错是内网地址连不上。
ConnectTimeoutError: HTTPConnectionPool(host='10.23.17.8', port=8080): Max retries exceeded with url: /internal/reconcile (Caused by ConnectTimeoutError(..., 'Connection to 10.23.17.8 timed out. (connect timeout=2)'))
原因很直白,我忘了绑 VPC,接口只开了内网 8080。我给函数挂了 vpc-prod、subnet-app-a,新建安全组 sg-fg-job,出方向只放行 10.23.17.8/32:8080,DNS 和 NTP 保留默认。网络打通后又撞上平台超时,监控里连续 6 次 Task timed out after 3.00 seconds,最大 Duration 3008 ms。
我把改动写进变更单,能对上问题来源。
- timeout 从 3 秒改到 15 秒,因为支付网关高峰响应在 4 到 6 秒,3 秒等于必炸
- 内存从 256 MB 调到 512 MB,CPU 配额跟着翻倍,单次 CSV 解析从 780 ms 降到 430 ms
- requests 从
timeout=2改成timeout=(2,10),加Retry(total=2, backoff_factor=0.6, status_forcelist=[502,503,504])
我还试过把 timeout 直接拉到 60 秒想兜底,这个方案我现在看是错的,坏请求挂更久,p95 从 5.8 秒涨到 9.1 秒,09-23 那天计算费单日 0.21 元涨到 0.47 元,第二天就回滚到 15 秒。
幂等这块我没上复杂组件,还是 MySQL 唯一键 (biz_date, channel_txn_id),SQL 从 insert ignore 改到 ON DUPLICATE KEY UPDATE updated_at=NOW(),因为我要追重复写入的时间波峰,ignore 会把证据吃掉。失败通知走 SMN 主题 prod-reconcile-alert,订阅企业微信机器人,最近 30 天失败到消息落地平均 8 秒,最慢 19 秒。
周六 01:07 我把 cron 机关了。
账单我按服务筛了 2026-09-01 到 2026-09-30,原来那台 s6.small.1 折月 78.6 元,40GB 高 IO 盘 18.4 元,公网带宽分摊 6.1 元,合计 103.1 元;迁到 FunctionGraph 后执行 4467 次,请求费 0.09 元,计算费 1.86 元,LTS 0.73 元,SMN 0.04 元,总计 2.72 元。4467 次里失败 2 次,一次 9 月 12 日 10:20 网关 502,原始体 {'code':'UPSTREAM_BAD_GATEWAY'};另一次 9 月 21 日 03:10 主从切换,报 pymysql.err.OperationalError: (2013, 'Lost connection to MySQL server during query'),都在 5 分钟内靠重试补齐。
现在还有个尾巴,定时触发偶发延迟,控制台最大 94 秒,日志里 scheduled_time=14:00:00,actual_start_time=14:01:34,报表会短时间缺口。下周我准备把关键对账改成事件驱动,定时任务只做兜底扫描,去重窗口先按 10 分钟跑一周,再看要不要改成 30 分钟。
- 点赞
- 收藏
- 关注作者
评论(0)