Teambition 后端API服务性能优化总结

2023-05-31 0 393

极少归纳 Node.Js 有关的增容和强化基本功,刚好前段时间在做操控性Listary并获得了极好的效用,趁此机会归纳和撷取呵呵,责任编辑首秀于 richardweis blog,同时收录于Node.js 应用领域机械故障摸查指南

(指南收录于了 Node.js 常用的操控性困局及应用领域机械故障、合作开发基本功等该文事例,帮助各阶层的 Node.js 合作开发人员应付合作开发和线上布署中碰到的问题)

Teambition | Team Collaboration Solutionsteambition.com/

Teambition 是这款动态协作的虚拟化应用领域,有关的销售业务USB也是处于读多写少的情景,因而主要的操控性困局也是出那时USB平均反应速度慢、不稳定等方面,此次我将结合 Teambition 实际销售业务从以下几个层次来回顾归纳强化的整个过程:

他们的USB是怎样减慢的? 慢在这儿?USB操控性和mammalian操作的博戈达?通过 Chrome:instpact 进行代码 Debug 及操控性分析?Node.Js 事件循环式数学模型及Renderscript为依据带来的优势与不足,怎样防止及解决?

一、不得已强化的的境地

何为不得已强化的境地?就是老板娘(CEO)说 “我觉得他们应用领域那时非常快,我希望达到秒开的效用” 和后端技师他们自己的志向: USB那么慢,于孟子曰都平心而论,多憋屈啊,该死的分布式系统、高操控性、高mammalian呢?

说了那么多他们的USB慢,是怎样慢呢?举两个特殊的情景,也是两个比较重要的

Teambition 后端API服务性能优化总结
Teambition 后端API服务性能优化总结

可以明显的看见有部分USB费时已经不能用微秒级来来衡量了,那个自然环境是 Teambition 内部的两个 GA 自然环境,由于所有应用领域那时均布署并运转在 k8s 中,当前那个自然环境为了试验仅开启了两个 pod 并运转一般而言 Teambition Server 示例保证多个允诺均允诺至同两个 pod 同一个民主化中。

作为现代应用领域,让使用者多等候几秒钟可能也会降低使用者新体验甚至感到恐惧,因而操控性强化也刻不容缓…

二、USB慢,慢在这儿为什么那么慢?

我曾经无数的问过自己,USB为什么那么慢,是数据太多了吗?是计算太多了吗?难不成,是数据库?语言的问题?

带着那么多的疑问很久,每次我都努力安慰自己,算了,还是先把销售业务写完吧…..好不开玩笑,其实这是我一种很久以来的一个心结,我尝试查过,但是一直集中不了零碎的时间导致迟迟没有结果,这次终于狠下心来刮骨疗毒,Teambition 一定要更快更优秀!

USB慢,慢在这儿为什么就可以那么慢,对USB本身可能出现的情况进行分析:

a. 数据量不多就几百条,费时也不是在传输过程中 。❌

b. 虽然就几百条数据,但是每条数据的逻辑计算和补全可能会很花时间。✅

c. 早就觉得框架中间件的使用方法有问题了。可能问题在这里。✅

d. 也有可能是数据库慢啊。❌

以上是我的四个猜测,应该大多USB操控性应该也离不开这几个点,这里我已经表明了最后的结果,接下来他们就一一结合该文开头提出的几个点进行分析、处理。

三、解决问题的过程总是那么令人兴奋

1、 对于框架中间件使用的问题

Teambition 服务端是多种语言来进行支撑和拓展的,包括 Node.Js、Go、Java,其中又以 Node 应用领域最多、应用领域最广,它支撑起了 Teambition 各产品线的搜索服务 (teambition-soa-search)、通知服务(tb-notification)、数据转换应用领域 (TIS)、应用领域中心和支付应用领域等等,而 GO 则作为 Teambition 后端 SOA 架构的最底层 (中台) 应用领域的首选语言。

