一年Node.js开发开发经验总结

本文首发于公众号:符合预期的CoyPan

写在前面

不知不觉的,写Node.js已经一年了。不一样于最开始的demo、本地工具等,这一年里,都是用Node.js写的线上业务。从一开始的Node.js同构直出,到最近的Node接入层,也算是对Node开发入门了吧。目前,我一我的维护了大部分组内流传下来的Node服务,包括内部系统和线上服务。新增的后台服务,也是尽量地使用Node进行开发。本文是一下本身最近的一些小小的总结和思考。javascript

本文不会深刻讲解Node.js自己的特性,架构等等。我也没有写过Node扩展或者库什么的,对Node.js的了解也并不够深刻。

为什么用Node

对于我来讲,对于团队来讲,适用Node的缘由其实很简单:开发起来快。熟悉JS的前端同窗能够很快上手,节省成本。选一个http server库起一个server,选择合适的中间件,匹配好请求路由,看状况合理使用ORM库连接数据库、增删改查便可。前端

Node的适用场景

Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。这种模型使得Node.js 能够避免了因为须要等待输入或者输出(数据库、文件系统、Web服务器...)响应而形成的 CPU 时间损失。因此,Node.js适合运用在高并发、I/O密集、少许业务逻辑的场景。java

对应到平时具体的业务上,若是是内部的系统,大部分仅仅就是须要对某个数据库进行增删改查,那么Server端直接就是Node.js一把梭。算法

对于线上业务,若是流量不大,而且业务逻辑简单的状况下,Server端也能够彻底使用Node.js。对于流量巨大,复杂度高的项目,通常用Node.js做为接入层,后台同窗负责实现服务。以下图:数据库

一样是写JS,Node.js开发和页面开发有什么区别

在浏览器端开发页面,是和用户打交道、重交互,浏览器还提供了各类Web Api供咱们使用。Node.js主要面向数据,收到请求后,返回具体的数据。这是二者在业务路径上的区别。而真正的区别实际上是在于业务模型上(业务模型,这是我本身瞎想的一个词)。直接用图表示吧。浏览器

开发页面时,每个用户的浏览器上都有一份JS代码。若是代码在某种状况下崩了,只会对当前用户产生影响,并不会影响其余用户,用户刷新一下便可恢复。而在Node.js中,在不开启多进程的状况下,全部用户的请求,都会走进同一份JS代码,而且只有一个线程在执行这份JS代码。若是某个用户的请求,致使发生错误,Node.js进程挂掉,server端直接就挂了。尽管可能有进程守护,挂掉的进程会被重启,可是在用户请求量大的状况下,错误会被频繁触发,可能就会出现server端不停挂掉,不停重启的状况,对用户体验形成影响。缓存

以上,多是Node.js开发和前端JS开发最大的区别。服务器

Node.js开发时的注意事项

用户在访问Node.js服务时,若是某一个请求卡住了,服务迟迟不能返回结果,或者说逻辑出错,致使服务挂掉,都会带来大规模的体验问题。server端的目标,就是要 快速、可靠 地返回数据。架构

缓存

因为Node.js不擅长处理复杂逻辑(JavaScript自己执行效率较低),若是要用Node.js作接入层,应该避免复杂的逻辑。想要快速处理数据并返回,一个相当重要的点:使用缓存。并发

例如,使用Node作React同构直出,renderToString这个Api,能够说是比较重的逻辑了。若是页面的复杂度高,每次请求都完整执行renderToString,会长时间占用线程来执行代码,增长响应时间,下降服务的吞吐量。这个时候,缓存就十分重要了。

实现缓存的主要方式:内存缓存。可使用Map,WeakMap,WeakRef等实现。参考如下简单的示例代码:

const cache = new Map();

router.get('/getContent', async (req, res) => {
  const id = req.query.id;
  
  // 命中缓存
  if(cache.get(id)) {
    return res.send(cache.get(id));
  }
  
  // 请求数据
  const rsp = await rpc.get(id);
     // 通过一顿复杂的操做,处理数据
  const content = process(rsp);
  // 设置缓存
  cache.set(id, content);
  
  return res.send(content);
});

使用缓存时,有一个很重要的问题是:内存缓存如何更新。一种最简单的方法,开一个定时器,按期删除缓存,下一次请求到来时,从新设置缓存便可。在上述代码中,增长以下代码:

setTimeout(function() {
  cache.clear();
}, 1000 * 60); // 1分钟删除一次缓存

若是server端彻底使用Node实现,须要用Node端直接链接数据库,在数据时效性要求不过高、且流量不太大的状况下,就可使用上述相似的模型,以下图。这样能够下降数据库的压力且加快Node的响应速度。

另外,还须要注意内存缓存的大小。若是一直往缓存里写入新数据,那么内存会愈来愈大,最终爆掉。能够考虑使用LRU(Least Recently Used)算法来作缓存。开辟一块内存专门做为缓存区域。当缓存大小达到上限时,淘汰最久未使用的缓存。

内存缓存会随着进程的重启而所有失效。

当后台业务比较复杂,接入层流量,数据量较大时,可使用以下的架构,使用独立的内存缓存服务。Node接入层直接从缓存服务取数据,后台服务直接更新缓存服务。

固然,上图中的架构是最简单的情形,现实中还须要考虑分布式缓存、缓存一致性的问题。这又是另一个话题了。

错误处理

因为Node.js语言的特性,Node服务是比较容易出错的。而一旦出错,形成的影响就是服务不可用。所以,对于错误的处理十分的重要。

处理错误,最经常使用的就是try catch 了。但是 try catch没法捕获异步错误。Node.js中,异步操做是十分常见的,异步操做主要是在回调函数中暴露错误。看一个例子:

const readFile = function(path) {
    return new Promise((resolve,reject) => {
        fs.readFile(path, (err, data) => {
            if(err) { 
                throw err; // catch没法捕获错误,这和Node的eventloop有关。
        // reject(err); // catch能够捕获
      }
      resolve(data);
        });
    });
}

router.get('/xxx', async function(req, res) {
  try {
    const res = await readFile('xxx');
    ...
  } catch (e){
    // 捕获错误处理
    ...
    res.send(500);
  }
});

上面的代码中,readFile 中 throw 出来的错误,是没法被catch捕获的。若是咱们把 throw err 换成 Promise.reject(err),catch中是能够捕获到错误的。

咱们能够把异步操做都Promise化,而后统一使用 async 、try、catch 来处理错误

可是,总会有地方会被遗漏。这个时候,可使用process来捕获全局错误,防止进程直接退出,致使后面的请求挂掉。示例代码:

process.on('uncaughtException', (err) => {
  console.error(`${err.message}\n${err.stack}`);
});

process.on('unhandledRejection', (reason, p) => {
  console.error(`Unhandled Rejection at: Promise ${p} reason: `, reason);
});

关于Node.js中错误的捕获,还可使用domain模块。如今这个模块已经不推荐使用了,我也没有在项目中实践过,这里就不展开了。Node.js 近几年推出的 async_hooks 模块,也还处于实验阶段,不太建议线上环境直接使用。作好进程守护,开启多进程,错误告警及时修复,养成良好的编码规范,使用合适的框架,才能提升Node服务的效率及稳定性。

写在后面

本文总结了Node.js开发一年多以来的实践总结等。Node.js的开发与前端网页的开发思路不一样,着重点不同。我正式开发Node.js的时间也不算太长,一些点并无深刻的理解,本文仅仅是一些经验之谈。欢迎交流。

相关文章
相关标签/搜索