You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel缓存MaxMind Reader实例报Attempt to read from a closed MaxMind DB求助

问题根因

你遇到的报错和Reader实例无法缓存的核心原因有两个:

  • GeoIp2\Database\Reader 内部持有mmdb数据库的文件句柄,而PHP中的资源类型(文件句柄属于资源)无法被序列化存储,存入Laravel缓存时会直接丢失资源引用,取出的实例根本无法正常读取数据库
  • 即使不存在序列化问题,你在第一次调用完查询后执行了$reader->close(),直接关闭了文件句柄,后续从缓存取出的实例对应的文件已经处于关闭状态,自然会抛出Attempt to read from a closed MaxMind DB异常
    Reader实例本身就不支持序列化后跨请求缓存,这条路走不通,不要尝试继续用这个方案

可行的性能优化方案

根据你的运行环境可以选择对应的优化方案:

方案1:同请求内复用Reader(适合所有环境,基础优化)

利用Laravel服务容器绑定单例,同一个请求内如果多次调用IP查询逻辑,只会加载一次mmdb文件,不需要重复实例化Reader

  1. 在app/Providers/AppServiceProvider.php的register方法中添加绑定逻辑:
public function register()
{
    $this->app->singleton(\GeoIp2\Database\Reader::class, function () {
        return new \GeoIp2\Database\Reader(storage_path('/app/ipToCountry/GeoLite2-City-v2.mmdb'));
    });
}
  1. 控制器中直接注入Reader实例使用,不要手动调用close方法,请求结束后PHP会自动释放资源:
public function check(Request $request, \GeoIp2\Database\Reader $reader)
{
    return $reader->city($request->ip());
}

方案2:缓存IP查询结果(适合FPM环境,优化效果明显)

不要缓存Reader实例,转而缓存每个IP对应的查询结果,相同IP在缓存有效期内不需要重复读取mmdb文件:

public function check(Request $request)
{
    $ip = $request->ip();
    // 可根据业务需要调整缓存有效期,这里示例缓存24小时
    return \Cache::remember('geo_ip_result_' . $ip, now()->addDay(), function () use ($ip) {
        $reader = new \GeoIp2\Database\Reader(storage_path('/app/ipToCountry/GeoLite2-City-v2.mmdb'));
        $result = $reader->city($ip);
        $reader->close();
        return $result;
    });
}

这个方案可以过滤掉绝大多数重复IP的查询请求,实际运行中mmdb文件的加载次数会下降90%以上,性能提升非常明显。

方案3:常驻内存环境复用Reader(性能最优,适合高并发场景)

如果你的业务请求量极高,建议切换到Swoole/Workerman等PHP常驻内存运行环境,Reader单例实例会一直驻留在内存中,跨请求不需要重复加载mmdb文件,完全消除70MB文件的加载开销,性能可以提升数十倍。
使用这种方案同样通过服务容器绑定单例即可,不需要额外修改业务代码,且不要手动调用close方法。


内容的提问来源于stack exchange,提问作者Dani Banai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 09:30:05