最近这一年多,见识过很多基于 Nginx 开发的项目,在这个过程中也遇到了几个很常见的不规范的做法。

离主分支过远

Nginx 是一个很有生命力的项目,不断的在开发一些新的特性,基于这种项目开发的项目,从一开始就要想到版本同步升级的问题。

Nginx 本身提供了强大的模块开发机制,在做自己的业务开发时,应该尽可能用模块去解决,而不要乱动 Nginx 核心代码。其实,如果不是业务场景特殊,或者对性能有更苛刻的要求,开发者都不应该去修改核心代码。如果实在到了不动核心代码不行或者解决方案非常憋屈的时候,也应该尽量先做好同步升级的方案,比如经常不定期合入主干代码等。连开发阵容强大的 Tengine,都会跟进 Nginx 的更新,你有什么理由不这样做呢。

这不,我就见过一个基于 Nginx 的某个上古版本(0.7.x)开发的系统,随着业务的积累,已经将 Nginx 原始代码改的不像样子了,甚至连基础的进程架构都改了。但业务需求越来越多,这种方式明显跟不上节奏了,一方面是新特性用不了,另一方面是过多的改动导致潜在的 bug 很多。直到现在不得不痛下决心,彻底更新 Nginx 版本,去掉之前的那些核心改动。虽然比较痛苦,但方向是对的。当业界都在利用 Openresty 这一利器,用 lua 开心的写着业务逻辑,你还在一行一行的用 c 来写业务,这项目怎么能维护得下去。

提到 Openresty,不得不赞叹章宜春的版本策略。首先 Openresty 是一个bundle,其核心是 ngx_lua 模块,这个模块本身是不会依赖对 Nginx 核心的改动。而且 Openresty 跟进 Nginx 版本的速度也很快,当前 Openresty 的 release 已经到 Nginx-1.9.3 了。

真的是一定要动核心吗?

事实上,并不是。 很多时候,所谓 “不得不” 进行的对核心的修改,只是因为开发者对 Nginx 本身理解的不够透彻。

例如,一个简单的场景: 一个模块 A 需要为当前请求生成几个内部变量,既然是与当前请求相关的,很自然的会想到在 ngx_http_request_s 结构体里增加一个成员。可是,Nginx 已经提供了请求上下文的的 ctx 机制了,使用起来干净、简单。

再例如, Nginx 的各模块之间本应互相独立的。但出于代码复用等目的,实际项目里会出现模块之间互相依赖的情况。比方说, A 模块固定完成一个功能,所有其他的业务模块,都去操作当前请求的对应 A 模块的上下文 ctx, 而 A 模块只去根据 ctx 做相应的处理。这种做法看上去很精明,却毫不优雅。导致了模块之间的耦合,编译 B 模块就必须把 A 模块一起编译进来。其实深入思考一下, 这些都可以用很好的方式来实现。

末尾

这篇文章似乎是在吐槽一些开发者,但实际不是的。从公司的角度,不管使用什么办法,能撑起业务来,就是英雄,相应的,开发周期的制定里,并不会去给那么多时间让开发者去透彻理解一个系统后再开始开发。

所以,当我们看到这些 “老旧” 的系统,虽然他们可能很难维护,但我们还是应该对当初开发的人持有敬意。