前后端分离的思考与实践(六)

原文出处: 淘宝UED - 筱谷

Nginx + Node.js + Java 的软件栈部署实践

关于前后端分享的思考,我们已经有五篇文章阐述思路与设计。本文介绍淘宝网收藏夹将 Node.js 引入传统技术栈的具体实践。

淘宝网线上应用的传统软件栈结构为 Nginx + Velocity + Java,即:

在这个体系中,Nginx 将请求转发给 Java 应用,后者处理完事务,再将数据用 Velocity 模板渲染成最终的页面。

引入 Node.js 之后,我们势必要面临以下几个问题:

  1. 技术栈的拓扑结构该如何设计,部署方式该如何选择,才算是科学合理?
  2. 项目完成后,该如何切分流量,对运维来说才算是方便快捷?
  3. 遇到线上的问题,如何最快地解除险情,避免更大的损失?
  4. 如何确保应用的健康情况,在负载均衡调度的层面加以管理?

系统拓扑

按照我们在前后端分离的思考与实践(二)- 基于前后端分离的模版探索一文中的思路,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 部署在单独的集群呢?
我们来比较一下两种方式各自特点:

淘宝网收藏夹是一个拥有千万级日均 PV 的应用,对稳定性的要求性极高(事实上任何产品的线上不稳定都是不能接受的)。如果采用同集群部署方案,只需要一次文件分发,两次应用重启即可完成发布,万一需要回滚,也只需要操作一次基线包。性能上来说,同集群部署也有一些理论优势(虽然内网的交换机带宽与延时都是非常乐观的)。至于一对多或者多对一的关系,理论上可能做到服务器更加充分的利用,但相比稳定性上的要求,这一点并不那么急迫需要去解决。所以在收藏夹的改造中,我们选择了同集群部署方案。

灰度方式

为了保证最大程度的稳定,这次改造并没有直接将 Velocity 代码完全去掉。应用集群中有将近 100 台服务器,我们以服务器为粒度,逐渐引入流量。也就是说,虽然所有的服务器上都跑着 Java + Node.js 的进程,但 Nginx 上有没有相应的转发规则,决定了获取这台服务器上请求宝贝收藏的请求是否会经过 Node.js 来处理。其中 Nginx 的配置为:

XHTML

1

2

3

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 来判断该服务器是否正常工作。如果请求 1s 后超时或者 HTTP Status Code 不为 200,则不将任何流量引入该服务器,避免线上问题。

这个请求的路径是 Nginx -> Java -> Nginx,这意味着,只要返回了 200,那这台服务器的 Nginx 与 Java 都处于健康状态。引入 Node.js 后,这个路径变成了 Nginx -> Node.js -> Java -> Node.js -> Nginx。相应的代码为:

XHTML

1

2

3

4

5

6

7

8

9

10

11

12

13

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 会使用 HTTP Agent 这个类来创建 HTTP 连接,这个类实现了 socket 连接池,每个主机+端口对的连接数默认上限是 5。同时 HTTP Agent 类发起的请求中默认带上了 Connection: Keep-Alive,导致已返回的连接没有及时释放,后面发起的请求只能排队。

最后的解决办法有三种:

  • 禁用 HTTP Agent,即在在调用 get 方法时额外添加参数 agent: false,最后的代码为:

XHTML

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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);

});

});

设置 http 对象的全局 socket 数量上限:

XHTML

1

http.globalAgent.maxSockets = 1000;

在请求返回的时候及时主动断开连接:

XHTML

1

2

3

4

http.get(options, function(res) {

}).on("socket", function (socket) {

socket.emit("agentRemove"); // 监听 socket 事件,在回调中派发 agentRemove 事件

});

实践上我们选择第一种方法。这么调整之后,健康检查就没有再发现其它问题了。

Node.js 与传统业务场景结合的实践才刚刚起步,仍然有大量值得深入挖掘的优化点。比比如,让 Java 应用彻底中心化后,是否可以考分集群部署,以提高服务器利用率。或者,发布与回滚的方式是否能更加灵活可控。等等细节,都值得再进一步研究。

原文地址:https://www.cnblogs.com/yuluoxingkong/p/9008372.html

时间: 2024-09-29 23:52:00

前后端分离的思考与实践(六)的相关文章

前后端分离的思考与实践(一)

原文出处: 淘宝UED - 常胤 也谈基于NodeJS的全栈式开发(基于NodeJS的前后端分离) 前言 为了解决传统Web开发模式带来的各种问题,我们进行了许多尝试,但由于前/后端的物理鸿沟,尝试的方案都大同小异.痛定思痛,今天我们重新思考了"前后端"的定义,引入前端同学都熟悉的NodeJS,试图探索一条全新的前后端分离模式. 随着不同终端(Pad/Mobile/PC)的兴起,对开发人员的要求越来越高,纯浏览器端的响应式已经不能满足用户体验的高要求,我们往往需要针对不同的终端开发定制

前后端分离实践(试探篇)

