健康检查上面提到,我们通过网关将流量转发到Node应用。网关如何确定节点应用程序的可用性?如果Node应用在发布过程中也转发了流量,则请求会失败。因此,我们的网关会对Node应用程序进行健康检查。我们首先要确定Node应用是健康的,即可以对外服务。具体来说,网关会每隔30秒调整一次Node应用健康检查的HTTP接口。如果接口返回的code为200,则表示Node应用可用,将转发用户的请求,等待下次校验。如果返回其他代码,则表示应用不可用,请求不会被转发。30秒后重复该过程。【示意图】这个方案实现起来非常简单,只需要添加一个可以向Node.js进行正常请求的HTTP接口即可。例如,我们使用的接口`/health/check`在它的控制器中有`this.ctx.body='OK'`That'sit.如果Node应用程序正常启动,可以接受用户请求,则该接口返回的code为200。如果无法正常访问该接口,返回的code不是200,则表示无法访问整个应用程序。那么上面的程序就没有问题了吗?必须有。比如我们发布的时候,首先要让Node应用下线。如果Node应用在通过健康检查后恰好下线,会导致转发给Node应用的流量在30秒内失效,所以我们有了升级方案——平滑发布。流畅发布流畅发布需要和发布系统配合,即我们发布应用时,发布系统会自动调用Node应用的离线接口。控制应用程序的状态,而这个状态与应用程序的真实状态无关。调用下线接口后,将应用状态设置为下线,然后等待一段时间,应用才真正下线,所以如果此时有流量进来,应用仍然可以正常服务。【示意图】逻辑很简单,但是在实现的时候要考虑到Egg.js的多进程模型。Egg.js一般会根据服务器的CPU核数启动相应数量的Worker进程,从而完美利用多核资源。每个进程运行相同的源代码,这些进程同时监听一个端口,所以当系统调用离线接口时,只有其中一个进程会收到请求。如果只有全局变量设置为offline的话,其他进程在收到健康检查的时候还是会返回online状态,这是错误的,所以使用进程间通信告诉所有进程都去offline。基于这些分析,我们实现了Egg.js插件`pp-ndp`。另外,由于Egg.js插件中不允许路由,所以我们采用中间件的形式实现。主要代码如下:```const{request}=ctx;const{path,hostname}=request;if(path===online){app.messenger.sendToApp(ONLINE,'');ctx.body='NDP:Nodejs在线';}elseif(path===offline){app.messenger.sendToApp(OFFLINE,'');ctx.body='NDP:Nodejs离线';}elseif(path===check){ctx.body='NDP:Nodejs启动成功';}elseif(path===status){if(app[ISONLINE]){ctx.body='NDP:Nodejs在线';}else{ctx.status=500;}}else{awaitnext();}```当然这个方案的前提是有多个Node服务机,并且分组发布。如果只有一台机器,就不用那么麻烦了。反正放出肯定会导致服务器停服。插件`pp-ndp`支持自定义配置在线、离线、查询、状态四个URL,以满足不同的业务需求。该方案不仅解决了我们顺利发布的问题,也让发布不再那么可怕,应用上线后也可以使用该方案提供更好的服务。例如,应用获取配置后,可以将应用设置为在线状态。或者,可以在应用程序成功注册或连接到服务后将应用程序设置为在线状态。让应用保证对外服务最健康的状态。CDN上的代码和代码发现看到CDN可能会比较陌生。为什么Node应用程序需要CDN?其实是因为我们为了方便使用同构渲染,将前端代码和Node代码放在了一个应用中。这样虽然解决了服务端Rendering代码访问问题,但是客户端代码使用CDN更合理。网上有很多关于webpack使用CDN的文章。我主要介绍如何找到前端代码,包括代码上的CDN和模板中插入的前端代码的URL。主要是使用webpack插件`webpack-manifest-plugin`,它会生成一个文件,比如我们使用的`manifest.json`,里面包含了前端代码资源名和对应的路径,类似:```{"vendor.js":"/static/f5e0281b/js/vendor.chunk.js","vendor.js.map":"/static/f5e0281b/js/vendor.chunk.js.map","Page.css":"/static/f2065164/css/Page.chunk.css","Page.js":"/static/f2065164/js/Page.chunk.js","Page.js.map":"/static/f2065164/js/page.chunk.js.map",}```只需要将这个文件中列出的文件上传到CDN即可,不需要手动一一查找包目录。上传到CDN时,请为每个文件保持相同的路径。发布过程中的代码编译完成后,使用我们实现的`pp-cdn`工具上传。在Node模板中引用代码时,使用我们开发的Egg.js插件`pp-just`,使用:```html```插件也会读取`manifest.json`文件,输出添加CDN域名后的URL。比如上面的代码转化为:```html```其实这样做的原因是为了利用前端代码多版本的好处。我们将文件哈希作为文件路径的一部分作为多版本控制,这样每次发布后,新生成的文件路径在编译后会被写入`manifest.json`,然后可以通过获取最新版本上面的方法代码。当然目前Node和前端代码放在一起是不合理的,可能会导致不必要的release,后面应该完全分开,但是使用`manifest.json`也可以作为我们后续的一种方式代码分离解决方案。总结技术方案的选择一般需要结合团队已有的技术方案和业务需求。本文介绍的平滑发布方案,确实解决了我们业务前期的发布问题,让发布更加安全。但是随着业务的发展,我们需要一个灰度环境来更好的保证应用的健康,提前发现应用中的问题。另外,我们还需要了解自己应用的运行状态,所以下节课我们会分享灰度发布和应用监控相关的内容。
