在源码编译安装 Apache HTTP Server 时,./configure 阶段的模块启用参数有两种典型写法:一种是 --enable-rewrite,另一种是 --enable-rewrite=shared。前者把模块静态编译进 httpd 主程序,后者则生成独立的 .so 文件,运行时通过 LoadModule 指令动态加载。这两种方式虽然最终都能让模块生效,但在文件形态、加载时机、内存占用和后期维护成本上存在明显差别。理解这些差别,对生产环境的部署和后续升级都有实际意义。

一、两种方式在编译阶段的差异
先看静态编译。执行 ./configure --enable-rewrite 时,rewrite 模块的源码会被直接编译链接进 httpd 二进制文件。编译完成后,在安装目录的 modules 目录下找不到 mod_rewrite.so,但执行 httpd -l 可以看到 mod_rewrite.c 出现在内置模块列表中。这意味着该模块已经成为 httpd 主程序的一部分,无法单独卸载或替换。
再看动态编译。写法是 ./configure --enable-rewrite=shared(也可以用 --enable-modules=most 批量生成动态模块),编译产物是 modules/mod_rewrite.so 这样一个独立共享库文件。模块本身不占用 httpd 主程序的体积,主配置文件 httpd.conf 中需要有一行类似 LoadModule rewrite_module modules/mod_rewrite.so 的指令才能让它在启动时被加载。如果这行被注释掉,rewrite 功能就直接失效了。
需要注意一个前提:要使用动态模块,编译时必须启用 mod_so,通常通过 --enable-so 参数实现。mod_so 是动态加载机制的载体,没有它,httpd 根本不具备加载 .so 文件的能力。好在绝大多数发行版的预编译包和常见编译配置默认都包含这个模块。
二、加载机制与运行时表现对比
静态模块随主程序启动直接可用,不存在加载延迟,也不依赖额外的磁盘文件。动态模块则是在 httpd 启动阶段由 mod_so 负责把 .so 文件映射进进程地址空间。理论上动态加载会带来一次磁盘寻址和符号解析开销,并且 .so 文件中的符号表会占用少量额外内存。但实测下来,这种差异在每秒数千次请求量级的场景中几乎可以忽略,因为加载只发生在启动阶段,请求处理过程中两种方式调用的都是同一段机器码。
内存占用方面有一个容易误解的点:动态模块并不会让每个 worker 进程都多占一份 .so 内存。共享库在内存中是所有进程共享同一份物理页的,只有写时复制触发的数据段才会各自独立。因此无论静态还是动态,N 个 worker 进程的模块内存开销基本一致,不必为了省内存而刻意选择静态编译。
真正的差异体现在灵活性和排障上。动态模块可以随时通过注释或添加 LoadModule 行来启停,更换模块版本只需替换 .so 文件并重启服务,不必重新编译整个 httpd。而静态模块一旦需要升级版本或打补丁,就必须重新 configure、make、make install 整个 Apache,升级窗口和操作风险都大得多。
三、如何查看当前模块的加载方式
排查问题时,两条命令最有用。httpd -l 列出所有静态编译进主程序的模块,输出中出现的模块一定不依赖 LoadModule 指令:
[root@web ~]# httpd -l Compiled in modules: core.c mod_so.c http_core.c event.c
httpd -M 则列出当前配置下实际生效的全部模块,静态和动态混合输出,动态模块末尾会标注 (shared),静态模块标注 (static):
[root@web ~]# httpd -M | grep -E "rewrite|ssl" rewrite_module (shared) ssl_module (shared)
如果某个模块在 httpd -M 中找不到,先检查 httpd.conf 或 conf.modules.d 目录下对应的 LoadModule 行是否被注释,再确认 modules 目录下 .so 文件是否存在。两者缺一不可,这是新手最常踩的坑。
四、生产环境如何选择
多数场景下推荐动态模块。核心原因是维护成本低:mod_ssl、mod_proxy 这类模块迭代频繁,跟随 OpenSSL 版本升级是常态,用 .so 替换的方式升级只需几秒钟,而静态编译则要整站重新编译安装。此外,动态方式还支持按需裁剪,内存敏感的容器环境里可以只加载真正用到的模块。
静态编译适合两类情况:一是对主程序体积和启动速度有极致要求的嵌入式或精简环境;二是安全加固场景,比如希望某个关键模块(如认证模块)无法被轻易卸载或替换,把它编译进主程序可以降低被篡改的风险。另外,如果服务器的模块集合已经长期稳定不再变动,静态编译也能减少 modules 目录的管理负担。
给一个折中的参考配置:核心必备的模块静态编译,外围功能模块全部动态化。示例 configure 参数如下:
./configure --prefix=/usr/local/apache2 \ --enable-so \ --with-mpm=event \ --enable-rewrite \ --enable-ssl=shared \ --enable-proxy=shared \ --enable-headers=shared \ --enable-expires=shared \ --enable-deflate=shared \ --with-ssl=/usr/local/openssl make && make install
这个配置里 rewrite 作为最常用的模块静态编译,ssl、proxy 等需要随依赖库升级的模块保持动态,兼顾了性能与可维护性。总之,两种方式没有绝对的优劣,理解加载机制后再结合升级频率和运维习惯做选择,才是合理的决策路径。