按照以往的开发模式,前端人员制作好静态页面交给与后端人员进行动态嵌套开发.迭代模式带来一系列问题,静态页面套成动态后,一些操作.业务.模版技术等动态页面已经跟静态页面有了一些差距.这时候前端人员改了一个BUG,或者叠加了一个功能,后端人员就必须向前端人员了解更改的范围,再叠加,由于都是人为过程,带来很多困扰,开发效率也很低. 当前后端页面以及逻辑没有合并之前,测试人员面临等待的问题,能否让测试与开发并行也是我们需要考虑的一个问题. 这时候我们想到了,能否以暴露服务的方式把java的接口暴露出来交

基于 koajs 的前后端分离实践

一.什么是前后端分离? 前后端分离的概念和优势在这里不再赘述,有兴趣的同学可以看各个前辈们一系列总结和讨论: 系列文章:前后端分离的思考与实践(1-6) slider: 淘宝前后端分离实践 知乎提问: 如何评价淘宝 UED 的 Midway Framework 前后端分离? Web 前后端分离的意义大吗? 尤其是<前后端分离的思考与实践>系列文章非常全面的阐述了前后端分离的意义和技术实现.如果不想看上面的文章,可以在脑海里留下这样一个轮廓就好: 本文主要阐述趣店团队基于Koajs的前后端分离实

移动端开发者眼中的前端开发流程变迁与前后端分离

写在最开始 移动端与前端的区别 前端开发的混沌时代 后端 MVC MVC 方案实现 MVC 的缺点与改进 前端只写 Demo HTML 模板 后端 MVC 架构总结 AJAX 与前端 MVC 前后端分离的缺点 双端 MVC 不统一 SEO 性能不够 集中 Or 分离 Nodejs 前后端分离的哲学 Nodejs 分层 实战应用 风险控制 总结 参考资料 写在最开始 这是一篇面向移动端开发者的科普性文章,从前端开发的最初流程开始,结合示范代码,讨论开发流程的演变过程,希望能覆盖一部分前端开发技术栈

《Spring Boot 入门及前后端分离项目实践》系列介绍

课程计划 课程地址点这里 本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 个部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 项目实践开发.Spring Boot 介绍.前后端分离.API 规范等内容旨在让读者更加熟悉 SpringBoot 及企业开发中需要注意的事项并具有使用 SpringBoot 技术进行基本功能开发的能力:这最后的项目实战为课程的主要部分,我会带着大家实际的开发一个前后端分离的 Spring Boot 实践项目,

关于大型网站技术演进的思考(十六)--网站静态化处理—前后端分离—下(8)

我第一次听说nodejs技术大概是在2009年年末,不过我真正认真在网络上进一步了解nodejs还是在2010年年中,当时对nodejs的认识和我现在对nodejs的认识有着天壤的区别,开始想了解nodejs我只是为了感慨谷歌公司开发的V8引擎居然如此强大,它不仅仅可以作为chrome浏览器的javascript内核运行平台,居然还能为服务端使用javascript语言作为平台,通过对nodejs的了解让我认识到chrome浏览器是如此的优秀,但是如此相对的是我并不认为javascript作为服

【转】关于大型网站技术演进的思考(十六)--网站静态化处理—前后端分离—下(8)

我第一次听说nodejs技术大概是在2009年年末,不过我真正认真在网络上进一步了解nodejs还是在2010年年中,当时对nodejs的认识和我现在对nodejs的认识有着天壤的区别,开始想了解nodejs我只是为了感慨谷歌公司开发的V8引擎居然如此强大,它不仅仅可以作为chrome浏览器的javascript内核运行平台,居然还能为服务端使用javascript语言作为平台,通过对nodejs的了解让我认识到chrome浏览器是如此的优秀,但是如此相对的是我并不认为javascript作为服

前后端分离实践【转】

前言 为了解决传统Web开发模式带来的各种问题,我们进行了许多尝试,但由于前/后端的物理鸿沟,尝试的方案都大同小异.痛定思痛,今天我们重新思考了“前后端”的定义,引入前端同学都熟悉的NodeJS,试图探索一条全新的前后端分离模式. 随着不同终端(Pad/Mobile/PC)的兴起,对开发人员的要求越来越高,纯浏览器端的响应式已经不能满足用户体验的高要求,我们往往需要针对不同的终端开发定制的版本.为了提升开发效率,前后端分离的需求越来越被重视,后端负责业务/数据接口,前端负责展现/交互逻辑,同一份

从.NET到Node.js谈前后端分离实践(by vczero)

一.最初的[无分离]实践 11年末的时候,用winForm开发程序,拖拖控件,点点按钮,连接数据库,做一些基本的管理系统:Java的JSP还能包揽一切,服务器端拼接模板,顶多使用servlet做一些业务逻辑,做到后端的MVC.那时候,带了一个学校的创新团队,做一些项目,但是基本上是一个人前端后一起搞,现在想想,真是[杂乱无章],后端MVC还好,倒是前端,基本上只能做一些简单效果,施展的空间不大,幸好,也基本能完成项目. 我称之为[无分离]实践,如果是像一个人搞,开发速度倒是挺快的. 前后端无分离