记一次使用 laravel-s 内存泄漏

环境#

  • 使用的是 swoole:alpine 的镜像构建环境
FROM phpswoole/swoole:php7.4-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && cat /etc/apk/repositories

# 快速安装 PHP 扩展 install-php-extensions xxxCOPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/RUN install-php-extensions pcntl redis pdo_mysql

WORKDIR /var/www
COPY . .
RUN chmod -R 0777 storage && chmod -R 0777 bootstrap/cache && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ && composer install

### 生成 laravels 启动文件 + 启动
RUN php artisan laravels publish --no-interaction
CMD ["php", "bin/laravels", "start", "--env=product"]
  • 安装 laravels 的时候可以先注释 Dockerfile 最后两行,先让环境启动起来
    ## 把 [/mnt/d/xxxx] 替换成宿主机的代码目录
    docker build . -t test-imagedocker run -d -it -p 80:5200 -v [/mnt/d/xxxx]:/var/dev  --name=test-service -w /var/dev test-image /bin/shdocker exec -it memory-service /bin/sh```
    

错误#

  • 线上的机器一直报错日志
    [2022-01-01 02:29:45 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1641, id=5) abnormal exit, status=255, signal=0[2022-01-01 02:30:24 *1642.0]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 65536 bytes) in /var/www/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php on line 22[2022-01-01 02:30:24 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1642, id=0) abnormal exit, status=255, signal=0[2022-01-01 02:31:07 *1646.2]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in /var/www/vendor/nunomaduro/collision/src/Highlighter.php on line 138[2022-01-01 02:31:07 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1646, id=2) abnormal exit, status=255, signal=0[2022-01-01 02:31:14 *1647.3]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in /var/www/vendor/nunomaduro/collision/src/Adapters/Laravel/Inspector.php on line 30[2022-01-01 02:31:14 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1647, id=3) abnormal exit, status=255, signal=0[2022-01-01 02:32:11 *1644.1]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32768 bytes) in /var/www/vendor/nunomaduro/collision/src/Writer.php on line 1[2022-01-01 02:32:11 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1644, id=1) abnormal exit, status=255, signal=0[2022-01-01 02:32:22 *1645.6]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 65536 bytes) in /var/www/vendor/nunomaduro/collision/src/Writer.php on line 135[2022-01-01 02:32:22 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1645, id=6) abnormal exit, status=255, signal=0[2022-01-01 02:33:44 *1653.3]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32768 bytes) in /var/www/vendor/nunomaduro/collision/src/Writer.php on line 1[2022-01-01 02:33:44 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1653, id=3) abnormal exit, status=255, signal=0[2022-01-01 02:33:55 *1650.5]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in /var/www/vendor/monolog/monolog/src/Monolog/Utils.php on line 141[2022-01-01 02:33:55 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1650, id=5) abnormal exit, status=255, signal=0[2022-01-01 02:34:22 *1652.2]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32768 bytes) in /var/www/vendor/filp/whoops/src/Whoops/Run.php on line 1[2022-01-01 02:34:22 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1652, id=2) abnormal exit, status=255, signal=0[2022-01-01 02:34:24 *1649.4]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32768 bytes) in /var/www/vendor/nunomaduro/collision/src/Writer.php on line 1[2022-01-01 02:34:24 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1649, id=4) abnormal exit, status=255, signal=0[2022-01-01 02:34:38 *1651.0]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in /var/www/vendor/nunomaduro/collision/src/Writer.php on line 193[2022-01-01 02:34:38 $19.0]    WARNING    Server::check_worker_exit_status(): worker(pid=1651, id=0) abnormal exit, status=255, signal=0[2022-01-01 02:36:13 *1656.3]  ERROR  php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32768 bytes) in /var/www/vendor/nunomaduro/collision/src/Adapters/Laravel/IgnitionSolutionsRepository.php on line 1```
    

官方文档的说明#

关于内存泄露#

  • 避免使用全局变量,如一定要,请手动清理或重置。

  • 无限追加元素到全局变量、静态变量、单例,将导致内存溢出。

    class Test
    {
    public static $array = [];
    public static $string = '';
      // 某控制器
      public function test(Request $req)
      {
       // 内存溢出
       Test::$array[] = $req->input('param1');
       Test::$string .= $req->input('param2');
      }
    }
  • 内存泄露的检测方法

    1. 修改 config/laravels.phpworker_num=1, max_request=1000000,测试完成后记得改回去;

    2. 增加路由 /debug-memory-leak,不设置任何路由中间件,用于观察 Worker 进程的内存变化情况;

      Route::get('/debug-memory-leak', function () {
         global $previous;
         $current = memory_get_usage();
         $stats = [
             'prev_mem' => $previous,
             'curr_mem' => $current,
             'diff_mem' => $current - $previous,
         ];
         $previous = $current;
         return $stats;
      });
    3. 启动 LaravelS,请求 /debug-memory-leak,直到 diff_mem 小于或等于零;如果 diff_mem 一直大于零,说明全局中间件Laravel框架可能存在内存泄露;

    4. 完成步骤3 后,交替请求业务路由与 /debug-memory-leak(建议使用 ab/wrk 对业务路由进行大量的请求),刚开始出现的内存增涨是正常现象。业务路由经过大量请求后,如果 diff_mem 一直大于零,并且 curr_mem 持续增大,则大概率存在内存泄露;如果 curr_mem 始终在一定范围内变化,没有持续变大,则大概率不存在内存泄露。

    5. 如果始终没法解决,max_request 是最后兜底的方案。


按照文档修改好配置并添加路由之后,进入容器操作#

docker exec -it memory-service /bin/shwatch -n 1 127.0.0.1:5200/debug-memory-leakEvery 1.0s: curl 127.0.0.1:5200/debug-memory-leak                                                                                                                                                                   2022-01-05 03:48:15
{"prev_mem":22620816,"curr_mem":22626592,"diff_mem":5776}
  • 不断的观察,发现 diff_mem 一直不等于 0

  • 之前使用 dcat-admin 有过 session 问题,参考了 github.com/hhxsv5/laravel-s/issues..., 然后这个问题把 App\Providers\AppServiceProvider::class 加入到了 laravels.register_providers 配置中

  • 然后查看 register_providers 的逻辑在 \Hhxsv5\LaravelS\Illuminate\CleanerManager::cleanProviders 方法,可以看到这里调用了 LaravelApplicatonregister 方法,并且是强制 register

  • 由于 \Illuminate\Foundation\Application::register 方法,当服务提供者注册完毕之后,会执行 markAsRegistered

    protected function markAsRegistered($provider)
    {
    $this->serviceProviders[] = $provider;
    
    $this->loadedProviders[get_class($provider)] = true;
    }
  • php-fpm 模式下没什么问题,但是在 laravel-s 加持下,laravel-s 会把 Application 这个对象作为整个生命周期来运行。这就导致了 serviceProviders 这个数组会不断的变大.

原文链接

本作品采用《CC 协议》,转载必须注明作者和本文链接
当神不再是我们的信仰,那么信仰自己吧,努力让自己变好,不辜负自己的信仰!
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
未填写
文章
43
粉丝
158
喜欢
714
收藏
347
排名:30
访问:22.2 万
私信
所有博文
社区赞助商