前端体验优化(3)——后端

发布时间 2023-12-18 09:57:47作者: 咖啡机(K.F.J)

  前端很多时候是不会接触到后端的工作,不过我们公司由于历史原因,维护了大量的 Node.js 服务。

  所以也积累了一些后端优化的经验,主要分两块 Node.js 和数据库。

一、Node.js

  Node.js 的监控没有从 0 开始,业务逻辑的日志直接记录在阿里云中,性能监控部署的是阿里云提供的系统。

  业务逻辑包括自定义的埋点,以及数据库的增删改查操作,还有内部接口访问的日志信息等。

  在性能监控中,可以导出内存快照,方便排查内存泄漏的问题。

1)服务

  Node.js 提供了定时任务、SSR、压缩图像、消息队列等服务。

  定时任务可以将一些比较费时的计算按指定的间隔执行,以免直接访问造成接口的慢响应。

  SSR 就是服务端渲染,是一种常见的页面优化手段,qwik 是一款语法接近 React 的前端 SSR 框架。

  其目标是延迟加载所有的代码,例如一个按钮在没有点击它之前,就不会去加载点击的逻辑,甚至不会去加载 React 相关的代码。

  理念很好,但是 qwik 还不够稳定,应该会有比较多的坑。

  消息队列是一种限流削峰的有效手段,一般由于瞬时访问量过大,导致流量暴增,系统无法处理请求甚至发生崩溃。

  而在加入消息队列后,系统可以从消息队列中取数据,相当于消息队列做了一次缓冲,极大地减少了业务处理的压力。

2)接口

  在业务迭代和人员流动的过程中,就会出现一些冗余接口,而这些接口的访问量还可能比较大。

  在对业务进行分析后,可以将其合并到另一个接口中,或者就是将接口响应直接写到前端脚本中,直接减少一次通信。

  比较影响接口查询时间的是与数据库的交互,可以减少交互次数,例如增加一层缓存,无必要就不去查询。

  或者要查询多条记录时,用一条 SQL 语句完成,可以在 SQL 使用 in 语法,例如 select * from user where id in (1,2,3)。

  对于提交到服务器的数据,需要进行安全测试,即排除潜在隐患,防止刷接口,例如增加身份验证。

  当代码出现异常时(例如调的内部接口没有响应),得避免接口奔溃,一直加载中,可以给到统一的响应。

  在统一响应的 JSON 格式后(例如 { code:0, data: {}, msg: "xxx"}),就可以比较方便的统计接口异常和规范前端代码。

  规定 code 为 0 是正常响应,非 0 为异常后,就能统计各种异常响应的数量以及占比,进行针对性的优化。

  诸如监控、埋点等非业务且量比较大的接口,推荐单独分离出来(可根据请求路径进行服务转发),以免出现错误时,影响线上业务,

3)版本升级

  公司之前所有的 Node 项目,其环境都是 8.9.4 版本,发布于 2018 年的一个比较古老的版本。

  老版本有两个比较明显的问题:

  1. Node 高版本的特性和方法都无法使用。
  2. 有些第三方新版本的包无法安装和升级,该包可能依赖比较高的 Node 版本。

  之前在开发项目时就遇到第三方包自身的问题,必须升级或换个包才能解决,但因为 Node 版本的原因,无法替换,只能用其他方式来修补漏洞。

  2022 年的 7 月份,才有机会将版本升级到 16.15,总共有 4 个 Node 环境需要升级。

  在 4 个待升级的项目中,有 3 个是对外的项目,有 1 个是对内的项目。

  那么先升级对内项目的 Node 版本,这样有两个好处:

  1. 即使出问题了,影响范围也能最小。
  2. 响应也能最及时,因为有问题的话,在公司内部能马上反馈到我们组。

  我们每个项目都会有 3 套运行环境:测试、预发和生产。

  首先将测试环境升级,测试环境都是开发人员使用,影响最小,反馈最快。

  观察一段时间后,再升级预发,预发环境与生产环境最为接近,数据库采用的也是一套。

  最后才是生产,给真实用户使用,再获取反馈。从开始升级到全部项目升级完成,前前后后操作了 20 多天。

