归来的技术栈——无状态的服务端

本系列文章都是以方便开发和维护为出发点,在可能的时候,尽量兼顾性能方面的需求

本文是 归来的技术栈 的第三篇,必要时请阅读链接中的内容了解背景。

如果拿 Node.js 和 PHP 比较,我向来是喜欢 Node 多一点。但有一点是 PHP 天然具备, 而 Node 需要付出一些努力才能实现的。

默认情况下,PHP 针对每个请求会使用独立的进程来处理,完成后即销毁。虽然从性能上来说,每次执行都要重复加载文件和数据会带来负面影响,但在另一些方面,却有着先天优势。

比如某个请求发生了错误,不会对另一个请求造成影响;内存泄漏的负面效果无法累积起来影响系统的长期稳定运行;在一定程度上只要堆服务器就可以横向扩展来提高并发能力;不需要针对服务器启停编写额外的代码来初始化和扫尾;服务器意外关闭一般也不会造成什么影响(虽然也不总是如此,比如用了APC之类)。

这些都是无状态带来的好处,可以降低开发和维护的成本,并通过堆硬件提高性能。抛弃一些东西,在其他方面就可以获得更多。

完全无状态的服务端类似一个巨大的纯函数,每个参数相同的请求都会获得相同的结果,也不对外部环境产生影响。但是如果真做到这个程度,服务端就变成静态了,失去其应有的作用。

因此我们还是需要一种手段,在外部保存数据,最好是基于文件的数据库。数据库用内存加速可以接受,但是完全依赖内存就会失去对意外宕机的抵抗力。其他临时性的数据,能省则省。包括 session,虽然 session 也可以用文件或数据库保存,但是我们可以通过使用 json web token ,把它也省了。服务器上的状态越少、维护越容易。

当然,在实际工作中,我也使用缓存,但仅限于特别关键的地方。缓存必须独立于自己开发的软件存在,并且往往是以单向的、即时计算的形式来生成。独立可以确保能在不同进程间共享;单向是指只有一种方法来计算缓存,如果要更新缓存,只能用相同的方法重新计算一次,而不允许对当前的缓存进行加减等操作,这样就算出现意外的并发,也不会导致缓存中的内容出现错误;即时计算是指在第一次需要某个值时,才对其进行计算,并将结果存入缓存,直到过期。如此一来,缓存也成了“无状态”的,缓存的内容不受计算次数的影响,宕机重启也不会影响软件的整体运行,缓存会在需要时各自重建。虽然这种做法没有将缓存的价值发挥到最大,但由于开发和维护成本极低,有很高的性价比。

Node.js 毕竟不是 PHP,要在 Node.js 和 Express 环境中做到完全的无状态有点费力不讨好,但是基本的思想可以借鉴,尽量减少服务端要维护的状态,并在模块的级别实现这一目标。

要做到这一点,最基本的一个要求,就是别让变量活过一个请求,Express 的每个请求最终都会对应到一个回调函数,这个函数里不能出现其他来自外部的变量(必要的第三方库和数据库连接池会出现在其中,但从整个模块角度看,它们应该算是不可变量)。如果你在整个模块中,都能只使用 const,不用 var 来完成所有功能,并且没有作弊(把 const 定义的对象下的属性当变量用),那么就基本达成无状态的目标了。

当你的模块实现了无状态。在性能方面,就可放心的使用 Cluster 来启动子进程扩展并发能力,请求无论落入哪个子进程,都不会影响返回的结果,如果在前面加上一道负载均衡,还可以进一步扩展到多台服务器;在开发和维护方面,你可以放心的使用热加载,通过监测文件变化,在不停止服务的情况下更新软件版本。粒度比使用 nodemon 或者 supervior 等工具更细。

浙ICP备15043004号-1