阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

Node.js 在携程的落地和最佳实践

  • 2019-12-21
  • 本文字数:4424 字

    阅读完需:约 15 分钟

Node.js在携程的落地和最佳实践

本篇主要介绍在携程,Node.js 技术栈是如何实现从 0 到 1 进行技术落地的,以及在不断磨合的过程中,总结出来的最佳实践。


在携程 Node.js 应用根据用户群,主要分两个方向:


DA(数据聚合服务)和 SSR(服务端渲染)是服务于外部用户的,目标是提升用户体验。当然,DA 和 SSR 同时也提升了开发效率,例如前端开发人员可以更加灵活的整合数据,例如同构给开发人员省去了大量重复的开发工作量;


公司桌面工具(例如内部 IM 等)是服务于内部员工的,一般是用 Electron,开发维护成本低,产品迭代快。

一、Node.js 工程化

基于上述三个场景,目前携程有一套 Node.js 的工程化方案。工程化的方案并不是一成不变的, 在任何阶段遇到了实际问题, 都会更新甚至推翻一些步骤,为的就是更好的服务于整个应用开发的生命周期。


工程化涵盖五大部分:开发、构建、测试、发布和运维。

1.1 开发

1)脚手架


有三个类型的脚手架:Web Application、DA Service 和 Desktop Tools。这三种类型的脚手架会服务于上述提到的三种场景。


这三种脚手架有共同点:标准化的 Docker 日志,预置统一的中间件。但同时他们也是有差异的,例如 Desktop Tools 和 Web Application 的应用模型不一样,Desktop 有 UI 层,那么 UI 层和应用层上的应用日志和用户行为如何关联,方便后续的排障;DA Service 需要将应用的健康状况周期性上报给治理中心、熔断机制等等,这些框架层面的差异,脚手架会集成进去,做业务开发同学可以不用关心这些基础设施的接入。


2)核心中间件


核心中间件主要是做基础设施的建设。目前有 20 多个中间件,主要的中间件如下:



图 1 核心中间件介绍


  • 存储服务,主要应用于长期的固化存储,例如静态资源。主要提供的是 Ceph 客户端。

  • 业务服务,主要应用于 DA 场景,提供 SOA Client 和 SOA Service。SOA Client 用来获取数据,需要重点关注的是读取性能和容错处理;SOAService 用来提供对外的聚合服务,需要重点关注的是稳定性和响应性能。

  • 监控服务,涵盖所有的应用,提供三个维度的监控:Tracing、Metrics 和 Logging。具体的介绍请参看下方”运维”部分。

  • 公共服务,主要包括配置中心,ABTest 的客户端、数据访问层等。

  • 缓存服务,主要用于配置信息的缓存、应用数据的缓存。提供 Redis 客户端和共享内存两个中间件。

1.2 构建

1)Docker 镜像


Node.js 的版本更新频率很快,每 6 个月会发布一个大版本的升级,期间会陆续出很多小版本。如果为每个版本都做一个镜像,会带来极高的开发和运维成本。基于更新频率,我们目前选取 2 个固定版本,在 Node.js 版本更替的时候,可以保证一个稳定的镜像。


2)安装依赖包


为了提升开发效率,在构建时安装依赖包需要保证速度快。如果中间件中用到一部分 C++模块,那么在安装时会做实时编译,这样会导致耗时长,甚至会因为环境问题编译失败。所以我们会将用到 C++模块的中间件做一下预编译,为 windows、linux 和 mac 这三个平台分别编译出 2 个固定版本的预编译包。


3)依赖包扫描


扫描的目的主要解决几个问题:


  • 应用中不同的包如果引用了同一个子包,但是子包的版本不一致,就会导致应用中装了多个版本同一个包,会引发 bug;

  • 中间件缺乏治理能力。通过扫描依赖包,能够做到中间件统一收口。一旦要升级,可以很快的通知到开发做快速升级。例如第三方依赖包有安全问题,可以在构建环节就提醒开发人员升级版本。

1.3 测试

目前测试环节包括单元测试、集成测试、压力测试和自动化测试。自动化测试主要针对 Service 和 UI 两方面测试。UI 自动化测试使用的是 Puppteer。每次代码更新,会走一遍自动化测试流程,保证代码质量。

