淘宝网上应用的传统软件栈结构是Nginx Velocity Java,也就是
在这个系统中,Nginx将请求转发给Java应用程序,Java应用程序处理事务,然后通过Velocity模板将数据呈现到最终页面中。
引入Node.js后,我们必然会面临以下问题:
如何设计技术栈的拓扑结构,如何选择部署模式,是科学合理的。项目建成后,如何分流交通方便快捷便于运维?遇到线上问题,如何尽快解除危险,避免更大的损失?如何确保应用程序的健康,并在负载平衡调度级别进行管理?轴承系统拓扑
根据我们对前端分离的思考和实践(2)——基于前端分离的模板探索,需要用Node.js代替Velocity,这样这个结构就变成:
这当然是最理想的目标。然而,这是首次将Node.js引入传统堆栈的新尝试。为了保险起见,我们决定只在喜欢的页面(shou苍.淘宝.com/item_collect.htm)上启用新技术,其他页面将沿用传统方案。也就是说,Nginx判断请求的页面类型,决定请求应该转发给Node.js还是Java。然后,最终的结构变成了:
部署方案
以上结构看似没有问题,但实际上新的问题还在前面。在传统结构中,Nginx和Java部署在同一个服务器上。Nginx监控端口80,与Java监控端口7001通信。现在已经引入了Node.js,有必要运行一个新的监听端口的进程。Node.js应该部署在与Nginx Java相同的机器上,还是应该部署在单独的集群中?让我们比较一下这两种方法各自的特点:
淘宝收藏夹是一个日均PV数千万的应用,对稳定性要求很高(其实任何产品在线不稳定都是不可接受的)。如果采用相同的集群部署方案,只需要一次文件分发和两次应用重启即可完成发布。在回滚的情况下,它只需要操作基线包一次。在性能方面,同样的集群部署也有一些理论上的优势(虽然内网的交换机带宽和延迟非常乐观)。至于一对多或者多对一的关系,理论上是可以充分利用服务器的,但是和稳定性的要求相比,这个就没有那么迫切需要解决了。因此,在收藏夹的转换中,我们选择了相同的集群部署方案。
灰度模式
为了确保最大的稳定性,该转换没有直接完全移除Velocity代码。应用集群中有近100台服务器,我们逐步引入基于服务器粒度的流量。也就是说,虽然Java Node.js进程运行在所有的服务器上,但是Nginx上是否有对应的转发规则决定了在这个服务器上获取宝藏收藏的请求是否会通过Node.js进行处理,Nginx的配置如下:
location='/item _ collect . htm ' { proxy _ pass http://127 . 0 . 0 . 1:6001;# Node.js进程监听的端口}只有添加了这个Nginx规则的服务器才会让node . js处理相应的请求。有了Nginx配置,可以非常方便快捷地增减灰流量,成本非常低。如果出现问题,可以直接回滚Nginx配置,瞬间回到传统的技术栈结构,解除危险。
当我们第一次发布它时,这条规则只在两台服务器上启用,这意味着大约只有不到2%的在线流量由Node.js处理,其余的流量请求仍然由Velocity呈现。之后根据情况逐渐增加流量。最后,在第三周,所有服务器都启用了。此时,生产环境中具有100%流量的产品集合页面由Node.js呈现(您可以在源代码中搜索关键字Node.js)。
转动
灰度过程并不总是平滑的。在流量全切之前,遇到了一些或大或小的问题。大部分都与具体业务相关,值得从一个与技术细节相关的陷阱中学习。
健康检查
传统架构下,负载均衡调度系统会每秒向每台服务器端口80的特定URL发送一个get请求,根据返回的HTTP Status Code是否为200来判断服务器是否正常工作。如果请求在1秒后超时或HTTP状态代码不是200,则不会向服务器引入流量,以避免在线问题。
这个请求的路径是Nginx-Java-Nginx,也就是说只要返回200,这个服务器的Nginx和Java都处于健康状态。随着Node.js的引入,这条路径变成了nginx-node . js-Java-node . js-nginx。对应的代码是:
var http=require(' http ');app.get('/status.taobao ',function(req,res) { http.get({ host: '127.1 ',port: 7001,path: '/status.taobao' },function(RES){ RES . send(RES . statuscode);}).on('error ',函数(err){ logger . error(err);第404号决议;});});但是在测试过程中发现,Node.js转发这样的请求时,每隔六七次就会有一次,需要几秒钟甚至十几秒钟才能从Java得到一个返回。这会导致负载均衡调度系统认为服务器异常,然后切断流量,但实际上这个服务器可以正常工作。这显然是个大问题。
经调查发现,默认情况下,Node.js会使用HTTP Agent类创建HTTP连接,实现套接字连接池,每个主机端口对的默认连接上限为5。同时,HTTP Agent类发起的请求默认携带Connection: Keep-Alive,导致返回的连接无法及时释放,后续请求只能排队。
有三种最终解决方案:
禁用HTTP Agent,即在调用get方法时将参数agent:添加为false,最终代码为:
var http=require(' http ');app.get('/status.taobao ',function(req,res) { http.get({ host: '127.1 ',port: 7001,agent: false,path: '/status.taobao' },function(RES){ RES . send(RES . status code);}).on('error ',函数(err){ logger . error(err);第404号决议;});});设置http对象的最大全局套接字数量:
http . GlobalAGENT . MaxSockets=1000;请求返回时及时主动断开连接:
http.get(选项,函数(res) { })。on('socket ',function(socket){ socket . emit(' AgentRemove ');//监控套接字事件并在回调中调度agentRemove事件});在实践中,我们选择第一种方法。经过这次调整,健康检查没有发现其他问题。
关闭
Node.js与传统业务场景结合的实践才刚刚起步,还有很多值得进一步探索的优化点。例如,在彻底集中Java应用程序之后,您可以测试集群部署以提高服务器利用率吗?或者说,发布和回滚的方式能否更加灵活可控?等等,都值得进一步研究。