TYPO3 11.5.11 PHP进程挂起 会话类报错故障排查
TYPO3站点PHP进程挂起故障原因及解决方案
故障根本原因
- 核心故障点是静态路由解析环节的对外HTTP请求无超时配置导致进程阻塞:从php-fpm慢日志的调用栈可以明确看到,挂死的进程全部卡在
curl_exec函数,对应TYPO3内核的StaticRouteResolver静态路由中间件逻辑。该逻辑会通过Guzzle HTTP客户端发起curl请求拉取静态路由配置的外部目标资源,但默认没有配置请求超时时间,一旦目标地址网络不通、服务无响应,PHP进程会一直阻塞等待curl返回,持续占用php-fpm工作进程。当所有php-fpm worker都被这类阻塞请求占满后,新的用户请求无法被处理,就会表现为站点完全挂死,重启PHP进程释放所有阻塞worker后服务就会临时恢复。 - 日志里的两个session相关警告都是进程阻塞后的次生问题,不是故障根因,不需要优先排查:
session_regenerate_id报错是因为进程长期阻塞时,会话上下文被PHP异常回收,调用会话ID重置方法时没有处于活跃会话状态,才会抛出警告。filemtimestat失败报错是因为会话垃圾回收逻辑遍历会话文件时,目标文件刚好被其他并发进程、系统定时清理任务删除,属于文件存储会话场景下的常见边缘警告,本身不会导致进程挂死。
报错详情
1. 后台会话重置警告
Core: Error handler (BE): PHP Warning: session_regenerate_id(): Cannot regenerate session id - session is not active in /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/install/Classes/Service/SessionService.php line 165
对应代码段:
/** * Generates a new session ID and sends it to the client. * * @return string|false the new session ID */ private function renewSession() { // we do not have parallel ajax requests so we can safely remove the old session data session_regenerate_id(true); return session_id(); }
2. 会话文件GC回收警告
Error handler (BE): PHP Warning: filemtime(): stat failed for /www/htdocs/wxxxxxxx/cmstypo3/typo3temp/var/session/5b746183ce7e94a328dc1c5bb6e5598574a6d5c1/hash_6e60e04342f6660702e7abff85ff99d9 in /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/install/Classes/Service/Session/FileSessionHandler.php line 210
对应代码段:
/** * Garbage collect session info. See @session_set_save_handler * * @param int $maxLifeTime The setting of session.gc_maxlifetime * @return bool */ #[\ReturnTypeWillChange] public function gc($maxLifeTime) { $sessionSavePath = $this->getSessionSavePath(); $files = glob($sessionSavePath . '/hash_*'); if (!is_array($files)) { return true; } foreach ($files as $filename) { if (filemtime($filename) + $this->expirationTimeInMinutes * 60 < time()) { @unlink($filename); } } return true; }
3. php-fpm慢日志记录
[27-Jun-2022 19:02:20] [pool wxxxxxxx] pid 3601 script_filename = /www/htdocs/wxxxxxxx/cmstypo3/index.php [0x00007f6b0e2163f0] curl_exec() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php:44 [0x00007f6b0e216350] __invoke() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php:28 [0x00007f6b0e2162a0] GuzzleHttp\Handler\{closure}() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php:48 [0x00007f6b0e2161f0] GuzzleHttp\Handler\{closure}() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php:35 [0x00007f6b0e216110] __invoke() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/Middleware.php:31 [0x00007f6b0e216060] GuzzleHttp\{closure}() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php:71 [0x00007f6b0e215fc0] __invoke() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/Middleware.php:63 [0x00007f6b0e215f20] GuzzleHttp\{closure}() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/HandlerStack.php:75 [0x00007f6b0e215e90] __invoke() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/Client.php:331 [0x00007f6b0e215d40] transfer() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/Client.php:168 [0x00007f6b0e215c60] requestAsync() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/vendor/guzzlehttp/guzzle/src/Client.php:187 [0x00007f6b0e215bd0] request() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/core/Classes/Http/RequestFactory.php:55 [0x00007f6b0e215b30] request() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/frontend/Classes/Middleware/StaticRouteResolver.php:131 [0x00007f6b0e215a80] getFromUri() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/frontend/Classes/Middleware/StaticRouteResolver.php:186 [0x00007f6b0e215990] resolveByType() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/frontend/Classes/Middleware/StaticRouteResolver.php:72 [0x00007f6b0e215890] process() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/core/Classes/Http/MiddlewareDispatcher.php:172 [0x00007f6b0e2157f0] handle() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/adminpanel/Classes/Middleware/AdminPanelInitiator.php:55 [0x00007f6b0e215760] process() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/core/Classes/Http/MiddlewareDispatcher.php:172 [0x00007f6b0e2156c0] handle() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/frontend/Classes/Middleware/SiteBaseRedirectResolver.php:94 [0x00007f6b0e2155c0] process() /www/htdocs/wxxxxxxx/cmstypo3/typo3_src-11.5.11/typo3/sysext/core/Classes/Http/MiddlewareDispatcher.php:172
解决方案
- 排查静态路由配置:进入TYPO3后台站点管理模块,检查所有配置为「拉取外部URI内容」类型的静态路由,从服务器本地测试目标地址是否可以正常访问,删除失效的静态路由配置,修正地址填写错误的条目。
- 给HTTP请求加上全局超时限制:在TYPO3的
AdditionalConfiguration.php配置文件中加入如下配置,从根源避免curl请求无期限阻塞:
$GLOBALS['TYPO3_CONF_VARS']['HTTP']['timeout'] = 5; $GLOBALS['TYPO3_CONF_VARS']['HTTP']['connect_timeout'] = 3;
以上配置表示连接超时3秒,总请求超时5秒,超过时限直接中断请求,不会长期占用PHP进程。
- 优化php-fpm配置:在php-fpm对应站点池的配置中加入
request_terminate_timeout = 30s,强制杀死执行超过30秒的PHP进程,避免个别异常请求长期占用worker资源。 - 修复次生session警告(非必须,不影响核心故障解决):
- 给
session_regenerate_id调用加上会话状态判断,修改SessionService.php的renewSession方法,只有会话处于活跃状态时才执行ID重置:
private function renewSession() { if (session_status() === PHP_SESSION_ACTIVE) { session_regenerate_id(true); } return session_id(); }- 给
FileSessionHandler.php的GC逻辑加上文件存在判断,调用filemtime前先校验文件是否存在,避免文件被并发删除后触发stat警告:
注意:直接修改核心源码的内容会在下次TYPO3版本升级时被覆盖,优先升级到11.5分支最新小版本解决(后续官方版本已经修复了这两个边缘警告),也可以通过XCLASS方式重写对应类做兼容。foreach ($files as $filename) { if (file_exists($filename) && filemtime($filename) + $this->expirationTimeInMinutes * 60 < time()) { @unlink($filename); } } - 给
- 定期清理
typo3temp/var/session目录下的过期会话文件,避免单目录下文件数量过多导致glob扫描、文件stat操作性能下降。
内容的提问来源于stack exchange,提问作者mikee
相关产品推荐
相关产品推荐

