笔者在刚进入阿里的时候,其实连灰度是什么也不知道,可是灰度这个概念在大厂很是广泛,只要有必定用户量的应用都会涉及到灰度发布,因此准备进大厂的同窗,灰度发布这个概念必定要了解一下。javascript
灰度发布,又被称之为金丝雀发布,是指某次新发布功能特性和旧功能特性之间可以以平滑过渡的方式呈现给用户,就像金丝雀的羽毛同样多种颜色平滑渐变。html
举个例子,某个已上线处于运行中的系统须要一次新的功能迭代,可是因为功能变更较大,因此发布须要考虑用户的使用反馈以及代码可能存在一些未知的异常,这时候则须要将新的功能逐步地一批一批的推送给用户。前端
在这个逐步放量的过程当中,能够根据用户接受度(用户投诉多很少)和观察本次功能是否存在上线前未发现的异常,来决定是否继续发布推送新功能,若是新功能反馈较差或者存在功能异常问题,则中止放量或者回滚到以前稳定的版本,及时修改问题。vue
这样便避免一次推送状况下,若是出现问题则形成线上问题忽然上升形成阻塞用户使用的问题。java
1.提早收集用户使用意见,及时完善产品功能
2.控制未知异常只出如今小范围内,不影响大多数用户
3.发现产品是否存在外在问题(如合规),可及时回滚至已旧版本react
1.放量规则nginx
若是逐步推送新功能,则必须有一种规则让用户按照某些特征分红不一样的群体,这个规则能够是年龄,城市,或者用户注册时的id。例如,用户注册时有一个从0自增的序号位,当灰度放量时能够以该序号为维度,从小到大的放量,直至百分百完成。程序员
一个完善的系统在设计之初必定会考虑到灰度方案,若是你仔细观察用户的uid在注册的时候必定有一个序号位,像身份证号里第十五位是从0-9的序号位,通常的用户UID会留两位做为自增序号位,灰度时这两位通常被做为灰度特征。
面试
2.资源新旧版本ajax
能明确的标识出要给用户展现两种页面形态,能够是之前端静态版本号的形式,如每次发布资源后,静态资源的版本号连接改变一下,这时候灰度则实际上是两个不一样资源请求连接逐步从旧到新的过程。
下面是灰度的实现基本原理,最关键的仍是判断灰度用户这一步操做,能够在请求发出去前进行判断,而后直接请求对应的资源,也能够请求到了服务端后,服务端先区别出用户是否属于灰度名单内,再返回对应资源内容,具体还要看前端应用是怎样的形式部署的,服务端渲染或者是客户端渲染均有关系。
1.服务端渲染应用
服务端渲染应用会在返回客户端以前将静态模板渲染好,知道这个是很是重要的,这意味着前端灰度这个过程要在用户的请求返回以前就完成,在客户端不处理任何灰度相关的内容。以下图:
简单描述一下,这里用户首先发起请求后,服务器并不会直接组装静态资源,而是先去灰度规则里获取名单,而后将灰度名单拿到进行判断处理来决定渲染那一套模板资源给到客户端,最终给处于灰度名单里的用户展现新版本页面,而非名单内的用户继续使用旧版本的页面内容,如须要放量时,直接在灰度规则里进行修改便可。
看看下面代码示例
// 服务端代码
// 静态模板
const model1 = () => {
return ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <div id="mydiv">我是A界面</div> </body> </html> `
}
const model2 = () => {
return ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <div id="mydiv">我是B界面</div> </body> </html> `
}
const isPass = getRule(req.uid) // 查询规则
if (isPass) { // 在白名单
res.render(model2)
} else {
res.render(model1)
}
...复制代码
上面的代码中,有A,B两个版本的界面,用户请求的资源在返回以前先经过getRule获取灰度规则,肯定了是否在白名单里,而后决定返回那一套模板内容。
2.先后端半分离的应用
这里先后端半分离的应用是指在有一部分前端应用的html文件依旧在服务端上,可是实际上却在客户端渲染的,相信你们见的比较多,例以下面的这段代码。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <div id="react-content"></div> </body> <script src="http://cdn.com/1.0.1/my.js"></script> </html>复制代码
当咱们使用react或者vue的时候,最后将代码打包到一个JavaScript文件里,在一个html文件中加载使用,而这个html文件则会被放到一个服务端系统里,当用户请求到资源时,将这段html返回给客户端,客户端拿到内容后加载在http://cdn.com/1.0.1/my.js 的网络资源,而后本地渲染。
这里注意一下,咱们每次打包的资源都会有一个版本号,好比上面的版本是1.0.1,这里在cdn上的存贮路径也是1.0.1/my.js,使用路径的惟一性来区别与其余版本不一致,固然也能够在文件名上加版本号,如 /my_1.0.1.js,只要能识别出资源的惟一性均是能够的。
下面来一段伪代码看看这里灰度又该怎么作。
// 服务端代码
const isPass = getRule(req.uid) // 查询规则
let version = 1.0.1; // 旧版本号
if (isPass) { // 在白名单
version = 2.0.0
}
// 静态模板
const model = version => {
return ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <div id="react-content"></div> </body> <script src="http://cdn.com/{version}/my.js"></script> </html> `
}
res.render(model(version)) // 返回带版本号的模板复制代码
这段代码中,由于两次迭代需求,前端开发者将前端资源打包成了两个资源包,分别上传到CDN的不一样位置处,以版本号做为标识来肯定新旧内容。
当用户的请求被接收到后,先经过getRule获取灰度规则来肯定给当前用户展现哪个版本号的资源,而后返回带着资源版本的模板内容,客户端接收到该模板以后,再加载对应的版本号资源,从而达到灰度要求。
3. 客户端渲染的前端应用
目前来讲最多见的一种部署类型,前端开发完成后,直接打包至CDN上,而后利用nginx来请求到静态资源,这时候CDN服务器并不会去作灰度判断相关的操做,即这时候不能让后端经过一段获取灰度逻辑来控制版本,而此时前端数据请求都是异步ajax的方式,那灰度又该怎么作呢?
第一种思路:
咱们能够在前端代码里写两套内容,在页面渲染以前发起异步获取灰度规则的请求,将结果拿到后在客户端决定渲染那一套页面,从而达到灰度的要求。
看看下面的伪代码:
// 客户端端代码
// 组件
const component1 = () => {
return (<div>我是A组件</div>)
}
const component2 = () => {
return (<div>我是B组件</div>)
}
const isPass = $.ajax('/getRule?uid') // 查询规则
...
render() {
if (isPass) { // 在白名单
return model2()
} else {
return model1()
}
}
...复制代码
上面的这段客户端代码便可完成用户灰度,可是有一个问题,当后期需求增多的时候前端代码将很是庞大,并且每次的新需求发布的时候势必要去测试回归旧的版本是否被改动了,将维护两套内容,随着应用体积变大维护将变得很是累。
第二种思路:
若是咱们继续保持版本号来区分每次的迭代,只是在渲染前获取到正确的版本资源来渲染是否是就能够解决上面的问题呢?
看看下面的伪代码:
// 客户端端代码
const syncLoadJs = function (version, fn) {
const oScript = document.createElement('script');
oScript.setAttribute('src',`https://cdn.com/{version}/my.js`);
oScript.setAttribute('type','text/javascript');
oScript.onload = fn;
oScript.onerror = function() { window.location.href = '/error.htm' };
document.body.appendChild(oScript);
},
...
const isPass = $.ajax('/getRule?uid') // 查询规则
let version = 1.0.1; // 旧版本号
if (isPass) { // 在白名单
version = 2.0.0
}
syncLoadJs(version, function(){
ReactDOM.render()); // 客户端获取完资源后进行渲染
})
...复制代码
这一种方式则是在客户端渲染以前先进行异步获取名单来决定资源版本,在拿到资源版本以后才会进行页面渲染工做。
可是这里存在一个问题,每个页面都须要去获取灰度规则,而后判断是否灰度,这个灰度请求将阻塞页面的,可能会形成较差的用户体验,因此咱们能够考虑使用客户端的localStrage来存储这个用户是否为灰度用户,而不是每次请求资源时都发请求去判断是否为灰度用户,而后按期的更新localStrage内存储的值,取代大量的请求形成的体验问题。
如上内容均为本身总结,不免会有错误或者认识误差,若有问题,但愿你们留言指正,以避免误人,如有什么问题请留言,会尽力回答之。若是对你有帮助不要忘了分享给你的朋友!也能够关注做者,查看历史文章而且关注最新动态,助你早日成为一名全栈工程师!
往期精彩回顾