2.4 租户开通:自动建库与初始化脚本
场景:新签一个客户,开发要忙活一整天
转型之前,新客户签约后走的是一套人肉流程:销售把需求扔进群,开发建库、建目录、改配置、建管理员账号、对着清单逐项勾选,熟练工也要小半天。这套流程在客户少时是苦力活,客户多时就成了交付瓶颈——销售月底冲了六个单子,开发那一周的产能就全被开通吃掉,新功能进度直接停摆。更要命的是人肉操作没有复验,建错权限、漏建目录的事故月月有,还要专门派人去擦屁股。
还有一种隐蔽的伤害:新客户的等待感。销售当面拍胸脯说开通很快,回头开发排了两天才动手,客户第一印象就打了折扣。开通速度是 SaaS 的门面,签约后五分钟和两天,在客户心里是完全不同的两种产品。
自动化开通是 SaaS 模式成立的证明:如果加一个新客户的边际成本不能趋近于零,那这门生意就还是定制生意。我们的目标是销售在后台点完成,剩下的全交给脚本:五分钟内新户能用、出错可回滚、进度全程可见。这个目标不是一天达成的,中间经历了三个版本的迭代,每个版本都在解决前一版留下的事故,本节会把演进过程一起讲出来,因为比最终方案更有参考价值的是踩坑的顺序。
开通脚本还有一个被低估的作用:它是租户生命周期的起点,所有后续运维动作(升级、扩容、停用、退租)都复用这套流水线思想。把开通做好,等于给整个治理体系打了样。
方案:开通流水线的六个步骤
开通不是一条命令,而是一条流水线。每一步都做成幂等且可重试,任何一步失败都能从断点继续,而不是从头重来。这套设计还方便了交接:新人接手开通模块时,对着六步表格就能理解全貌,比读人肉流程文档快十倍。另外每一步的输入输出都有清晰的契约,任何一步未来想换实现(比如从建独立库换成按需分配共享库)都只影响那个类,流水线的其余部分完全无感。
| 步骤 | 动作 | 幂等手段 |
|---|---|---|
| 一 | 校验清单参数合法 | 失败立即拒绝,不动任何资源 |
| 二 | 建库或注册共享库户 | 先查再建,已存在则跳过 |
| 三 | 建对象存储前缀与权限 | 先查再建 |
| 四 | 写配置中心与套餐记录 | 更新或插入 |
| 五 | 建管理员账号与默认角色 | 已存在则刷新密码 |
| 六 | 冒烟验证与通知 | 跑不通则标记异常并告警 |
幂等是这条流水线的灵魂。独立库户失败重试时,第二步发现库已经存在,直接跳过往下跑;共享库户则是先检查记录是否存在。原则就是同一条命令跑十遍与跑一遍结果相同,客户那边看到的是进度条,我们这边看到的是可以放心交给脚本的黑盒。
我们在设计每一步时都问同一个问题:如果这一步执行到一半断电了会怎样。答案必须是多种坏情况中最不坏的那一种,并且重试能收敛。这个思维训练在第二步(建库)上尤其有用:云接口是异步的,调用返回成功不代表库可用,所以步骤里必须包含轮询等待,直到实例真正可连接才继续。
演进:三代流水线的迭代史
开通流水线不是一次设计到位的。回顾三代演进,每一代解决的问题都实打实来自事故现场:
| 版本 | 特征 | 救了什么事故 |
|---|---|---|
| 一代 | 脚本顺序执行,失败即停 | 代替人肉,省下大半天 |
| 二代 | 加断点续跑与失败回滚 | 半成功残留与脏数据 |
| 三代 | 加冒烟验证与进度通知 | 客户登进白屏的体验事故 |
如果今天从头做,我们会直接上三代版本,但中间这些弯路是教科书上没有的。特别提醒:别被工具链的 fancy 程度迷惑,开通脚本最值钱的部分是那些朴素的校验与回滚,而不是编排引擎本身。
度量:开通流水线的指标看板
流水线上线后需要持续看数据才知道它是否健康。我们在后台做了三块小看板:
| 指标 | 采集点 | 告警阈值 |
|---|---|---|
| 开通成功率 | 流水线完成状态 | 低于九成五 |
| 平均开通耗时 | 从触发到冒烟通过 | 超过十分钟 |
| 回滚触发次数 | 回滚步骤执行记录 | 单周超过两次 |
看板跑了两个月发现一个反直觉的事故源:失败集中在每天下午三点左右,查下来是公司内部其他系统在做定时备份抢资源,导致云接口响应慢。把批量开通挪到避开这个窗口后,成功率回到九成九以上。度量最美妙的地方就在于它总能把你想不到的问题直接摆到你面前。
配置:流水线编排脚本
编排脚本按步骤依次执行,失败抛出具体步骤号,支持 resume 参数从断点续跑。下面是它的骨架,每一步都是独立的类,便于单独测试与替换:
class ProvisionTenant
{
public function run(string $tenantId, array $blueprint): void
{
$steps = [
Step\Validate::class,
Step\CreateDatabase::class,
Step\CreateStoragePrefix::class,
Step\WriteConfig::class,
Step\SeedAdmin::class,
Step\VerifySmoke::class,
];
foreach ($steps as $i => $step) {
with(new $step)->run($tenantId, $blueprint);
// 每步完成写一条流水记录,断点续跑就读它
Progress::mark($tenantId, $i);
}
Notify::send($tenantId, '开通完成,请查收管理员邮件');
}
}
冒烟验证那一步尤其关键:脚本会用管理员账号跑通登录、发文章、上传文件几个核心动作,全过才标记开通成功。有次云数据库接口返回稍慢,创建库步骤其实是半成功状态,脚本没验证就直接通知客户,客户登进去白屏。加了冒烟验证后这类半成功状态会被立刻识别,脚本自动进入重试,客户体验再没掉过链子。冒烟动作要选最核心的链路而不是随便测个小功能,我们选的是登录、发文、上传三条,覆盖了数据库、缓存、对象存储三件套,一次验证三层。
通知环节也有讲究:开通完成会同时给客户管理员和我们的运营群发消息,邮件里附首次登录指引。开通记录全部留档,谁在什么时候执行了哪一步,事后查问题一清二楚。有客户两年后问当初开通时约定的配置,翻流水记录五分钟出结果,客户对这种细节是很买账的。
踩坑:开通自动化的四个坑
第一个坑是失败后残留。早期脚本失败时已建的库与前缀就残留在那里,像没人认领的垃圾,每月清理一次还得人工判断。后来加了反向回滚:失败时自动执行与流水线相反的清理步骤,把资源回收干净。回滚本身也要幂等,我们用资源标记(tag)来区分哪些是本次开通创建的,避免误删别人家的东西。回滚失败则是更高一级的告警,直接 page 值班同事,绝不留隔夜垃圾。
第二个坑是云接口的限流。批量开通遇到云 API 频率限制,直接报错退出。脚本里统一加了退避重试,并且把重试次数与耗时记录到进度里,从面上看就是要稳。另外我们把开通任务做了简单的队列化,并发开通三个是上限,超过排队等待,避免销售月底冲单时六七个流水线一起跑压垮云接口。
第三个坑是测试租户污染生产。演示和试用创建的租户混在租户表里,客服打电话过去差点把演示户当成正式客户。后来给租户加了状态字段(草稿、试用、正式、停用、已删除),后台列表默认只看正式户,试用户自动三十天过期,从此列表干净、计费也只算正式户。开通流程的云资源操作部分,和 腾讯云实践 的资源编排思路可以相互印证;想给客户做开放接口对接开通的,参考 PHP API 实战 的设计。
第四个坑是开通安全。流水线权力太大,建库、建前缀、建账号都是高危操作,最初的触发入口只靠一个登录态保护。我们随后给开通动作加了二次审批与操作者留痕,并对批量开通做了人数上限与人工确认。越自动化的系统,越要把开关攥紧,否则一次误操作就是十个租户的灾难,这个觉悟希望读者从我们的冒冷汗里直接继承,不必再亲身经历一次。
小结
开通自动化把新客户边际成本压到了电费级别:流水线六步、每步幂等可重试、失败自动回滚、完成前冒烟验证、全程留档。它省下的不只是开发时间,更把交付能力还给了销售——半夜签单也能立刻开通,第二天的周会上销售看你的眼神都不一样。
再给一组数字感受一下收益:开通自动化之前,每月新增八个客户要吃掉两个开发约三天;自动化之后,同样八个客户只占用值班同事十五分钟的巡阅。同一个团队,交付能力差了一个数量级,这就是工程化的复利。再补一个开通之外的收益:开通流水线跑顺后,同样的幂等思路被复用到了环境复制——销售要给大客户做演示沙箱,原来要手工搭半天,后来一键克隆开通流程即可,数据来自脱敏后的生产快照。一套自动化衍生出第二套自动化,这也是工程化复利的另一个侧面。
第二篇到此结束,租户模型四根柱子立稳之后,下一篇进入功能共用:一套代码到底怎么服务所有租户,从权限、模板、文件到缓存。
