代码迁移之旅(二)- 渐进式迁移方案
说在前面
这是代码迁移的第二篇文章,也是最后一篇了,由于个人原因,原来的迁移我无法继续参与了,但完整的方案我已经准备好了,在测试环境也已经可以正常进行了。 上篇文章 代码重构之旅(一) 项目结构 介绍了迁移代码的前期准备和项目结构的设计,本篇文章来介绍一下可实施的迁移方案。
使代码的迁移过程更简单、更安全是我们要追求的目标,在迁移之前,代码的可用性我们一定也只能画一个问号。
文章欢迎转载,但请注明来源:http://www.cnblogs.com/zhenbianshu/p/8110912.html, 谢谢。
问题抽象分析
首先要看一下一次完整的迁移需要满足什么要求:
- 灰度发布,谁也无法保证一次将整个系统迁移到另一个系统不会发生问题,而以接口或接口部分流量为单位进行迁移则可以大大提升可控性。
- 客户端无感知,即迁移平滑,长时间的系统不可用是完全无法接受的。
- 可回滚,一旦出现异常问题可以快速回滚,避免造成较大影响。
- 易实现,尽量避免大量地操作,操作多意味着犯错的可能性更大,回滚的难度也大。
只有实现了以上要求,才算是一次成功的迁移。那么先分析一下目前的情况:
如上图是我们两个系统的目前状态:
- 两个系统共享一个 Nginx 服务器,而且在 Nginx 中,由于新老系统的
Host::Ip
也不需要变动,所以新老系统还共享一个同一个 Server。 - 新旧两个模块分别对应着两个版本控制目录,旧模块将 Http 请求进行 url 重写后直接分发到各 PHP 脚本,例如:
rewrite ^/api/common/test.json?(.*)$ /api_test.php?$1;
- 新模块将 Http 请求直接分发到 index.php 后,由 index.php 进行内部路由转发。
两个模块初始状态相安无事,现在的问题是如何将旧模块的接口逐渐过渡到新模块中。由于旧模块的分发入口在 Nginx 中,最简单的办法自然是修改其原来的重定向规则。
Nginx重定向
先看一个典型的 Nginx Http 服务器配置:
http {upstream stream_name{}server {listen port;server_name domain_name host_name;rewrite ori destA;location pathA {rewrite ori destB type;}location pathB {if(match){rewrite ori destC type;}rewrite ori destD type; }}
}
我们要使用的就是 Nginx 强大的路由重定向功能。
location
location 是一个 URI 捕获语句,它被定义在 server 模块内,会对 server 内的所有请求进行 uri 匹配,一旦匹配,则进入 location 模块内部执行。
location 常见的使用形式是:
location path_pattern {operation;
}
它的 path_pattern
有以下几种形式,优先级从高到低为:
- 完全相等匹配
location = uri {}
- 前缀匹配
location prefix {}
或location ^prefix ~ {}
- 正则匹配
location ~ regex {}
或不区分大小写正则匹配location *~ regex {}
- 通用匹配
location / {}
不同的 pattern 类型匹配顺序与定义顺序无关,而是由优先级从高到低进行匹配,同一类型的,优先使用 pattern 串更长的进行匹配,因为长串会更精确。
它的 operation 一般是 rewrite 或 proxy_pass 语句,对捕获到的请求进行重写或转发。用于转发的 proxy_pass 语句很简单, proxy_pass proxy_name;
即可,下面具体说一下路由重写功能。
if
if 语句可以对 uri 进行更加灵活的判断和操作,它的常见使用形式是:
if (match) {rewrite ori destA type;
}rewrite ori destB type;
在 match
语句中,可以使用如 $request_uri
等全局变量,常见的还有 $query_string,$uri,$remote_addr
等。
但是需要注意使用 if 语句是十分低效的行为,它就像普通的代码一样,每个 Http 请求碰到 if 语句都会进行一次 match
计算并判断,虽然写在 location 内部会好一些,但最好还是极力避免此语句。
rewrite
rewrite 是对匹配到的请求进行 uri 重写,它可以被写在 server/location/if
模块中,使用方式 是 rewrite ori dest type;
。在 server 模块中,rewrite 和 location 的执行顺序为:server中的rewrite -> location -> location中的rewrite
我们可以使用正则或全相等来匹配 ori
,并将正则结果应用于 dest
上,如 rewrite ^/api/common/test.json?(.*)$ /api_test.php?$1;
则将 ori 内部的 query_string 匹配出来并使用 $1
赋值给 dest。
rewrite 默认将 uri 重写后并不直接将请求分发到 CGI,而是将结果 uri 作为一个新的请求再次进行 server 模块内处理,如果循环重入超 10 次 nginx 会直接返回 500 internal server error
,而控制 rewrite 匹配后的行为 主要依靠其 type 参数:
- last 结束此模块(server/location) 匹配,并重入 server 模块处理,rewrite 默认使用此项;
- break 结束所有模块匹配,直接将请求分发到 CGI;
- redirect 直接分发请求,返回 Http 状态码 302 临时重定向;
- permanment 直接分发请求,返回 Http 状态码 301 永久重定向;
应用
介绍完了 Nginx 的重定向功能,还需要考虑怎么使用此功能进行代码的过渡。
- 使用 location 捕获对应接口;
- 使用 if 进行部分流量分发(可选);
- 将请求 rewrite 到新模块。
如:
location ~ /api/test.json { # 匹配到 test 接口if ($remote_addr ~* 1$) { # 分流 IP 末位为 1 的请求root new_dir/public; # 设置新项目的目录为根目录rewrite ^(.*)$ /index.php$1 break; # 将请求分发到新项目的 index.php 入口文件}rewrite ^/api/test.json?(.*)$ /api_test.php?$1; # IP 末位不为 1 的请求继续访问旧项目
}
Linux链接
如上,我们发现如果针对每个接口进行一次 location 重定向,都需要写 7 行代码,即使不用 if 语句(多数情况如此),每次也需要 4 行代码。
location ~ /api/test.json { # 匹配到 test 接口root new_dir/public; # 设置新项目的目录为根目录rewrite ^(.*)$ /index.php$1 break; # 将请求分发到新项目的 index.php 入口文件
}
如此下来,项目如果有 100 个接口,那么维护这100个 location 模块也颇为废劲。其实更多时刻,我们并不需要使用 location 语句,直接在 server 模块内部使用 rewrite 即可,而阻止我们直接使用 rewrite 的,就是由于新旧模块不在同一文件夹下,我们必须使用 root 语句将根目录定义到新项目下。至于为什么不将新旧项目的父文件夹定义为 root,是因为旧项目中有一些路径可能会有深坑。
这里我们可以使用 linux 的 软链接
来 把新项目“放置”在旧项目下:linux 中软链接的功能就像 windows 中的快捷方式
一样,是一个指向文件或真实目录的符号。至于其实现,就要说到 linux 文件结构中的重要概念 inode
了,不过这里不再多提。
使用 ln -s /path/to/dir_new /path/to/dir_old/yaf
在旧项目目录下创建一个 yaf
软链接指向新项目目录;
这样,就可以以旧项目目录为根目录,找到新项目目录下的文件了,使用单行命令 rewrite ^(/api/test.json(.*)$) /yaf/public/index.php$1 break;
即可。
框架内URL重写
通过上面 Nginx 的重定向,所有的请求都会被分发到 index.php 中, 接下来就需要在 yaf 内对 index.php 接收到的 Http 请求进行内部分发。
yaf 提供了 Yaf_Route_Static、Yaf_Route_Simple、Yaf_Route_Supervar、Yaf_Route_Map、Yaf_Route_Rewrite、Yaf_Route_Regex
六种路由方式,各有其适合的场景,需要在 /conf/application.ini
中配置 application.dispatcher.defaultRoute.type="type"
。
我们的内部接口名完全不规则,有改写为 .json
后缀的,也有保持 .php
的,有带下划线的,也有大小写敏感的,找不到什么规律,于是使用了 map
类型,直接匹配 uri
然后映射向 controller 类。
我们将 uri 和controller的映射统一保存在一个文件内,形如:
return array(// 接口作用'key' => array('type' => 'rewrite', 'match' => '/api/test.json', 'route' => array('controller' => 'Api_Test'),),...);
然后在 Bootstrap.php
内加载此配置文件:
public function _initRouter() {$router = \Yaf\Dispatcher::getInstance()->getRouter();$config = getConfig('rewrite_file_name');$router->addConfig($config);}
自此,关于迁移的配置就完成了。
测试
一次安全的迁移,完整的测试当然必不可少。在保证技术方案没问题的前提下,还要进行完整的业务逻辑测试。在 QA 测试之前,开发首先要通过尽可能完整的测试,将 BUG 率降到最低。
我们的系统对外提供服务都是通过接口,这也方便了我们进行测试。为了保证测试的完整性,可以将线上流量引入到新代码中进行测试,而实行请求导流的最好媒介就是日志。
一般来说,服务器都有完整的线上请求日志,如果有必要,在给特定接口添加特定日志以配合测试也是可以的。接入线上日志,构造跟线上一样的请求到测试服务器,再对比原始服务器的响应内容,将异常响应记录下来由开发分析并查找原因,直到最后新旧项目对所有请求的响应完全一致。
小结
项目的重构不是一个小事,特别是大规模的项目代码迁移,执行它必须胆大心细,但每一次重构,无论是对自己的技术能力还是项目的生命周期都是很大的提升。
虽然不鼓励没事就瞎折腾代码,但一定要时刻警惕,走出代码的舒适区,一定要提前预防根治代码疾病,不要在代码已经无可救药时才想到重构。
技术发展迅速,代码总有过时的一天,所以经常对代码有目的有计划的小幅优化是非常有意义的。
关于本文有什么问题可以在下面留言交流,如果您觉得本文对您有帮助,可以点击下面的 推荐
支持一下我,博客一直在更新,欢迎 关注
。
转载于:https://www.cnblogs.com/zhenbianshu/p/8110912.html
代码迁移之旅(二)- 渐进式迁移方案相关推荐
- Exchange2003-2010迁移系列之二,迁移前的准备工作(上)
Exchange2010迁移前的准备工作(上) 上篇博文发出后,很多博友支持得非常给力,在此一并谢过!也有一些博友反映看得不是很明白,但仍然支持-..本文中首先就环境问题再为大家解释一下,然后介绍如何 ...
- 【TiDB@丰巢】支付平台的迁移之旅
来源:丰巢技术团队 丰巢第一次在生产环境实际使用TiDB,是在2018年,其场景是每天产生一亿条以上数据的推送平台,当时我们还发了一篇文章,被PingCAP官方收录:TiDB at 丰巢:尝鲜分布式数 ...
- 机房迁移细则规范 机房搬迁实施规划方案 | 实用资料
机房迁移细则规范 & 机房搬迁实施规划方案 | 实用资料 点击蓝字关注→ twt企业IT社区 今天 机房迁移细则规范 机房整体迁移是弱电布线项目中的一个重要环节, 为保证机房设备能够安全.可靠 ...
- 迁移学习篇之如何迁移经典CNN网络-附迁移学习Alexnet,VGG,Googlenet,Resnet详细代码注释和方法-pytorch
鸽了好久的迁移学习篇学习终于打算更新,这次我们来学习一个机器学习中经典常用的简单快速提高网络指标的trick,迁移学习,迁移学习本身是机器学习中的一个trick,但是近些年在深度学习中应用广泛.之前我 ...
- 计算机视觉PyTorch迁移学习 - (二)
图像迁移学习 3.PyTorch实现迁移学习 3.1数据集预处理 3.2构建模型 3.3模型训练与验证 3.PyTorch实现迁移学习 文件目录 3.1数据集预处理 这里实现一个蚂蚁与蜜蜂的图像分类, ...
- 教育行业云迁移最佳实践:海云捷迅使用HyperMotion云迁移产品为北京某大学实施渐进式迁移,成功率100%
项目概述 北京某大学是教育部直属的全国重点大学,是国家首批"211工程"重点建设高校和国家"优势学科创新平台"建设项目试点高校,是世界一流学科建设高校. 学校现 ...
- 服务器虚拟化中的系统迁移二——P2V 迁移
本章介绍了使用易讯通Physical-to-Virtual(P2V)解决方案-Virt P2V把物理机迁移为虚拟机的内容. Virt P2V由virt-p2vserver(包括在virt-v2v软件包 ...
- CoreData整理(二)——多线程方案
CoreData整理(二)--多线程方案 目录 为何使用多线程 如何使用多线程 多线程方案 为何使用多线程 到了这里你一定会问,增删改查功能已经实现了,用的好好的为什么要使用多线程呢?其实想一想,Co ...
- 基于特征的对抗迁移学习论文_有关迁移学习论文
如果你有好的想法,欢迎讨论! 1 Application of Transfer Learning in Continuous Time Series for Anomaly Detection in ...
最新文章
- 非视线成像 - 把墙角变为相机
- heartbeat原理介绍
- Android如何实现简单音乐播放器的代码
- IBM存储部分常见配件PN号查询及描述翻译
- LOJ 数列分块入门6
- jzoj6308-中间值【分治】
- ISO语言代码和国家代码+Locale常量+ISO货币符号
- 趣文:如果编程语言是车
- js禁用按钮_探索js让你的网页“自己开口说话”
- Opencv项目实战:05 物体检测
- 性能优化之电量和网络
- ThinkPad T430i黑苹果Yosemite 10.10.3成功显卡驱动
- 人工智能对图书馆未来的影响,主要包含哪三个方面?
- java spel_Java spring SPEL表达式注入
- 解密Animate.css之CSS3动画实现方式大全源码(6星级)
- 如何将文件FLAC格式快速转换为MP3格式
- 哥特巴赫猜想 尾递归 湘潭孕妇之后的自我检讨
- 在抖音全程看世界杯,超高清直播背后的硬实力
- 如何安装小企鹅输入法
- USB 3.0规范中译本 附录
热门文章
- C++ 中的类型限定符 类型限定符提供了变量的额外信息。
- 疫情加速人脸识别落地:多地试点AI门禁,居民刷脸核验健康码
- 从0到50家AI独角兽,中国人工智能凭什么让世界刮目相看?
- 中国高中生近视率高达81%,眼科专家:源头在小学阶段
- OJ系列之---字符串分割
- ai描边工具怎么打开_ai切片工具怎么用?ai切片工具使用教程
- 006_全局异常处理器
- RecyclerView notifyItem闪屏问题
- pyecharts a python_python 可视化 | pyecharts基本使用
- video php上下切换,TP3.2实现上下篇切换