作者 | 禹立彬
编辑 | 薛梁
前端 Node.js 的使用场景大多集中在前端工具上,当前的前端主要把它定位为辅助。苏宁易购使用 Node.js 作为前后端分离的主要手段,经历了从技术引进到全面开花,从边缘功能到核心业务,从纷乱到稳定的过程。同时 Node.js 作为新引入的技术,与公司原有架构融合衔接面临着怎样的挑战?以下是苏宁技术总监 禹立彬老师在 7 月深圳 ArchSummit 全球架构师峰会上的演讲整理。(点击“阅读原文”查看 12 月 7 日北京 ArchSummit 架构师峰会日程)
在苏宁引入 Node.js 之前,苏宁已经有了成熟的技术架构。
苏宁的技术架构,由苏宁云、基础支撑、后台、中台和前台组成。苏宁云主要为业务开发提供云服务。基础支撑,包括数据连接协议、防火墙、日志、中间件、短信等。在苏宁云和基础支撑之上,业务开发分为前中后台。而 Web 前端,主要集中在前台上。包含 PC 端、移动 WAP 端等。
Node.js 的应用非常广泛,在不同的公司,可以用作微服务,也可以用来提供 API,苏宁引入 Node.js,最主要的,是用 Node.js 做中间层,当做一个 Web 渲染器,渲染页面,来实现前后端分离。
Web 前台系统
在前台系统里,以前的开发模式,完全是 Java 技术栈。Java 系统,分为 Java Service 服务器,和 Java Web 服务器,Java WEB 服务器读取 Java Service 服务器提供的接口,通过 FTL 模板来渲染页面。
引入了 Node.js 以后,苏宁研发团队的目标是使用 Node.js 替代 Java Web 服务器的渲染位置,使用 Node 模板,去替换 Java 模板,去除了模板文件谁写这样的模糊地带,让后端的 Java 工程师,只写 JSON 服务,实现前后端分离。
在应用前后端分离后,显式的获得了一些好处,Node.js 系统的迭代速度优势明显强于 Java Web,包括由于 Node.js 的轻量,带来的快速开发快速迭代,以及减少了前后端沟通上的“联调”时间成本,加快了项目的开发速度。同时,减少老项目里 Java 后台工程师写页面导致的一些 BUG,提高了代码质量。
由于以上的这些优点,现在苏宁易购的 Node.js 项目越来越多,逐渐深入到核心业务。最早期,苏宁只是在用户体验收集这样的边缘页面使用 Node.js,现在已经在海外购,小程序,大聚惠,我的易购,香港站,购物车等业务中都广泛的使用了 Node.js,这样就不可避免的直面更多的技术挑战。
Node.js 如何融入已有技术架构
当只是作为边缘业务时,Node.js 项目尚可以通过一些临时方案,或者引入试点,来逃避,但是深入核心业务后,Node.js 项目很快,就会被纳入总的技术架构里。
这是一张比较简单的 Node.js 应用部署图。一个 Node.js 应用被访问时,会使用公共的负载均衡,使用应用防火墙,当达到 Node 服务器时,要使用物理机和虚拟机,Node 服务器要访问 Java Service 服务器也需要连接协议。
最简单的是 IaaS,在这个层级上,Node 可以完全可以复用苏宁云成熟的网络,存储,物理机,虚拟机等资源。
到了虚拟机这个层级,在这幅 Node.js 服务器图上,可以看到单台 Node 服务器,有一个 Nginx,来做访问的 accessLog 和做反向代理到本机的 Node.js 应用端口,这台机器上同时安装了 PM2,来启动多个 Node 应用,对应不同的端口,来提供对外服务。
在 PaaS 这个层级上,苏宁也尽量沿用了公司已有的技术资源。比如在操作系统方面,使用了 RedHat Linux,尽量向已有技术架构靠拢。
在服务器的 Node.js 版本上,在去年年初的版本是 Node 6.9,去年年底已经将 Node 版本升级到 Node 8 了,研发团队坚持使用 LTS 版本的 Node.js。到 Node 有大版本更新时,同时更新 PaaS 平台的 Node 版本。
在 Nginx 上,选择的也是已有的通用 Nginx 版本,Node 服务器对 Nginx 版本要求不严格,Nginx 监听多域名的 80 端口后,反向代理到 Node 端口就好。
Redis 的情况要麻烦一些,Node.js 由于进程的原因,遇到 Session 这样需要多进程或者多服务器直接共享数据时,就必须借助 Redis。很显然,Java 体系内,并没有对应的 Node 版本的 Redis 客户端,于是苏宁自己编写了一个基于 ioredis 的 Redis 客户端,来满足需求。
在 DB 上,苏宁则遵循总的技术架构要求,Node 服务器不直连 DB,要获取数据时,永远是连接 Java 服务。
监控报警
解决了服务器环境后,Node 也要接入日志服务和报警系统。通过配置 Nginx 日志格式和 PM2 的日志插件 pm2-logrotate 来将日志格式符合总技术架构的日志平台要求,并在日志平台上配置 4XX 和 5XX 报警,并且针对 Node 本身的一些特色,编写 PM2 插件,监控 Node 进程异常,并发送异常到苏宁内部的即时通信团建上。
CI/CD 发布系统
服务器好了,代码编写好了,也需要发布。利用公司的统一发布平台,在平台上新建了 Node.js 标准发布,统一了 Node 代码包打包方案,统一了代码部署目录,统一从内部私库安装 NPM 包,统一了应用重启的方法。
除此之外,为了满足公司的总技术架构要求,苏宁研发团队还编写了基于 Node 的调用链监控组件,可以适配 ESB/RSF 通信协议的客户端组件,以及适合 Varnish 下的 KOA,EXPRESS 中间件。
技术挑战
解决了技术架构要求方面的问题,Node 可以正规军上岗了,核心业务又会带来更高的技术要求。
以大聚惠页面为例,大聚惠是苏宁的一项营销业务,很多优惠活动,是通过大聚惠的名义放出的,因此业务非常重要。又因为运营需要,总是在凌晨 0 点,货品上新,要求此时页面不能有缓存,而且由于电商业务的特殊性,会遇到 618,818,双十一这种半夜抢购的情况,因此全靠服务器硬扛流量洪峰,这就对应用性能,提出了很高的要求。
这个项目上线的时候,恰好是升级了 Node 8,研发团队把框架从 express 转换为 KOA,写了一样的代码,压测时,4C4G 单机才 40TPS,而同样的 Java 系统,单机约为 200TPS,性能差距明显。
并发性能优化之路
首先考虑是缓存问题,经过排查,在 KOA 下,并不会默认开启,模板缓存,导致每次总是去硬盘读模板,当然快不了。果断优化,TPS 上升为 120TPS,依然差距明显。Express 框架中会默认开启模板缓存,而默认的 KOA2 并不会开启,首先开启模板缓存性能提升明显。
另外,通过 CPU-PROFILER 排查,发现路由消耗的时间挺多,匹配字符串路由,和匹配正则路由,时间消耗差距明显,于是将路由排序,优先选择字符串路由,将 TPS 推向 140TPS。
再查,为了减少并发,除渲染模板外,Node 还在业务里,合并了静态资源地址,并且中间件加载的策略也可以优化,去除掉一些可以不用的中间件。比如只用 EJS 模板,那么就去除其他模板引擎的支持,通过这些优化,讲 TPS 提升到了 180TPS。
TPS 没有提升后,再排查,发现 include 函数执行时间很长,发现 ejs 源码处理 include 时,总是去硬盘里查找是否有被 include 的模板文件,而这个页面是多人开发,include 使用非常频繁,再进一步优化,终于达到了 220TPS。
不低于 Java 的渲染性能
最后,获得了不低于 Java 的渲染性能。很多文章博客对待 Node 渲染页面时会两级分化,一极认为 Node 不适合做这种 CPU 密集型操作java配置,另一极就是不断宣扬 Node 性能强劲,但是在苏宁实际的应用中,从来没想过超过 Java 多少倍的性能,事实上也是。Node 的基础性能略高于或者持平于老的 Java FTL 渲染器。
安全与稳定
除了性能,安全与稳定也是重点需要的环节。针对 Node.js 来说,客观的说,安全文档方面是不如 Java 等语言的。
一方面是最后后来者,追赶前辈需要时间,另一方面也是 Node.js 的固有问题。Node.js 是单线程的,代码报错会导致进程退出,在 Node 生态早期,会直接导致 Node 访问挂掉;
第二个,JS 是弱类型语言,很多人认为弱类型的语言写的没有安全感,没有代码检查;
第三个,NPM 很好,开源社区很强大,组件很多很方便,但是对于企业用户来说,都抵不过一个 left-pad 事件,公网的 NPM 包也会良莠不齐,如果出现了安全漏洞,影响就会非常大。
问题如何解决
NPM 包策略。苏宁使用公司的私有 NPM 仓库来安装 NPM 包,避免外网扰动,导致无法安装问题。在核心业务中,限制使用不流行的 NPM 包,减少风险。在 package.json 里的包版本,使用确定的版本,不用符号,减少包升级导致的 bug。对于自己开发的 NPM 包,严格进行单元测试及安全测试,进一步的减少风险。
使用 PM2。针对 Node 进程挂掉的问题,苏宁使用了留下的 PM2,来保证 Node 进程的存活。当 Node 进程挂掉时,PM2 会重启他们。感谢 PM2,通过它,也实现了发布的无缝重启,保证了平滑升级。
Node.js 系统的相对安全
安全上,苏宁强制了所有的 Node 系统加入应用防火墙 WAF,使用基于 KOA 的安全中间件 XSS,尽量使用精确匹配的路由,减少注入。并在上线前,做完全的安全测试,实现 Node 系统的相对安全。
前端团队的挑战
另一方面,由于引入了 Node,前端工程师对 Node 相关的知识了解较少,也会犯一些低级错误,技术挑战也是非常大的,知识要求被增加了很多。
为了解决这个问题,苏宁成立了专门的前端架构组,为各业务团队保驾护航。在发布,配置,安全监测等各个方面帮助业务开发团队。
全栈技能提升计划
并在工作中,加强 Node 技能培训。梳理出容易犯的低级错误,比如 promise 不写 catch,某个条件分支里,不写请求返回,通过宣讲的方式,提高代码质量,并组织代码评审等活动,进一步的提升技术能力。
Node.js 的影响
可以说,进入了核心业务,前端团队遇到的挑战是越来越大的,同时,Node 的推进也带来了一些正面负面的影响,时间有限,不做太多的讲解,仅举几个方面。
第一个方面,项目更敏捷了,Node.js 发布不涉及后台服务,即使发布出了小问题,也可以快速再次发布和回滚,因为 Node.js 系统其实是可以 24 小时发布,对业务支撑显然更迅速敏捷。运营商务。都显然的欢迎 Node。
同时,Node 的引入,也对前端团队带来了影响。Node 的引入带来了前端工作量的增加,需要更多的前端工程师投入。
另一方面,也显著的提高了团队的技术活力,在团队内部刮起了全栈风,技术更活跃,解决问题的方案也更多了。
最后还有一些小型的负面影响,定位 bug 时,时间有所增加。需要查 Node 的问题,还是 Java 的问题;另一方面,访问性能因为 HTTP 的问题,略微增加了几毫秒。当然这对于前面的好处来讲都是可以接受的。
嘉宾介绍
禹立彬,苏宁技术总监,十年 Web 前端开发经历,中国最早一批前端开发者,历任西祠胡同前端负责人java配置,途牛旅游网前端架构师等职务。现任苏宁消费者平台研发中心前端技术总监,负责苏宁易购网站前端领域的技术管理工作。在基于 Node.js 的前后端分离,ReactNative/Weex 开发上有丰富的技术实践经历。
12 月 7 日北京 ArchSummit 全球架构师峰会上,来自美团、百度、阿里、快手的讲师齐聚一堂,共同分享“打造 Native 体验 Hybrid App 实践”、“定制统一可维护的前端架构”、“10 年双十一前端关键技术”和“同构 Web App 的另一种探索”的分享。
会议开幕倒数第 10 天,购票联系票务灰灰 17326843116