Taobao 온라인 애플리케이션의 전통적인 소프트웨어 스택 구조는 Nginx Velocity Java입니다.
이 시스템에서 Nginx는 트랜잭션을 처리한 다음 Velocity 템플릿을 사용하여 데이터를 최종 페이지에 렌더링하는 Java 애플리케이션에 요청을 전달합니다.
Node.js를 도입한 후에는 다음과 같은 문제에 직면하게 됩니다.
기술 스택의 토폴로지는 어떻게 설계해야 하며, 배포 방법은 어떻게 과학적이고 합리적으로 선택해야 합니까? 프로젝트가 완료된 후, 운영 및 유지관리가 편리하고 신속하도록 트래픽을 분할하는 방법은 무엇입니까? 온라인 문제가 발생하면 최대한 빨리 위험을 해결하고 더 큰 손실을 피하는 방법은 무엇입니까? 애플리케이션의 상태를 확인하고 로드 밸런싱 스케줄링 수준에서 이를 관리하는 방법은 무엇입니까? 시스템 토폴로지 상속
프론트엔드와 백엔드 분리에 대한 우리의 생각과 실천에 따르면 (2) - 프론트엔드와 백엔드 분리를 기반으로 한 템플릿 탐색은 Velocity를 Node.js로 대체해야 이 구조가 다음과 같이 됩니다. :
물론 이상적인 목표입니다. 하지만 기존 스택에 처음으로 Node.js 레이어를 도입하는 것은 결국 새로운 시도입니다. 안전을 위해 즐겨찾기의 아이템 컬렉션 페이지(shoucang.taobao.com/item_collect.htm)에서만 신기술을 활성화하기로 결정했으며, 그 외 페이지에서는 기존 솔루션을 계속 사용할 예정입니다. 즉, Nginx는 요청된 페이지 유형을 결정하고 요청을 Node.js 또는 Java로 전달해야 하는지 여부를 결정합니다. 따라서 최종 구조는 다음과 같습니다.
배포 계획
위의 구조는 문제가 없어 보이지만 사실은 여전히 새로운 문제가 기다리고 있습니다. 전통적인 구조에서 Nginx와 Java는 동일한 서버에 배포됩니다. Nginx는 포트 80에서 수신하고 상위 포트 7001에서 수신하는 Java와 통신합니다. 이제 Node.js가 도입되었으므로 포트를 수신하는 새로운 프로세스를 실행해야 합니다. Node.js와 Nginx Java를 동일한 시스템에 배포해야 할까요, 아니면 별도의 클러스터에 Node.js를 배포해야 할까요?
두 가지 방식의 특징을 비교해 보겠습니다.
Taobao Favorites는 일일 수천만 건의 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 发起一次 <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 应用彻底中心化后,是否可以考分集群部署,以提高服务器利用率。或者,发布与回滚的方式是否能更加灵活可控。等等细节,都值得再进一步研究。