Teambition 主站服务是两个大型的(十几万行标识符) 的 Node.js 单体应用领域,框架使用的是 express,框架数学模型不用我多介绍,中间件是他们标识符分层、结构模块化的核心组件,由此也衍生了大大小小大弊端,其中最为重要的则是中间件的串行使用:

// 举两个简单的 Demo, 他们为每两个 API、中间件做了一层封装,使得每两个 API、路由、中间件能够独立在各自的文件维护、并且不重复 // 其中最为核心的问题是他们将许多后置销售业务放到中间件中来进行执行,比如 arr = [ mdl.toJson(), mdl.fillCreator(), mdl.fillExecutor(), mdl.fill…., mdl.emit()…, mdl.socket()…. ….. ] // 是的,他们通过中间件的形式来进行外键的补全、消息同步、事件触发、和后续的一些销售业务同步处理 (req, res, next) => { //do… next() } // 中间件传过来的数据始终是同一个份,但是他们对这一份数据进行了多次重复计算、串行阻塞处理

此为问题一。

2、 数据本身的读取、处理暴露的问题

除了中间件使用的问题,剩下的就是数据处理的问题,首先说说数据的查询。

Teambition 数据库主要使用的是 MongoDB,所以从早期到那时他们一直使用的是 mongoose ODM,在一开始问题的查找中,我发现停留在 DB 查询的费时特别久,大概也占到了总费时的一半,于是我会猜测是否存在 DB 的慢查询,但是在运维同事的协查中并没有在 oplog 中找到有关的一丁点慢查询,那么问题来了,有可能与 ODM 有关,带着猜疑而又激动的心情,果断 Debug 起来,这里顺便撷取呵呵使用 Chrome inspect 的增容方法,当然本质还是 Node 本身支持的inspector Debug 方案的封装,同时也安利呵呵增容工具 ndb:

a. 开启应用领域时携带 –inspect 参数,terminal 中应该会有如下提示:

// 应用领域已经通过 websocket 的方式连接至如下地址 Debugger listening on ws://127.0.0.1:9229/3d9f739a-127f-44df-97fb-c7b179350fda For help, see: https://nodejs.org/en/docs/inspector

b. 打开 Chrome 浏览器访问: chrome://inspect/#devices

Teambition 后端API服务性能优化总结
Teambition 后端API服务性能优化总结

在这里,他们主要查看他们的 CPU 费时时间,点击开始,访问对应的USB,生成一段时间的 CPU 利用率的时间线快照,大概如下:

Teambition 后端API服务性能优化总结

c. 这里我就不做过多的演示和介绍,相此方法,再联系之前执行的函数:

Teambition 后端API服务性能优化总结

他们可以清晰地发现,在这之前做了 DB 操作,并且将 mongoDB 的 BSON 对象反序列化解析出来,因而猜测后面应该是 mongoose 做了一些额外的操作,这里大概已经能猜到大概了,应该是做了 数据的补全,因为在 mongoose Model Query 的操作中,如果不加 lean() 方法的话,mongoose 拿到结果会将数据包装成为他们熟知的 mongoose Document ,这也是他们用 mongoose 原因之一,可以轻松为每两个 field 定义默认值、method、和 validator等等,但前提是这是两个 Document,而源码也是做了如此操作:

/*! * Given a model and an array of docs, hydrates all the docs to be instances * of the model. Used to initialize docs returned from the db from `find()` * * @param {Model} model * @param {Array} docs * @param {Object} fields the projection used, including `select` from schemas * @param {Object} userProvidedFields the user-specified projection * @param {Object} opts * @param {Array} [opts.populated] * @param {ClientSession} [opts.session] * @param {Function} callback */ function completeMany(model, docs, fields, userProvidedFields, opts, callback) { const arr = []; let count = docs.length; const len = count; let error = null; function init(_error) { if (_error != null) { error = error || _error; } if (error != null) { count || process.nextTick(() => callback(error)); return; } count || process.nextTick(() => callback(error, arr)); } for (let i = 0; i < len; ++i) { arr[i] = helpers.createModel(model, docs[i], fields, userProvidedFields); try { arr[i].init(docs[i], opts, init); } catch (error) { init(error); } arr[i].$session(opts.session); } }

