流程改了三个步骤,上线后把一百家店铺的价格全部调错。
这种事故只要经历过一次,就会把发布流程刻进骨头里。
我们在做店群自动化的过程中,经历过一段特别混乱的时期。影刀的流程文件用微信传来传去,文件名里带着日期和修改人——采集_张三_0518_fix.flow——哪个是最新版本没人说得清。一台执行机上一个版本,另一台又是另一个版本,出了Bug根本不知道是哪次改动引入的。
这不是影刀的锅,而是我们根本没把RPA流程当成软件交付来看待。
后来我们把自动化流程的发布,完全按照后端服务的CI/CD标准来设计,才真正告别了那种失控感。这篇文章就是这套体系的完整还原。
一、把影刀流程当成代码来管
影刀的流程文件是一个二进制或者特定格式的打包文件(取决于版本),没办法直接做文本diff,这是最大的拦路虎。但我们不需要在流程文件内部做diff,只需要在版本层面做管理和追溯。
我们做的第一件事,就是强制所有流程文件必须走Git托管。虽然不能逐行对比流程里的拖拽节点,但可以做到:
- 每个版本有唯一的commit hash,版本号不可篡改
- 每次修改都有提交记录和说明
- 版本可以通过CI打包为一个可部署的制品
- 任意历史版本随时可查可取
影刀的流程文件直接放在仓库里,配合一个metadata.json描述流程的元信息:
{
"flow_name": "temu_order_collect",
"version": "1.3.2",

"platform": "temu",
"safety_level": "safe",
"author": "林焱",
"commit": "a3f2b1c",
"dependencies": {
"browser_type": "chromium",
"min_browser_pool_version": "2.1.0"
},

"change_log": "修复订单列表分页采集遗漏的问题"
}
这个元信息文件随着流程文件一起提交,Git标签自动与version对齐,CI在构建时校验一致性,不一致则构建失败。这样我们就有了一个可靠的单点真相来源。
二、制品管理与部署包结构
流程文件本身是不能直接部署的,因为线上执行需要配合环境参数、依赖声明、前置脚本。所以我们定义了一个标准的自动化流程制品包结构:
temu_order_collect_1.3.2/
├── flow/
│ └── main.flow
├── config/
│ ├── metadata.json
│ └── runtime_params_template.json
├── scripts/
│ ├── pre_check.py
│ └── post_cleanup.py
└── resources/
└── selector_config.yaml
runtime_params_template.json 是运行时参数模板,定义了该流程需要哪些外部参数(店铺ID、时间范围等),执行节点在启动流程前会用实际值渲染这个模板。
制品包在CI里打包成zip,上传到内部的制品仓库,调度中心在分配任务时根据流程名和版本号拉取对应的制品包,解压到执行机的临时目录,然后调用影刀命令行执行其中的main.flow。
这样做有一个非常大的好处:流程的运行环境与流程本身解耦了。同样的流程制品,可以在任意执行机上跑,只要执行机的基础环境满足依赖声明。
三、灰度发布的完整流水线
我们给每一个流程定义了四个发布阶段,必须逐阶段通过验证才能走向下一个。
阶段一:单店铺验证
新流程上线,先在 canary_shop(一台专用于验证的测试店铺,不涉及真实业务)上跑一轮。验证内容不只是流程能否跑完,还包括:
- 所有关键元素是否定位成功
- 执行时间是否在合理范围
- 有无异常日志
- 操作结果是否正确(与手工操作结果比对)
这一步由CI自动触发,跑完后生成验证报告,通过则自动进入阶段二。
阶段二:小批量灰度
选3~5个真实店铺,将它们任务路由到的流程版本切换为新版本。调度中心的路由器根据店铺ID查找灰度配置,决定使用哪个流程版本。
灰度期间,我们重点监控这些店铺的任务成功率、平均耗时、业务结果数据是否出现异常波动。如果有任何一项指标偏离基线超过20%,灰度自动暂停,触发回滚检查。
def should_use_canary_version(shop_id: str, flow_name: str) -> bool:
canary_config = redis.hgetall(f"canary:{flow_name}")
if not canary_config:
return False
canary_shop_ids = canary_config.get("shop_ids", "").split(",")
return shop_id in canary_shop_ids
这段逻辑看起来简单,但它卡在调度中心每次分配任务时都会执行,是灰度的核心开关。canary配置存储在Redis里,可以动态调整灰度范围而不需要重启任何服务。
阶段三:扩大灰度
小批量跑满一个业务周期(通常是24小时,覆盖一天的完整运营节奏)且无异常后,灰度比例扩大到30%50%的店铺。这个阶段会持续23个业务周期,确保不同时间段、不同数据量下都稳定。
阶段四:全量发布
全量后保留旧版本制品一周,随时可回滚。全量发布的同时,旧版本在制品仓库中标记为 deprecated,不再接受新任务但已有任务不受影响。
四、回滚机制设计:不只是换个文件
回滚这个词说起来简单,做起来全是细节。
我们在实践中最惨痛的教训是:只回滚了流程文件,没回滚数据结构的变化,导致更严重的脏数据。
例如有一个流程v1.4在采集订单时,为了优化性能,把采集结果写到了一个新的Redis Key结构里,旧版本流程读的是旧结构。灰度回滚到v1.3后,v1.3试图从新结构里读数据,解析失败,全部报错。
后来我们的回滚操作被设计成了一个事务性的流程,包含以下步骤:
- 冻结新版本的任务队列:调度中心停止向该流程类型的任务推送新版本,所有新任务走旧版本。
- 等待进行中的新版本任务完成或超时:不做强制kill,避免留下半成品数据。
- 数据兼容性检查:如果新版本修改了数据落盘结构,检查旧版本是否能正常读取当前数据。如果不能,需要执行一次数据回迁脚本(这部分通常在发布前就预备好)。
- 切换路由配置:将灰度配置里的版本号指向旧版本,清理Redis里的新版本缓存。
- 验证旧版本可用性:在canary店铺跑一轮旧版本,确保切换成功。
- 解除冻结:恢复任务下发。
这些步骤在一个Python脚本里编排,依赖人工触发但执行过程全自动,每一步都有日志和状态更新,出错了也知道卡在哪一步。
回滚不是紧急情况下的手忙脚乱。
它应该是一次演练过无数遍的标准化操作。
五、多版本共存的能力边界
在灰度期间,同一个流程的多个版本是同时运行的。这就要求我们在设计流程时遵守一条铁律:流程必须保证向后兼容性,至少在相邻版本之间。
具体约束包括:
- 流程的输入参数模板只能新增字段,不能删除或修改已有字段的类型
- 输出的数据格式(写入数据库的表结构、Redis Key模式)如果变更,新版本必须同时兼容旧格式的读取,旧版本回滚时也要能容忍新格式的存在
- 店铺环境的
user-data-dir不能被新版本做出不可逆的修改(比如安装了旧版本不支持的浏览器扩展)
我们曾经打破过最后一条规则,在一个新版本流程里给浏览器环境装了一个辅助插件。灰度回滚后,旧版本启动时发现浏览器里多了个不认识的东西,直接导致登录态校验逻辑错乱,好几个店铺被迫重新登录。那一次之后,我们任何涉及浏览器环境变更的发布,都要求额外评估回滚的环境修复成本。
六、CI/CD的落地形态
我们用GitLab CI来编排整个流程的持续集成和部署。不是花哨的流水线,只做几件实在事:
stages:
- validate
- build
- canary_test
- deploy_gray
- deploy_full
validate:
stage: validate
script:
- python ci/validate_metadata.py
- python ci/check_flow_naming.py
build:
stage: build
script:
- python ci/create_artifact.py --version $CI_COMMIT_TAG
artifacts:
paths:
- artifact.zip
canary_test:
stage: canary_test
script:
- python ci/push_to_canary.py --artifact artifact.zip --shop canary_shop_01
- python ci/wait_and_verify.py --timeout 600
deploy_gray:
stage: deploy_gray
when: manual
script:
- python ci/set_canary_config.py --percent 10
deploy_full:
stage: deploy_full
when: manual
script:
- python ci/set_canary_config.py --percent 100
每一个阶段都有明确的门禁。validate阶段检查元数据完整性和命名规范,canary_test在测试店铺跑真实的自动化流程并等待验证结果。灰度发布和全量发布都是手动触发,因为这个决策目前我们还不敢完全交给机器。
七、那些在发布流程上栽过的跟头
跟头一:流程文件体积膨胀导致制品拉取超时
一些流程里嵌入了大量的截图和资源文件,单个制品包膨胀到200MB以上,执行节点每次从制品仓库下载都要一分多钟,任务整体耗时被严重拉长。后来我们强制流程设计规范:所有静态资源外置到独立的资源服务,流程里只保留引用链接,制品包控制在10MB以内。
跟头二:灰度比例配置错误引发全量切换
有一次运维在设置灰度比例时,误把小数填成了整数—— percent: 10 写成了 percent: 100——瞬间全部店铺都切到了未充分验证的新版本,半个小时不到出了问题,影响了一百多个店铺。从那以后,灰度配置的修改必须经过双人复核,并且在调度中心加了一个硬限制:灰度比例单次上调幅度不得超过30%,超过则需要分步操作。
跟头三:旧版本制品被误删,回滚时发现zip不存在
制品仓库配置了定期清理策略,本意是清理一年前的旧版本,结果因为时间戳配置错误,把三个月前的历史版本全清了。好在Git仓库里还保留着源文件,紧急重新打包才完成回滚。现在我们的制品仓库对标记为 active 和 standby 的版本永久保留,只清理明确标记 archived 的版本。
八、发布与业务的协同节奏
店群自动化有一个特点:运营有固定的业务节奏。比如TEMU的促销活动周期、TikTok Shop的直播高峰时段。在这些关键时间窗口内,我们是严禁做流程发布的。
我们在调度中心加了一个发布日历模块,标注了每个平台的关键业务周期。提交灰度或全量发布时,系统会自动检查当前是否处于禁发期,如果在禁发期,直接阻止发布,只能排队等到窗口结束。
这个功能拯救了我们很多次。有次一个同事周五下午想发一个新版本的批量上货流程,被发布日历拦住了——因为那个周末正好是大促,任何异常都可能导致巨大损失。他当时很不理解,觉得小改动没问题。但周一数据出来时,我们发现同行的确有店铺因为在促销期间自动化出错而被平台处罚。那之后,发布日历成了团队默认遵守的规则。
九、这套体系还缺什么
坦率说,我们的流程版本管理还有很多粗糙的地方。最大的痛点是流程内部逻辑的diff能力缺失——当流程出问题时,我们只能靠发布说明和提交记录来猜哪里改了,没有办法像代码一样精确对比两个版本的节点变化。
目前我们正在尝试把影刀流程的关键逻辑(元素选择器、条件分支、循环结构)导出为一种中间描述语言,然后用这个中间语言做diff和影响分析。不过这个方向还处于探索阶段,尚未正式上线。
另外一个缺口是自动化回滚后的数据修复。现在对数据结构不兼容的情况,我们依赖手动执行数据回迁脚本,希望能演进到自动检测并触发修复的机制。
十、写在最后
很多人觉得RPA流程就是拖拖拽拽,改改就能用,不需要搞版本管理、发布流水线这么“重”的东西。但我们的血泪教训一再证明:当自动化流程承载的是真实业务,哪怕它只是一个脚本,也必须用交付软件的严谨来对待它。
流程不是写出来就完事了。它是活的,会迭代,会出Bug,会有多个版本同时跑在线上。只有把从开发到发布的整条链路工程化,才能让每次改动都心中有底,出了问题退得回去。
敬畏发布,就是敬畏业务。
这条原则,放在任何自动化体系里都不为过。
作者:林焱
一个每次点击“发布”按钮前都要再三确认的工程师