1.4 发布

1)携程云和公有云


每个云的部署环境、网络、位置等差异,会带来应用访问差异,例如访问异常,网络延迟等。这些差异需要在基础设施层面抹平,避免放在应用逻辑层面处理。


2)应用一体化发布


一体化发布也可以理解为一键发布。一条发布指令包含了应用核心框架、静态资源、配置的同时发布,而不需要开发人员思考什么步骤需要发什么资源。这样不仅可以提升效率,还能有效的控制发布回滚。


3)私有 npm 包发布


私有包的发布和 GIT 做高度集成。原因是:第一可以通过 git 做快速的发布;第二有历史可查,方便的查看到每个版本发布的时间、人员;第三有权限控制,避免发生生产级别故障。

1.5 运维

运维是整个环节中最重要也是最容易被忽略的环节。一个应用上线只是开始,真正要关注的一定是运维指标。


1)日志监控


三种维度的监控:tracing、logging 和 metric。



图 2 三种维度的监控


图片来源于网络:http://peter.bourgon.org/blog/2017/02/21/metrics-tracing-and-logging.html


Tracing 提供的是整个请求过程中的数据,例如请求信息(头部、地址)、响应信息(状态码,响应体)、请求耗时、调用链等信息。


Logging 提供的是在请求处理过程中,每一个具体的事件埋点,这些埋点相对是分散的。可以是记录普通的日志,也可以是记录抛出的错误。


Metric 提供的是聚合数据。最大的特征是可聚合的,它展现的是一个时间跨度中的某个维度的指标。一般用来记录量化的指标,例如访问量、性能等数据。


2)应用排障


一般我们排查问题的时候,会先通过 Metric 的聚合指标发掘出异常,然后追踪到某一批有异常的 Tracing,可以查看到调用链、耗时等具体情况,也可以跟踪到某一个请求,查看里面的事件埋点。


也有其他方式的排障,例如下图中展示,可以在线直接通过一个特殊的地址访问到的一张火焰图,可以非常快速地去排障。当有用户说这个页面出现问题,打开这个页面排障,可以定位到底那个对应的地方出现问题。



图 3 火焰图

二、Node.js 最佳实践

2.1 部署模型


图 4 部署模型


Node.js 应用部署在 Docker 上,采用 Nginx+PM2 的模式。

2.2 问题一:多进程通信

多进程通信主要用于数据交换,最常见的有 2 种场景:


1)提供 SequenceId:在单台机器需要提供唯一的并且按时间序列排列的 ID。


2)提供远端配置信息:当获取远端配置信息时,需要考虑多进程的共享分发。



图 5 多进程通信 V1.0


在第一版本设计中,我们采用的是 IPC 机制进行多进程的通信。Master 作为一个中转站,当 Slave 有消息分发时,通知给 Master,再由 Master 分发给各 Slave,从而达到进程之间通信的效果。


但是上线之后发现,这样的机制会遇到几个问题:数据量必须控制好体积;数据的同步会有延迟;Master 必须时刻在线,一旦 Master 进程挂掉,就需要等待重启再重连。


基于这些问题,我们重新设计了第二个版本:



图 6 多进程通信 V2.0


在第二个版本的设计中,我们使用了共享内存(shared memory)。举一个场景为例,当需要获取某个配置的时候,先将这块内存锁定,尝试从内存中获取数据。如果判断数据存在且在有效期内,那么解锁并从内存中读取数据返回,否则从服务端获取数据,当服务端有数据返回时,将数据和有效期更新到内存中,解锁并从内存中读取数据返回。通过共享内存的机制,可以非常轻量级且高效的实现多进程之间的数据共享。

2.3 问题二:监控什么内容


图 7 监控指标


Nginx 会监控整个 Docker 上所有应用的情况:


  • CPU util:CPU 总的使用率。

  • CPU throttle count&time:CPU 被限制的次数和 CPU 使用率被限制的总时间。这两个指标的上升一般表示应用有 CPU 密集型操作,需要检查一下是否有大量的计算等操作。

  • Mem RSS used:这个指标上升一般显示应用内存泄漏的问题。

  • HTTP imcoming&outgoging:http request 的数量变化趋势。如果有错误响应或者超过了告警的阈值,则会在趋势图中显示。

  • Connection reset:这个指标如果上升,表示应用出现了大量的拒绝请求,例如是服务器的并发数超过了原本的承载量等原因。