而在 Teambition 的大部分USB中,对于数据的一条数据包装上对应的 default value、method等等,其中包含其自定义的默认方法和他们使用时候加上的各类数据,形成两个很大的 model 对象,对 CPU 的压力可想而知

当查询使用了 lean() 以后,则不会调用 completeMany()

_this.model.populate(docs, pop, function(err, docs) { if (err) return callback(err); return mongooseOptions.lean ? callback(null, docs) : completeMany(_this.model, docs, fields, userProvidedFields, completeManyOptions, callback); });

强化后,DB 查询速度提高了至少 1.5 倍

3、 DB、中间件大强化过后,USB响应不稳定

在前两步的强化中,response time 已经降为了微秒级 600~700 ms,已经获得了很大的进步了,但是回到他们的销售业务情景,刷新整个页面在USBmammalian允诺过来的时候,USB反应速度同样为提升至 2~3 s,这让我百思不得其解,单USB的 QPS 操控性已经很明显了并且稳定,为何实际mammalian的情景下反应速度明显减慢了。

有了之前的经验,很快定位到了问题所在,在第一张他们的API允诺列表中,还有两个特别费时的USB—「计数」,仔细摸查之后,同样发现该USB计数的计数方式存在不合理性:查询到所有的数据,然后进行分类,在此过程中,completeMany() 同样占用了CPU大量的时间。

到这里一切都能够解释的通了,因为 Node.Js 单线程的原因,即便同时能处理多个 I/O,也可能因为 CPU 没有空闲时间导致多个允诺 hang up,因为前两个「计数」USB占用的 CPU 过高,导致主线程没有空闲的时间去处理别的计算,导致后续依赖大量计算操作的USB hang up,可以看到两个很明显的现象就是: 「计数」USB响应完毕之后,waiting 的USB也就跟着返回了—— Node 说这锅我可不背,所以针对计数USB他们做了一定的强化: 主要是 DB 操作和算法上的强化,这里就不细说了,压测之后,当前情景下整体页面加载效率大概提升了 8-10 倍,算是两个相当惊人的强化了,当然也和他们自己使用不当有关,也算是两个坑吧,目前为止,填坑完毕,看看效用:

Teambition 后端API服务性能优化总结

之前的种种强化,伴随着问题的解决,在使用者端有了明显的表现,当然,这仅仅是肉眼可以感知的提升,实际在 CPU 占用、内存消耗上面也有了很大的强化和进步,Teambition 那时使用 Node 出现的问题,主要还是在计算上,在面对大量的计算的时候,还是显得比较吃力,因为两个 CPU 同时只能处理两个线程的任务,而两个线程中的任务也只能在同两个 CPU 上进行,不管能够支撑多大的 I/O,也禁不住线程的长时间阻塞,无法利用多核的情况下,他们应当防止可能出现的长时间造成的线程阻塞、费时计算等阻塞他们的事件循环式,试着把费时操作交给别的服务或者编写两个 C++ 库 去进行计算,这也是众多方案中比较简单粗暴的选择。

Teambition 后端API服务性能优化总结

当然,实际的生产自然环境中,单个线程的阻塞可能并不会有太大的影响,在 Teambition 的 k8s 集群中,布满了成百上千的 Pods,通过 k8s 自带的轮询调度算法,允诺基本会被均衡到不同的 pod、不同民主化的示例中。但假着日积月累、销售业务的增长,会在某些方面表现出一定的颓势,他们应该将这种现象提早治理,刮骨疗毒,才能为以后的销售业务保驾护航、承载更多的压力和优异的表现。

操控性强化的解决途径有很多种,当前使用的方法可能也不是最好的办法,欢迎与我讨论其中的不足与你的宝贵建议。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务