4)框架

  Node.js 的框架众多,公司使用的是 KOA2,它非常轻量,诸如路由、模板等功能默认都不提供,需要自己引入相关的中间件。

  洋葱模型的中间件非常强大,我们将异常响应、身份验证、发送站内信等各类操作都交由中间件处理。

  各类框架都有其自身的特点和适用场景,选择最合适自己团队的框架才是正解。

  在公司的仓库中,发现之前有用基于 next.js 建立的一个 SSR 项目,不过后面没人维护,再之后就被运维释放掉了。

  如果选择了某一框架,那还是要尽量维护起来,投入到实际生产中,发挥其作用。

二、数据库

  数据库被分为关系型和非关系型数据库,公司用的 MySQL 就属于前者,而 Redis、MongoDB、ElasticSearch 等数据库就属于后者。

  线上数据库也需要有个监控平台运行着,了解数据库的 CPU,定位慢查询等。

1)MySQL

  在某张表的数据量上去后,性能瓶颈就会浮现出来。

  常见的优化包括创建索引、缩小查询范围等,利用执行计划,可以观察索引创建前后影响的行数。

  每次索引创建后,都能提高几百、甚至几万倍的查询速度,当然,创建索引也是有讲究的,也不是随便创建的。

  之前就遇到创建太长的索引,被拒绝了。

  另一种优化手段是将数据归档,就是将比较旧或不用的数据迁移至别处,另一个数据库或直接下载到本地。

  当然,将数据存储在云服务中,是会产生经济成本的,所以还有种办法就是直接删除,这类通常是冗余数据,不会用到。

  对于非业务的数据表,也是像接口那样需要分离,以免出现容量上限、慢查询等问题而拖垮线上服务。

2)MongoDB

  自研的前端监控系统中的表字段有很多是 JSON 格式,这类数据其实很适合存储在 MongoDB 中。

  因为 MySQL 需要先将对象进行字符串序列化,然后再存储,而此时若需要以对象中的某个属性作为查询条件,MySQL 就难以实现了。

  不过,最终并没有将数据存储在 MongoDB 中,因为云服务只提供了 MySQL 的可视化操作界面和监控平台。

  为了便于自己排查异常,还是选择了 MySQL 作为存储媒介,但公司有许多遗留业务都存储在 MongoDB 中,日常还需要维护。

  所以后期自研了 MongoDB 可视化查询工具,在后台可以查询线上的 MongoDB,省得每次都登录服务器查看。

3)ElasticSearch

  当需要全文检索一张千万级的表时,在 MySQL 中实现就比较困难,很容易将数据库挂起,并且在 5.7.6 之前的 MySQL 还不支持中文检索。

  此时可用 ElasticSearch 替代,这是一款基于 Lucene 的分布式、可扩展、RESTful 风格的全文检索和数据分析引擎,擅长实时处理 PB 级别的数据。

  前端监控数据每日会增加 70W 条记录,在 ES 中用关键字检索这批数据会非常快。

  若数据中有比较大的字段,那么生成的倒排索引也会比较巨大,当时我存储了接口的响应,结果整个容量直接膨胀了 3 倍。

  若要联立好几张表的 MySQL 数据,那么也比较适合存到 ES 中。

  例如用户发布的日志,分成了 100 张表存储在 MySQL 中,现在需要关键字检索,就可以将这 100 张表合并到 ES 中。

4)Redis

  Redis 是一种支持 key-value 等多种数据结构的存储系统,基于内存,可持久化,用于缓存、消息队列、集群等场景。

  Redis 之所以快,一方面是因为大部分操作在内存中完成,以及采用高效的数据结构,例如哈希表、跳表等。

  另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。

  而 Redis 的并发处理能力(每秒处理请求数)能达到万级别,甚至更高。

  前面也讲到过,在接口中增加一层缓存,可加速接口响应,这也是缓存的常规用途,但是要注意,当缓存被穿透时,数据要能自动恢复。

  也就是说,缓存不保障数据的一致性,不能因为缓存的异常,而影响线上业务,将错误信息存储到数据库中。

  适当的将小部分特定的数据提前存入到 Redis 中,可起到预热的作用,增加体验。