Nginx 中监控的是整个 Docker 的情况,但是我们更需要的是监控应用的指标。应用一般采用 PM2 cluster –i max 模式启动,最大化利用 CPU。


  • Heartbeat(心跳信息)


每个 slave 一分钟发送一次 Heartbeat(心跳信息)给到 CAT 数据中心。一般来说,如果 Heartbeat 告警的话,需要立刻查看一下错误日志,是不是有异常错误导致进程已经退出了。


Heartbeat 主要包括 CPU、Memory、网络信息等。这些信息和上述提到的 Nginx 信息不是一个维度的。这个更细节的关注了应用的情况,而不是整个 Docker 的情况。如果需要分析应用细节的问题,是需要查看这里的 Heartbeat 信息。


  • 性能情况


一般来说,中间件会处理应用常规的性能日志记录。包括:


1)每一个响应的请求耗时(服务端逻辑处理耗时,不包括网络耗时)。


2)每一个 Transaction 的耗时。一个 Transaction 可以简单理解为一个有功能意义的代码片段。


3)跨应用调用的请求耗时。


  • 错误/告警信息


错误告警信息是应用中需要重点关注的,包括:


1)应用逻辑出错,例如处理 JSON 数据出错等。


2)HTTP 请求出错,会记录状态码、请求地址、返回内容。


3)应用中使用了不同版本的同一个包,会报一条告警信息通知开发工程师。


  • 详细数据日志


详细数据日志一般有开发工程师针对应用的逻辑埋点,而非中间件统一处理。这些日志会包括返回数据的记录,具体运行在哪一段 transaction 中。这些日志一般是故障发生时,用来复盘时的辅助手段。

2.4 问题三:全链路监控

全链路监控指的是端到端的监控,监控的是一系列的调用链情况。



图 8 Tracing 模型


在介绍全链路模型之前,首先介绍 Tracing 模型(图 8)。Tracing 模型是一个树状结构的模型。以一个场景为例,当用户发起一个请求,这个请求的处理中有三段逻辑(authentication、soa request 和 data aggregation)。


在整个请求体外层会有一个 Transaction#1,记录请求响应等信息。每一个逻辑段会对应一个 Transaction#2,Transaction#2 的父节点是 Transaction#1。Transaction#2 中可以有多个 Logging 信息,根据类型可以分为 Event/Error/Log,也可以包含 Metric 信息。这些 Logging 和 Metric 都有父节点,是 Transacation#2。按照这样的结构可以将一整个 request 的过程的监控信息记录下来。


要做全链路监控,就是需要将每个 request 和调用链做关联。


在过程中遇到的最核心的问题是,如何将上下文进行关联。第一个版本使用的是 domain 的模块,使用 domain 的 add api 将上下文信息记录下来,使用 run api 运行逻辑代码块。第二个正在测试中的版本是使用 async_hook 的模块,引入了生命周期的概念,通过 executionAsyncId 和 ttriggerAsyncId 可以追踪每个函数体。



图 9 页面请求模型


通过上图的页面请求模型可以将每个请求做关联,从而达到全链路监控的效果。

三、总结


  • Node.js 工程化需要结合业务,反复磨合;

  • 设计好运维指标,做好 Tracing/Logging/Metric 的结合;

  • 密切关注上线之后的监控指标,防止内存泄漏;

  • 发掘出 Node.js 技术栈的差异,有针对性的解决问题;


不要盲目相信同一个技术栈,合适才是最好的。


作者介绍


潘斐斐,Trip.com 高级研发经理。2008 年加入携程,目前工作内容为 Node.js 框架平台整体构建、产品性能优化和创新型项目研发。本文来自在 2019 携程技术峰会上的分享。


本文转载自公众号携程技术中心(ID:ctriptech)。


原文链接


https://mp.weixin.qq.com/s/afJ0RFL7FSG1S2vlESdCEQ


2019-12-21 10:003290

