(三)——上线
最后更新于:2022-04-01 20:59:36
> 本文链接:[Web开发新人培训系列(三)——上线](http://rapheal.sinaapp.com/2015/01/22/webdev-release/ "本文固定链接 http://rapheal.sinaapp.com/2015/01/22/webdev-release/")
> 来源:[拉风的博客](http://rapheal.sinaapp.com/)
## 前言
上一篇我们介绍清楚了一个普通Web应用的网络结构,浏览器发起一个请求的目的无非就是想要Web服务器给他一个资源。资源对于这一篇的主题又有什么联系?看来没有解释清楚我这里想说的“资源”,上线这个话题没法继续了。
发个招聘广告:微信开放平台基础部招后台开发,有意者把简历发到我邮箱:raphealguo@qq.com
## 资源
我们看看浏览器其实最后收到服务器返回的都是什么“资源”:描述页面结构的HTML文件;网页的样式CSS文件;网页的脚本JS文件;网页的音频/视频/Flash……
因此我们得到一个简单的结论:
> 资源 == 文件
对于那些动态数据(通过PHP/Java等执行后得到的结果),例如:HTML内容;JSON数据…… 其实我们也可以把这个请求的资源等同于对应的CGI执行文件(PHP脚本文件/CGI二进制等程序)
浏览器需要知道服务器回的是什么内容,所以每次的响应包还有一个类型说明这个资源是什么东西,也就是我们经常说的MIME type`(Multipurpose Internet Mail Extensions)代表互联网媒体类型(Internet media type)`
> MIME type == 文件类型
## 上线?
好,既然浏览器每次需要服务器返回一个资源,那我们的Web服务器其实就是放着对应服务所需要的资源。所以我们把这个操作定义为上线:
> 上线 == 把资源放上Web服务器
而资源等同于文件,因此:
> 上线 == 把文件放上Web服务器
像云龙([@前端农民工](http://weibo.com/fouber))说过的,Web架构最重要的能力就是:如何管理资源跟如何部署资源,这里的部署资源我们可以简单认为就是上线。 留意一下,下文为了通顺,有些地方用了`发布` 跟 `部署`这个字眼来描述*上线*的一个动作。
## 静态资源 & 动态资源
其实我们可以对Web服务器上的资源做个简单的分类,像JS文件、CSS文件这些资源对于所有用户来说都是一样的,我们把它们归类到静态资源。对于不同用户,它们看到的网页其实是不一致的(例如页面里边有他们各自的昵称,id等信息),这是通过CGI程序实时计算,为不同用户生成不同的HTML产生,我们把这样的HTML资源归类为动态资源。
由于动态资源是需要实时获取的,因此请求最后都需要落到我们的CGI程序上。而对于静态资源来说,所有请求都是一样的,不需要通过CGI去实时获取文件内容做响应。
## 1台Web服务器
我们假设世界很美好,我们仅需要一台服务器就能抗住所有的访问。OK,现在我们在自己机器(下文把开发阶段使用的机器称为`开发机`)上开发完毕的代码通过文件传输到Web服务器。 传输完毕的那一时刻,我们就完成了上线!!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d0e875.png)
对于像二进制等程序,在传输完成后,我们还需要重启该程序,让新的程序生效。
## 多台Web服务器
但是现实往往不是这样的,在前一篇文章也介绍过,一般我们需要多台Web服务器,在它们上层的proxy机器把用户的请求平摊在这些机器上。我们现实的网络结构大多数是这样的:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d25671.png)
看到这里,你有没有意识到其实上线的过程隐含一个什么样的问题!? 刚刚我们在只有一台服务器的情况下,我们只需要把文件传输到这台服务器就算此次上线完毕。同样道理在多台服务器的网络结构下,我们把文件依次上传到这些服务器上,对于所有服务器都传输完毕后才算此次上线完毕。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d3ebab.png)
你可以注意到,在上线的过程中,有一些服务器是新资源,有一些服务器是旧资源。问题就来了:
*服务器1已经部署上新的资源,而服务器2上还是旧资源。假设这样一个场景,用户第一个HTML请求到了服务器1,然后返回一个新的HTML,接着用户第二个JS请求又跑到服务器2。*
在用户侧的表现就是一个旧的JS文件作用在一个新的HTML上,这样可能就引发了异常。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d5ce15.png)
存在问题我们就要解决它!分析一下:既然问题是出在服务器资源不一致,那有没有办法使得服务器资源一致呢?仔细想想没法做到资源的实时一致,多台服务器的网络结构下必然会存在某小段时间间隙存在资源不一致的现象。既然我们没法解决这个问题的根本,那就绕开,我们让同一个用户的多次请求永远都在同一台机器上,如何?
*用户第一个HTML请求到了服务器1,用户第二个JS请求还是到服务器1,其他CSS请求、Flash请求……所有资源都落在服务器1* 这样在没完成上线的这个间隙时间,用户要么访问到的全部都是旧资源,要么全部都是新资源。这样就没有新旧资源依赖关系混乱造成的异常了。非常完美! 那剩下的问题就是我们怎么让同一个用户的请求一直都落在同一台机器上了!我们按照下边三步走,通过proxy把同一个用户的请求转发到同一个Web服务器:
1. proxy反向代理机器收到用户请求
2. proxy通过这个请求某些flagid标记得知当前这个请求是A用户的
3. 接着proxy定义一个hash算法getServer,通过计算`svr = getServer(flag)`得到最后这个请求要去到的Web服务器svr
所以我们只要保证getServer这个hash算法每次输出是唯一即可,这个很容易做到。嗯,最后遗留了一个小问题,用户的请求带上的flagid是怎么带的?我们先不纠结这个怎么做,我们只要在用户登陆我们系统后,在cookies种上一个能标记出这个用户的id即可。
## 灰度
我们回顾一下刚刚讲到了那个让我们头疼的问题在经过我们解决之后得到一个什么样的现象:
* 用户a所有的资源请求都落在服务器1
* 用户b所有的资源请求都落在服务器2
到这里,我们看到如果我们上线一个新的功能,在上线的过程中,有一些用户可以体验到新功能,一部分用户则体验不到。这是个不错的AB test方法!倘若我们有一个非常重要的更新,或者做了一个可能对后台服务器有压力的功能,我们可以通过控制这个上传的过程先让小部分用户体验,确认新功能在各方面都正常(包括:用户体验、服务器压力等等)之后,我们才把这次更新慢慢部署到所有服务器上。 我们竟然在解决一个问题的情况下还得到了一个非常棒的灰度发布方案! 当然我们也可以通过代码来控制新功能的灰度,有时候我们也可以两种灰度方案相互结合,有时候可以避免更大的灾难。
最后我们简单总结一下:
> 在灰度上线过程中,新/旧两套资源是同时存在现网服务的。
## 测试环境
我们现在整个过程是这样:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d7dd7a.png)
按照这个流程,测试人员在开发机测试完毕后,把开发机的资源发布到现网服务器。这里存在一些非常容易出错的环节:
1. 测试过程中,开发人员在开发机修改代码,可能会打断测试流程
2. 在发布的过程中,如果开发机器的代码不小心被某个开发删掉或者修改,就会出现现网bug
3. ……
归根到底,出现这些问题的原因就是我们没有把上线和不稳定的开发环境相互隔离!
既然这样,我们在开发机跟现网服务器中间在加入一个中间层:测试机。为了保证资源发布到测试机的过程不会发生以上问题,所以我们再加入一层:编译机。我们来看现在上线的流程:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-09-07_57cfde1d94f5a.png)
开发人员在不稳定的环境开发,通过辅助系统(我们这边称为变更系统)提个变更单记录需要上线的资源。开发每次在修改完确定代码稳定后,去编译机上把需要变更的资源更新。接着测试人员通过变更系统可以把编译机上的文件部署到测试机,测试就可以专心在一个稳定的测试环境上开始工作了。直到整个发布特性都测试完毕,再通过变更系统把测试机变更的资源发布到现网Web服务器。
到这里好像已经把上线涉及的所有问题都说清楚了。但是还没考虑全!再回顾一下我上一篇文章[《Web开发新人培训系列(二)——经典的Web应用网络模型》](http://rapheal.sinaapp.com/2014/10/24/webdev-network-model/ "《Web开发新人培训系列(二)——经典的Web应用网络模型》")。到现在为止我们一直在讲述如何把资源上线到我们自己的Web服务器,而忽略了Web网络结构重要的角色CDN。
## CDN
上次说到我们把资源部署到CDN有两种方式:
1. 主动推送资源到CDN
2. CDN被动回源到Web服务器请求资源
我们设定一个场景:要上线的资源index.php和index.js,由于index.js是静态资源,所以我们把它部署到CDN上。 我们留意到一个正常的访问是html页面请求之后才发起对静态资源请求,也就是有新的js引用(index.php),才有新的js请求。所以我们在index.php部署前,先把静态资源部署好,这样就不会有混乱的现象出现了。
接下来下边简单叙述一下两种方式的上线时序。
### 主动推送资源到CDN
在我们把index.php部署到我们的Web服务器上之前,我们需要先把index.js的内容推送到CDN的全部节点上,等待推送完毕后,我们才能上线动态资源index.php。
### CDN被动回源
对于回源,有个非常重要的节点,就是回源机器!也就是CDN收到JS文件请求index.js,然后CDN节点上没有这个文件,它需要向回源机器要这个资源。我们上线第一步是把静态资源index.js先部署到回源机器。
### 遗留问题
也许你会注意到,如果我把新的index.js部署到CDN后,旧的index.js就没了,这样就违背了我们刚刚前边说的结论:灰度上线过程,新旧资源在现网服务共存。 当然这个问题是可以解决的,为了不把问题再扩散,这篇文章就到此结束吧,下次再整理文章详细说一下我们在实际项目中整套资源的部署图。
## 后话
我计划下一篇文章大概写一下SVN的相关概念,紧接着再有一篇专门写我们如何把SVN代码管理跟上线流程更规范化,提高在上线/测试环节的效率以及减少我们人工操作带来的失误。
';