灰度这个概念在大厂非常普遍,只要有一定用户量的应用都会涉及到灰度发布,所以准备进大厂的同学,灰度发布这个概念一定要了解一下。
一、灰度发布是什么?
灰度发布,又被称之为金丝雀发布,是指某次新发布功能特性和旧功能特性之间能够以平滑过渡的方式呈现给用户,就像金丝雀的羽毛一样多种颜色平滑渐变。
简单描述一下,这里用户首先发起请求后,服务器并不会直接组装静态资源,而是先去灰度规则里获取名单,然后将灰度名单拿到进行判断处理来决定渲染那一套模板资源给到客户端,最终给处于灰度名单里的用户展示新版本页面,而非名单内的用户继续使用旧版本的页面内容,如需要放量时,直接在灰度规则里进行修改即可。
看看下面代码示例:
// 服务端代码 // 静态模板 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 内存储的值,取代大量的请求造成的体验问题。
如上内容均为自己总结,难免会有错误或者认识偏差,如有问题,希望大家留言指正,以免误人,若有什么问题请留言,会尽力回答之。
文章来源:全栈者