评论 1 条评论

发布
用户头像
e
2019-12-23 10:19
回复
没有更多了
发现更多内容

11.11数据可视化大屏设计揭秘

京东科技开发者

大数据 AI 数据分析 数据可视化 交互设计

美团架构师总结整理的这份GitHub标星150K+的神仙笔记,我花了两个月肝完成功面进了阿里定级P7,现在分享出来希望大家也能有所提升!

Java架构之路

Java 程序员 架构 面试 编程语言

GitHub 标星 1.3k+,一款超赞的用于字符串处理的 Java 8 库,附带源码分析

沉默王二

Java GitHub 字符串

甲方日常 58

句子

工作 随笔杂谈 日常

高德最佳实践:Serverless 规模化落地有哪些价值?

阿里巴巴云原生

阿里云 Serverless 云原生

继linux命令之后,我又给你们整理了网络命令归纳,快给我来收藏

北游学Java

Linux 网络协议 网络 网络层

前嗅教你大数据:常见的网站反爬策略与解决方案

前嗅大数据

大数据 数据采集 代理IP 网站反爬 反爬策略

CPU虚拟化系列文章1——x86架构CPU虚拟化

华章IT

云计算 Linux cpu 操作系统 虚拟化

技术实践丨基于MindSpore框架Yolov3-darknet模型的篮球动作检测体验

华为云开发者联盟

AI 华为云 modelarts

索引为什么能提供查询性能...

小林coding

MySQL 索引 数据结构与算法 B+树

Week1 命题作业

J

极客大学架构师训练营

一文带你了解两种Transformer文字识别方法

华为云开发者联盟

人工智能 AI 文字识别

支付宝阿牛整合Netty+Redis+ZooKeeper「终极版」高并发手册

Java架构追梦

Java redis zookeeper 面试 Netty

没想到我费劲心力学的kafka,还不如阿里大佬整理的这份学习手册,真的是差距啊

小Q

Java kafka 学习 架构 面试

分享一份大佬的MySQL数据库设计规范,值得收藏

小Q

学习 架构 面试 JVM 多线程

接口测试和性能测试的区别

测试人生路

软件测试 性能测试 接口测试

Linux 服务器开发学习路线总结(配图 c/c++ )后台开发、Golang后台开发、后端技术栈

Linux服务器开发

Linux 后台开发 后端 Linux服务器 Go 语言

感恩,改变世界的开发者们!

京东科技开发者

开发者 程序人生

广电总局严打劣迹主播:净化行业环境迫在眉睫

石头IT视角

《迅雷链精品课》第八课:迅雷链多链结构

迅雷链

区块链

通过python基于netconf协议获取网络中网元的配置数据,助力企业网络控制自动化轻松实现!

华为云开发者联盟

通信 企业 网络自动化

收藏 | 阿里程序员常用的 15 款开发者工具(2020 版)

阿里巴巴云原生

阿里云 程序员 开发者 云原生 Java 25 周年

LeetCode题解:17. 电话号码的字母组合,队列,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

秋招offer收割机——后台服务器开发方向(专业学习路线图总结)

程序员小灰

c++ Linux 后台开发 架构师 服务器端开发

【领福利啦】广受欢迎的人工智能实战课程+“智能音箱”教程免费领!

小冬

人工智能 技术 福利 项目实战 智能音箱

如何保障企业数据资产的全生命周期安全?看这篇就够了

华为云开发者联盟

数据 数据资产 数据安全

红外遥控接收发射原理及ESP8266实现

IoT云工坊

人工智能 物联网 esp8266 红外遥控 pwm

基于 GraphQL 的信息聚合网关的实现与展望

QiLab

高并发系统设计 graphql

监控之美——Prometheus云原生监控

华章IT

运维 云原生 监控 Prometheus

最详细的Linux TCP/IP 协议栈源码分析

linux大本营

Linux 后台开发 网络编程 C/C++ TCP/IP

谈谈持续集成、持续交付和持续部署三者究竟是什么,有何联系和区别呢!

ShenDu_Linux

Linux 持续集成 架构师 持续交付 持续部署

Node.js在携程的落地和最佳实践_大前端_潘斐斐_InfoQ精选文章