PHP多实例运行时如何共享单次加载到内存的大文件避免内存溢出
解决方案
你当前方案的核心问题是file_get_contents会将文件全量拷贝到进程私有内存,多进程运行时会产生多份副本导致内存溢出,以下是经过验证的可行方案,按改造成本从低到高排序:
方案1:只读mmap映射加载文件(改造成本最低)
操作系统的只读内存映射(mmap)机制天然支持多进程共享同一份文件的物理内存页:
- mmap不会将文件内容拷贝到进程私有用户态内存,仅将文件映射到进程虚拟地址空间,实际物理内存由内核页缓存统一管理
- 多进程以只读模式映射同一个文件时,所有进程共享同一份物理内存,无论多少个进程访问,内存中仅保留一份5G的文件数据
- 映射得到的内存区域可以直接用PHP原生
strpos操作,完全保留你当前方案的检索速度优势
PHP安装ext-mmap扩展后,仅需修改加载逻辑即可,原有匹配逻辑无需改动:
// 只读模式映射地址文件 $file = fopen("/root/addresses.txt", "r"); $address_db = mmap($file, 0, filesize("/root/addresses.txt"), PROT_READ, MAP_SHARED); // 原有匹配逻辑,记得补充边界判断避免子串误匹配 if (strpos($address_db, "\n" . $address . "\n") !== false) { // 业务逻辑 }
注意事项:
- 必须使用只读权限映射,开启写权限会触发写时复制,产生额外内存开销
- 提前在addresses.txt首尾各加一个换行符,确保每个地址独立一行,避免短地址匹配到长地址的子串造成误判
方案2:本地常驻查询服务(内存占用最稳定)
如果不想安装额外扩展,可以基于Swoole/WorkerMan实现一个极简常驻查询服务:
- 服务启动时仅加载一次addresses.txt到自身进程内存
- 服务暴露本地TCP/HTTP接口,接收待查询地址,返回是否存在的布尔结果
- 所有业务PHP脚本无需加载大文件,仅通过本地环回请求服务获取结果
该方案下无论启动多少个业务PHP进程,内存中仅存在服务加载的一份5G数据,本地环回请求延迟低于0.1ms,几乎无性能损耗,还可按需增加热点地址缓存进一步提升速度。
方案3:APCu共享内存存储(适配现有APCu环境)
如果你的环境已经安装APCu扩展,可直接将地址库存入APCu共享内存实现多进程共享:
- 调整内核参数
kernel.shmmax至6G以上,修改php.ini配置apc.shm_size=6G,关闭APCu自动过期逻辑 - 增加全局判断:如果APCu中不存在地址库数据,就加载文件写入APCu
- 所有业务进程直接从APCu读取地址库做匹配,共享同一块内存空间
注意更新地址库时需要主动删除APCu中的旧缓存,否则会一直使用旧数据。
避坑说明
- 不要使用Redis、MySQL等外部存储做匹配:单条查询的网络IO、索引查找开销是内存
strpos的10倍以上,性能差距明显,且外部存储本身还会占用额外内存 - 不要使用逐行遍历文件的方案:磁盘IO开销极大,百万级校验场景下速度比全量内存匹配慢两个数量级
内容的提问来源于stack exchange,提问作者xowim46108
相关产品推荐
相关产品推荐

