最近这一年多, 见识过很多基于 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 模块一起编译进来。 其实深入思考一下, 这些都可以用很好的方式来实现。

末尾

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

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