La structure traditionnelle de la pile logicielle des applications en ligne Taobao est Nginx Velocity Java, c'est-à-dire :
Dans ce système, Nginx transmet la demande à l'application Java, qui traite la transaction puis restitue les données sur la page finale à l'aide du modèle Velocity.
Après avoir introduit Node.js, nous sommes forcément confrontés aux problèmes suivants :
Comment concevoir la topologie de la pile technologique et comment sélectionner la méthode de déploiement pour qu'elle soit scientifique et raisonnable ? Une fois le projet terminé, comment diviser le trafic afin qu'il soit pratique et rapide pour l'exploitation et la maintenance ? Lorsque vous rencontrez des problèmes en ligne, comment résoudre le danger le plus rapidement possible et éviter des pertes plus importantes ? Comment s’assurer de la santé de l’application et la gérer au niveau de la planification de l’équilibrage de charge ? Héritage de la topologie du système
Selon notre réflexion et notre pratique sur la séparation front-end et back-end (2) - Exploration de modèles basée sur la séparation front-end et back-end, Velocity doit être remplacé par Node.js, pour que cette structure devienne :
C'est bien sûr l'objectif idéal. Cependant, introduire la couche Node.js pour la première fois dans la pile traditionnelle est après tout une nouvelle tentative. Pour des raisons de sécurité, nous avons décidé d'activer la nouvelle technologie uniquement sur la page de collecte d'articles des favoris (shoucang.taobao.com/item_collect.htm), et les autres pages continueront à utiliser la solution traditionnelle. Autrement dit, Nginx détermine le type de page demandé et détermine si la demande doit être transmise à Node.js ou Java. Ainsi, la structure finale est devenue :
Plan de déploiement
La structure ci-dessus ne semble poser aucun problème, mais en fait, de nouveaux problèmes attendent toujours. Dans la structure traditionnelle, Nginx et Java sont déployés sur le même serveur Nginx écoute sur le port 80 et communique avec Java qui écoute sur le port haut 7001. Maintenant que Node.js a été introduit, nous devons exécuter un nouveau processus qui écoute le port. Devons-nous déployer Node.js et Nginx Java sur la même machine, ou devons-nous déployer Node.js dans un cluster séparé ?
Comparons les caractéristiques des deux méthodes :
Taobao Favorites est une application avec des dizaines de millions de PV moyens quotidiens, qui a des exigences de stabilité extrêmement élevées (en fait, l'instabilité en ligne de tout produit est inacceptable). Si vous adoptez la même solution de déploiement de cluster, vous n'avez besoin que d'une seule distribution de fichiers et de deux redémarrages d'application pour terminer la version. Si une restauration est nécessaire, vous n'avez besoin d'exploiter le package de base qu'une seule fois. En termes de performances, le déploiement dans le même cluster présente certains avantages théoriques (même si la bande passante du commutateur et la latence de l'intranet sont très optimistes). Quant à la relation un-à-plusieurs ou plusieurs-à-un, il est théoriquement possible d'utiliser pleinement le serveur, mais par rapport aux exigences de stabilité, ce point n'est pas si urgent et doit être résolu. Par conséquent, dans la transformation des favoris, nous avons choisi la même solution de déploiement de cluster.
Mode niveaux de gris
Afin d'assurer une stabilité maximale, cette transformation n'a pas directement supprimé complètement le code Velocity. Il y a près de 100 serveurs dans le cluster d'applications. Nous utilisons les serveurs comme granularité pour introduire progressivement du trafic. En d'autres termes, bien que les processus Java Node.js soient exécutés sur tous les serveurs, l'existence de règles de transfert correspondantes sur Nginx détermine si les demandes de collecte de trésors sur ce serveur seront traitées par Node.js. La configuration de Nginx est :
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 发起一次 <font face="NSimsun">get</font>
请求,根据返回的 HTTP Status Code 是否为 <font face="NSimsun">200</font>
来判断该服务器是否正常工作。如果请求 1s 后超时或者 HTTP Status Code 不为 <font face="NSimsun">200</font>
,则不将任何流量引入该服务器,避免线上问题。
这个请求的路径是 Nginx -> Java -> Nginx,这意味着,只要返回了 <font face="NSimsun">200</font>
,那这台服务器的 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', function(err) { logger.error(err); res.send(404); }); });
但是在测试过程中,发现 Node.js 在转发这类请求的时候,每六七次就有一次会耗时几秒甚至十几秒才能得到 Java 端的返回。这样会导致负载均衡调度系统认为该服务器发生异常,随即切断流量,但实际上这台服务器是能够正常工作的。这显然是一个不小的问题。
排查一番发现,默认情况下, Node.js 会使用 <font face="NSimsun">HTTP Agent</font>
这个类来创建 HTTP 连接,这个类实现了 socket 连接池,每个主机+端口对的连接数默认上限是 5。同时 <font face="NSimsun">HTTP Agent</font>
类发起的请求中默认带上了 <font face="NSimsun">Connection: Keep-Alive</font>
,导致已返回的连接没有及时释放,后面发起的请求只能排队。
最后的解决办法有三种:
禁用 <font face="NSimsun">HTTP Agent</font>
,即在在调用 <font face="NSimsun">get</font>
方法时额外添加参数 <font face="NSimsun">agent: false</font>
,最后的代码为:
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.statusCode); }).on('error', function(err) { logger.error(err); res.send(404); }); });
设置 <font face="NSimsun">http</font>
对象的全局 socket 数量上限:
http.globalAgent.maxSockets = 1000;
在请求返回的时候及时主动断开连接:
http.get(options, function(res) { }).on("socket", function (socket) { socket.emit("agentRemove"); // 监听 socket 事件,在回调中派发 agentRemove 事件 });
实践上我们选择第一种方法。这么调整之后,健康检查就没有再发现其它问题了。
合
Node.js 与传统业务场景结合的实践才刚刚起步,仍然有大量值得深入挖掘的优化点。比比如,让 Java 应用彻底中心化后,是否可以考分集群部署,以提高服务器利用率。或者,发布与回滚的方式是否能更加灵活可控。等等细节,都值得再进一步研究。