基于NodeJS的前后端分离的思考与实践(六)Nginx + Node.js + Java 的软件栈部署实践

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

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

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

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

按照我们在前后端分离的思考与实践(二)- 基于前后端分离的模版探索一文中的思路,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 的配置为:

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。相应的代码为:

  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,最后的代码为:

  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 数量上限:

 http.globalAgent.maxSockets = 1000;

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

http.get(options, function(res) {
  }).on("socket", function (socket) {
  socket.emit("agentRemove"); // 监听 socket 事件,在回调中派发 agentRemove 事件
});

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

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

(0)

相关推荐

  • Java编程用栈来求解汉诺塔问题的代码实例(非递归)

    [题目] 汉诺塔问题比较经典,这里修改一下游戏规则:现在限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间.求当塔有N层的时候,打印最优移动过程和最优移动总步数. [解答] 上一篇用的是递归的方法解决这个问题,这里我们用栈来模拟汉诺塔的三个塔,也就是不用递归的方法 原理是这样的:修改后的汉诺塔问题不能让任何塔从左直接移动到右,也不能从右直接移动到左,而是要经过中间,也就是说,实际上能做的动作,只有四个:左->中,中->左,中->右,右->中 用栈

  • java使用泛型实现栈结构示例分享

    思路分析:既然是用泛型实现栈结构,那就不能用JDK自带的stack包了,需要自己定义一个栈结构,比如LinkedList. 代码如下: Stack.java: 复制代码 代码如下: package cn.edu.xidian.crytoll;import java.util.LinkedList; public class Stack<T> { private LinkedList<T> container = new LinkedList<T>(); public v

  • Java实现栈和队列面试题

    面试的时候,栈和队列经常会成对出现来考察.本文包含栈和队列的如下考试内容: (1)栈的创建 (2)队列的创建 (3)两个栈实现一个队列 (4)两个队列实现一个栈 (5)设计含最小函数min()的栈,要求min.push.pop.的时间复杂度都是O(1) (6)判断栈的push和pop序列是否一致 1.栈的创建: 我们接下来通过链表的形式来创建栈,方便扩充. 代码实现: public class Stack { public Node head; public Node current; //方法

  • 输出java进程的jstack信息示例分享 通过线程堆栈信息分析java线程

    复制代码 代码如下: #!/bin/shpro_name=java #process namekeys=`ps -ef |grep "$pro_name" |grep -v "grep" | awk '{print $2}'`nowdate=`date +%Y%m%d%H%M%S` jstackpath="/usr/java/jdk1.6.0_07/bin/jstack"cpulogpath="/home/" for key

  • 基于NodeJS的前后端分离的思考与实践(六)Nginx + Node.js + Java 的软件栈部署实践

    淘宝网线上应用的传统软件栈结构为 Nginx + Velocity + Java,即: 在这个体系中,Nginx 将请求转发给 Java 应用,后者处理完事务,再将数据用 Velocity 模板渲染成最终的页面. 引入 Node.js 之后,我们势必要面临以下几个问题: 技术栈的拓扑结构该如何设计,部署方式该如何选择,才算是科学合理?项目完成后,该如何切分流量,对运维来说才算是方便快捷?遇到线上的问题,如何最快地解除险情,避免更大的损失?如何确保应用的健康情况,在负载均衡调度的层面加以管理?承系

  • 基于NodeJS的前后端分离的思考与实践(五)多终端适配

    前言 近年来各站点基于 Web 的多终端适配进行得如火如荼,行业间也发展出依赖各种技术的解决方案.有如基于浏览器原生 CSS3 Media Query 的响应式设计.基于云端智能重排的「云适配」方案等.本文则主要探讨在前后端分离基础下的多终端适配方案. 关于前后端分离 关于前后端分离的方案,在<基于NodeJS的前后端分离的思考与实践(一)>中有非常清晰的解释.我们在服务端接口和浏览器之间引入 NodeJS 作为渲染层,因为 NodeJS 层彻底与数据抽离,同时无需关心大量的业务逻辑,所以十分

  • 基于NodeJS的前后端分离的思考与实践(一)全栈式开发

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

  • 基于NodeJS的前后端分离的思考与实践(二)模版探索

    前言 在做前后端分离时,第一个关注到的问题就是 渲染,也就是 View 这个层面的工作. 在传统的开发模式中,浏览器端与服务器端是由不同的前后端两个团队开发,但是模版却又在这两者中间的模糊地带.因此模版上面总不可避免的越来越多复杂逻辑,最终难以维护. 而我们选择了NodeJS,作为一个前后端的中间层.试图藉由NodeJS,来疏理 View 层面的工作. 使得前后端分工更明确,让专案更好维护,达成更好的用户体验. 本文 渲染这块工作,对于前端开发者的日常工作来说,佔了非常大的比例,也是最容易与后端

  • 基于NodeJS的前后端分离的思考与实践(三)轻量级的接口配置建模框架

    前言 使用Node做前后端分离的开发模式带来了一些性能及开发流程上的优势, 但同时也面临不少挑战.在淘宝复杂的业务及技术架构下,后端必须依赖Java搭建基础架构,同时提供相关业务接口供前端使用.Node在整个环境中最重要的工作之一就是代理这些业务接口,以方便前端(Node端和浏览器端)整合数据做页面渲染.如何做好代理工作,使得前后端开发分离之后,仍然可以在流程上无缝衔接,是我们需要考虑的问题.本文将就该问题做相关探讨,并提出解决方案. 由于后端提供的接口方式可能多种多样,同时开发人员在编写Nod

  • 基于NodeJS的前后端分离的思考与实践(四)安全问题解决方案

    前言 在前后端分离的开发模式中,从开发的角色和职能上来讲,一个最明显的变化就是:以往传统中,只负责浏览器环境中开发的前端同学,需要涉猎到服务端层面,编写服务端代码.而摆在面前的一个基础性问题就是如何保障Web安全? 本文就在前后端分离模式的架构下,针对前端在Web开发中,所遇到的安全问题以及应对措施和注意事项,并提出解决方案. 跨站脚本攻击(XSS)的防御 问题及解决思路 跨站脚本攻击(XSS,Cross-site scripting)是最常见和基本的攻击Web网站的方法.攻击者可以在网页上发布

  • 基于Spring Security前后端分离的权限控制系统问题

    目录 1. 引入maven依赖 2. 建表并生成相应的实体类 3. 自定义UserDetails 4. 自定义各种Handler 5. Token处理 6. 访问控制 7. 配置WebSecurity 8. 看效果 9. 补充:手机号+短信验证码登录 前后端分离的项目,前端有菜单(menu),后端有API(backendApi),一个menu对应的页面有N个API接口来支持,本文介绍如何基于Spring Security前后端分离的权限控制系统问题. 话不多说,入正题.一个简单的权限控制系统需要

  • Nginx实现前后端分离

    #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream;

  • Springboot与vue实例讲解实现前后端分离的人事管理系统

    目录 一,项目简介 二,环境介绍 三,系统展示 四,核心代码展示 五,项目总结 一,项目简介 系统是前后端分离的项目,直接启动Springboot应用程序类后,再启动前端工程访问即可.主要实现了企业的人事管理功能,主要包含员工管理.薪资管理.职位管理.权限管理.网盘文件分享管理等模块. 系统亮点:使用REDIS进行数据缓存,优化查询性能:使用分布式文件系统进行文件存储服务:基于Springboot+vue实现前后端分离开发 二,环境介绍 语言环境:Java: jdk1.8 数据库:Mysql:

  • nodeJS(express4.x)+vue(vue-cli)构建前后端分离实例(带跨域)

    准备工作: 1.安装nodejs ---还用我教了? 2.安装依赖包express4.x  点这里>>>nodeJS搭建本地服务器 3.安装vue-cli脚手架 点这里>>>vue-cli构建vue项目 这里强调一下,express是后端服务器,它是一个独立的服务器,vue启动的是前端服务器,vue-cli中已经集成了一个小型的express,这两个服务器是分开放的,但是它们都是基于nodejs的. nodeJS部分:这里我已经认为你搭建好了express服务器,并且能

随机推荐