2023年Q3,某SaaS平台的React迁移项目卡在最后一米。技术负责人盯着监控大屏,旧系统AngularJS(一款前端框架)的代码债已经堆到47万行,新React(一款前端框架)应用通过了所有测试,但没人敢点那个100%切流的按钮。
「大 bang 发布(一次性全量上线)的噩梦我见过太多次了。」技术负责人在内部复盘文档里写道。他的方案是:让新旧两套门户在同一域名下共存,用金丝雀发布(灰度放量)把风险切成10份。1000张工单变100张,这个数字说服了CTO。
两套系统怎么塞进同一个域名
旧门户是Spring MVC(一款Java Web框架)+ FreeMarker(一款模板引擎)的老架构,根路径直接渲染portal.ftl。新React应用被打包成静态资源,塞进nginx容器,挂在/new-ui/子路径下。
用户访问https://portal.example.com/时,流量走向DashboardController → portal.ftl → AngularJS。访问/new-ui/则直达nginx → React单页应用。两个入口,同一域名,session共享——这是第一道关卡。
认证状态靠cookie打通。旧门户在域名级别种下userToken和userHash,新门户启动时直接读取,注入Redux(一款状态管理库)的auth状态。代码很直白:拿到两个cookie,判断是否登录,完事。
真正的坑在灰度策略本身。
Traefik(一款云原生网关)的权重路由可以按百分比分流,但问题是:用户可能在会话中途被「甩」到另一套系统。他在旧门户点了三个菜单,下一秒权重翻转,被丢进新UI的某个完全不对应的路由——直接404。
解决方案是把登录页做成唯一决策点。登录页走服务端渲染,一旦用户从新UI的登录页进来,整个会话周期锁死在新系统。Traefik的IngressRoute只针对/user/login路径做权重拆分,其他路径跟着会话走。
URL里的/new-ui/怎么「消失」
内部路径暴露给用户是产品红线,SEO也会遭殃。Traefik中间件干了件脏活:stripPrefix(剥离前缀)。浏览器地址栏永远干净,内部路由照常走/new-ui/,用户感知为零。
这种「表里不一」的架构在网关层消化了所有复杂度。应用代码不用关心自己在哪个路径下运行,React路由器的basename配好就行。技术负责人提到一个细节:v7_skipActionErrorRevalidation这个参数被显式打开,是为了跳过React Router v7的某个行为变更——具体什么行为他没展开,但注释写得很清楚:「避免重复验证导致的意外重载」。
静态资源部署也有讲究。新前端构建产物直接打进nginx镜像,没有CDN(内容分发网络)中间层。理由很简单:这个阶段的流量还在灰度,走容器本地文件最快,出了问题回滚镜像就行。
10%到100%的47天
放量节奏比技术实现更考验耐心。10%跑了一周,监控没报警,提到25%;又两周,50%;最后100%切流时,支持工单量确实控制在了两位数。
技术负责人在文末补了一句:「没有用户反馈说『你们换系统了』——这就是最好的反馈。」
整个方案的核心假设是:用户不关心技术栈,只关心功能连续性。登录页作为流量闸口,把「技术迁移」翻译成「无感升级」。Traefik的权重路由在中间件层消化了路径重写,应用层保持愚蠢。
这种分层架构的代价是运维复杂度上移。网关配置、中间件链、会话粘滞策略——这些原本分散在应用里的逻辑,现在集中在基础设施层。团队需要有人能读懂Traefik的CRD(自定义资源定义),能在Kubernetes(一款容器编排平台)里调试IngressRoute的匹配规则。
但相比大 bang 发布的回滚噩梦,这个代价被接受了。
项目收尾时,旧系统的portal.ftl还在仓库里,只是再也没有流量经过DashboardController。技术负责人没删它,「留个墓碑,提醒自己下次重构别拖这么久」。
最后一个数据:从第一行React代码到100%切流,用了11个月。其中技术实现3个月,灰度放量占了4个月,剩下的时间在等业务方确认「真的没有遗漏功能」。
如果你的团队也在啃遗留系统的迁移,你会选择这种「双轨并行」的慢功夫,还是押注一次性切流的效率?
热门跟贴