报销系统换新版界面那次,我们一开始想省事,全公司同时切。上线两小时,财务那边炸了:新版把"费用类型"改成了三级联动,老数据里两百多条只有一级的类别选不出来,提交按钮点不动。当天下午回滚,加班改了三天。
第二次上线改成分批:先财务自己用一周,再给行政和采购,然后研发和销售,末了一批才是全公司。整个过程二十六天,中间拦下四个问题,没有一个影响到全员。
分批的价值不在于"稳一点"这种笼统的说法,而在于把问题的暴露面控制在一个部门内。这篇讲的就是批次怎么排、配置怎么写、每一批看什么。
上线前先把要验证的问题列出来:新流程在一个真实部门里能不能跑通、数据迁移有没有漏、性能在并发下扛不扛得住、用户会不会用。四个问题对应的观察对象不一样,排批次的时候要照顾到。
如果只是"想稳一点",批次顺序就会变成拍脑袋:谁好说话就先给谁。结果往往是给了一个业务简单的部门,什么问题都没验出来,等到全员上线才暴露。
我按三条筛:业务覆盖全、有人愿意反馈、出问题能兜住。
业务覆盖全,是指这个部门的单据类型多、流程分支多,能覆盖到边角场景。财务符合这条,因为它自己就是流程的终点,各类单据都过它手。
有人愿意反馈,是指部门里有一个人愿意每天花十分钟说哪里别扭。这个角色比任何监控都管用,因为很多问题是"用起来烦"而不是"报错"。
出问题能兜住,是指这个部门就算停一天,业务也不会断。仓库和生产线不符合这条,所以放在靠后的批次。
我们的顺序是:项目组自己、财务、行政与采购、研发、销售、其余部门。这条线是按"离系统近"到"离系统远"排的。
离得近的人知道这是新东西,容忍度高,反馈也具体;离得远的人只关心能不能报账,出问题直接投诉。把难听的话留在后面听,前面先把明显的坑填掉。
一份可用的放量配置,至少要能表达:当前放到第几批、每批包含哪些人、单批内部是否再按比例细分、出问题时怎么一键停。
我们的配置放在配置中心,改完即时生效,不用发版。应用启动时拉一次,之后监听变更。这样凌晨发现问题,值班的人改一个字段就能停,不用等构建。
{
"feature": "expense_form_v2",
"enabled": true,
"kill_switch": false,
"bucket_key": "user_id",
"stages": [
{
"name": "s1_self",
"percent": 100,
"rules": { "dept": ["fin"], "role": ["admin", "fin_mgr"] },
"observe_hours": 72
},
{
"name": "s2_admin",
"percent": 100,
"rules": { "dept": ["adm", "buy"] },
"observe_hours": 96
},
{
"name": "s3_rd",
"percent": 50,
"rules": { "dept": ["rd"], "exclude_users": [10231, 10288] },
"observe_hours": 48
},
{
"name": "s4_all",
"percent": 100,
"rules": { "dept": ["*"] },
"observe_hours": 0
}
],
"current_stage": "s2_admin",
"fallback": { "on_error_rate": 0.02, "window_minutes": 10 }
}这份配置有四个设计点。一是 current_stage 是唯一开关,切批只改这一个值,不用动数组,减少改错的可能。二是 percent 和 rules 是并且关系,部门命中之后还要过比例,方便在一个部门内部再切一半人试。三是 exclude_users 用来排除特定账号,比如财务里负责对账的那位,她需要一直用旧版对照。四是 fallback 定义了自动回退的阈值,错误率超过百分之二持续十分钟,配置服务自动把 kill_switch 置为真。
按比例放量常见的坑是分桶不稳:同一个用户刷新一次页面,命中了新版;再刷一次,回到旧版。表单填一半被切走,数据就废了。
解决办法是拿用户标识做哈希再取模,同一个用户永远落在同一个桶里。哈希函数用同一种,不要一边用 Java 的 hashCode 一边用前端的不同实现,两边算出来不一样,就会出现"接口说新版、页面给旧版"的错位。
判断"这个人走新版还是旧版"的逻辑只能有一份实现。我们早期在网关和业务服务里各写了一遍,规则一改只改了一处,两边判断不一致,出现了权限校验走新版、数据读取走旧版的情况,查了半天。
现在的做法是网关统一判断,把结果写进请求头,后端只读这个头,不再自己算。
每批的观察时间不一样。内部批次给三天,因为人少、可以随时找人对;给业务部门的头一批给四天,要跨一个完整的周循环,才能看出周一集中提单时的表现。
观察期内不切下一批,这条要写进流程。否则一忙就往前推,等于没观察。
底下是技术指标:接口错误率、响应时间的九五分位、数据库慢查询条数。中间是业务指标:提交成功率、退回率、平均审批时长。上面是主观反馈:每天一次十分钟的电话,问三个问题,哪里慢、哪里看不懂、哪里比以前麻烦。
三层里我更看重退回率。技术上一切正常,但退回率翻倍,说明表单填的东西变了,用户不知道该怎么填。这种问题监控发现不了。
每批的指标看板,我们直接在搭贝里搭了一页,切批和看数在同一个界面,值班的人不用来回切系统。
灰度期间新旧两套同时在线,数据必须双向兼容。具体是三条:新表加字段一律给默认值,旧代码读不到也不报错;新逻辑写的数据,旧逻辑要能读,不能出现旧版打开单据是空白;状态机新增的状态,旧版要能显示成一个合法状态而不是乱码。
这三条在开发阶段就要测,不能等灰度时才发现。我们的做法是准备一套旧版本回归用例,每次改完跑一遍。
回滚不只是改配置把流量切回去。新版已经写入的数据怎么办,这是要在灰度前就想清楚的问题。
我们的原则是:灰度期间新逻辑只写新字段,不改老字段的含义。这样切回旧版,旧版读老字段,数据完整,不需要做数据修复。等全量稳定一个月之后,再跑一次任务把新字段的值合并回老字段。
停的权限要给到值班的人,不要卡在项目负责人身上。凌晨两点出问题,找负责人批一次要半小时,这半小时里的投诉比问题本身更麻烦。
权限的边界是:任何人都能停,但停完要在一个固定的群里说明原因。事后复盘看的是判断对不对,不是有没有权力停。
全量之后还有三件事:清理开关代码、删掉旧版入口、把灰度过程写成一页记录。
清理开关这件事经常被拖。开关留着,代码里到处是分支,半年后没人敢删。我的做法是给每个开关记一个到期日,到期自动提一条任务,不处理就升级到负责人。
过程记录写四行就够:每批的时间、发现的问题、怎么处理的、下一批改了什么。这份记录后来成了新同事了解系统演进的入口,比看代码快。
从我们的几次实践看,一个中等复杂度的表单改版,四到五批、三到五周是合适的。批次太少等于没分,批次太多管理成本上来,每批都要发通知、都要盯指标。
每批的人数也有讲究。头一批十几个人,第二批几十个,第三批上百个,末一批全量。前面批次人少,问题好定位;后面批次人多,才看得出性能。
还有一条容易被忽略:切批要选在业务低峰。我们的切批时间是周四下午,理由是离周末还有一天,出问题有时间处理;周一不切,因为周一本来是提单高峰,两件事撞一起分不清是谁的问题。
问:按部门放量,跨部门的审批流怎么办?
答:按发起人判断走哪套,不按审批人。这样一笔单据从提交到审批走的是同一套逻辑,不会出现申请单是新版、审批页是旧版的情况。审批人初次收到新版单据时,页面顶部给一条说明,告诉他这是新格式。
问:灰度期间用户反馈"我的界面和别人不一样",怎么解释?
答:提前发一封说明,讲清楚分批上线的时间表,让每个人知道自己大概什么时候切。另外在新版页面留一个"切回旧版"的入口,试用期内允许自行切回,降低抵触。切回的比例本身也是个观测指标。
问:配置中心的配置改错了,会影响多少人?
答:取决于配置的作用范围。把 current_stage 改错,影响的是整批人,所以这个字段要加二次确认和变更记录。把 percent 改错影响小一些。建议把危险字段单独做成一个受保护的配置项,只有少数账号能改。
问:灰度结束后,旧的代码分支什么时候删?
答:稳定运行一个完整的结算周期之后。财务类系统建议至少一个季度,因为季度末的批量操作和日常不一样。删之前先在测试环境把开关关掉跑一轮回归,确认没有隐藏依赖再动主干。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。