Windows Server环境下Apache Worker进程卡顿问题求助
会话相关的Apache Worker卡顿与用户白屏问题
环境信息
- 自2017年起基于WAMP栈运行内部后台Web应用,服务200-300名用户
- 服务器:两台Windows Server 2016虚拟机
- 应用服务器:Apache 2.4.58 + PHP 8.2.12
- 数据库服务器:MySQL 8.0.33
症状表现
- 随机部分用户使用Chrome浏览器加载页面时出现白屏,标签页持续处于加载状态
- 删除浏览器中的PHP session cookie后可恢复正常
- 服务器端通过mod_status页面观测到异常:
- HTTP/1.1协议下,多个Worker进程卡在W - 发送响应状态,其“SS - 当前请求持续时长”远超配置的Timeout值
- 切换至HTTP/2协议后,Worker进程会卡在C - 关闭连接状态,SS值同样过高


已尝试的配置调整
Apache配置
# Core config ThreadLimit 512 ThreadsPerChild 512 ThreadStackSize 8388608 MaxRequestsPerChild 0 KeepAlive On KeepAliveTimeout 5 MaxKeepAliveRequests 500 TimeOut 60 ProxyTimeout 60 RequestReadTimeout handshake=0 header=10-40,MinRate=500 body=20,MinRate=500 # Http2 config Protocols h2 http/1.1 H2Direct On H2MinWorkers 64 H2MaxWorkers 512 H2MaxSessionStreams 512 H2StreamMaxMemSize 1048576 H2WindowSize 1048576 H2MaxWorkerIdleSeconds 10 H2StreamTimeout 60
PHP会话配置
[Session] session.save_handler = files session.save_path = "c:/tmp" session.use_strict_mode = 1 session.use_cookies = 1 session.cookie_secure = 0 session.use_only_cookies = 1 session.name = PHPSESSID session.auto_start = 0 session.cookie_lifetime = 14500 session.cookie_path = / session.cookie_domain = session.cookie_httponly = 1 session.serialize_handler = php session.gc_probability = 1 session.gc_divisor = 1000 session.gc_maxlifetime = 14500 session.referer_check = session.cache_limiter = nocache session.cache_expire = 180 session.use_trans_sid = 0 session.hash_function = sha512 session.hash_bits_per_character = 5
自定义PHP会话处理类
<?php // Framework Session handler class Session { // Generic PHP session token private $_session_id; // Number of seconds after the session id is needs to be regenerated private $_session_keepalive = 300; // Error logging handler private $_error = null; /** * @Function: public __construct($config); * @Task: Default constructor for Framework session handler * @Params: * array $config: associative configuration array for construction * Default: array() - Empty array * Note: It can contain any of the class' configurable variables * without the first underscore as key * @Returns: * Session **/ public function __construct($config = array()) { // Setting config foreach(get_object_vars($this) as $key => $var) { if($key != '_session_id' && isset($config[mb_substr($key,1)])) $this->$key = $config[mb_substr($key,1)]; } // Make sure use_strict_mode is enabled. // use_strict_mode is mandatory for security reasons. ini_set('session.use_strict_mode', 1); ini_set('session.cookie_secure', 1); // Start the session $this->start(); // Create error logging handler $this->_error = new Error_Logging(); } /** * @Function: public __destruct(); * @Task: Destructor for Framework Session handler * @Params: None * @Returns: void **/ public function __destruct() { // Destroy variables foreach(get_object_vars($this) as $key => $var) unset($this->$key); // Releases the session file from write lock session_write_close(); } /** * @Function: private start() * @Task: Starts the PHP session * @Params: none * @Returns: none **/ private function start() { session_start(); // Store the session id $this->_session_id = session_id(); // Set CreatedOn if not set if(!$this->exists('CreatedOn')) $this->set('CreatedOn', date('Y-m-d H:i:s')); // Do not allow the use of old session id $time_limit = strtotime(' - ' . $this->_session_keepalive . ' seconds'); if(!empty($this->get('DeletedOn', '')) && strtotime($this->get('DeletedOn', '')) <= $time_limit) { session_destroy(); session_start(); $this->set('CreatedOn', date('Y-m-d H:i:s')); // Store the new session id $this->_session_id = session_id(); } // Regenerate the session when older than required if(strtotime($this->get('CreatedOn', '')) <= $time_limit) { $this->regenerate(); } } /** * @Function: private regenerate() * @Task: Regenerates the current PHP session * @Params: none * @Returns: none **/ public function regenerate() { // Call session_create_id() while session is active to // make sure collision free. if(session_status() != PHP_SESSION_ACTIVE) { $this->start(); } // Get all session data to restore $old_session_data = $this->get_all(); // Create a new non-conflicting session id $this->_session_id = session_create_id(); // Set deleted timestamp. // Session data must not be deleted immediately for reasons. $this->set('DeletedOn', date('Y-m-d H:i:s')); // Finish session session_write_close(); // Set new custom session ID session_id($this->_session_id); // Start with custom session ID $this->start(); // Restore the session data except CreatedOn and DeletedOn if(isset($old_session_data['CreatedOn'])) unset($old_session_data['CreatedOn']); if(isset($old_session_data['DeletedOn'])) unset($old_session_data['DeletedOn']); if(!empty($old_session_data)) $this->set_multi($old_session_data); } /** * @Function: public set($key, $val); * @Task: Set Session variable * @Params: * mixed key: Key of the session array variable * mixed val: Value of the session variable * @Returns: * bool **/ public function set($key, $val) { // Return variable $response = false; try{ // check if session is started if(empty(session_id())) throw new Exception('Session Error [0001]: Session is not started.'); // check if key is not null if(empty($key)) throw new Exception('Session Error [0002]: Session key cannot be empty.'); // set session key $this->write($key, $val); $response = true; } // Handle errors catch(Exception $e) { $this->_error->log($e->getMessage()); } return $response; } /** * @Function: public get($key); * @Task: Get session variable * @Params: * mixed key: Key of the session array variable * mixed default: Default value if result is empty * @Returns: * bool/mixed **/ public function get($key, $default = '') { // Return variable $response = false; try{ // check if session is started if(empty(session_id())) throw new Exception('Session Error [0001]: Session is not started.'); // check if key is not null if(empty($key)) throw new Exception('Session Error [0002]: Session key cannot be empty.'); // get session key if exists, else false $response = $this->read($key, $default); } // Handle errors catch(Exception $e) { $this->_error->log($e->getMessage()); } return $response; } /** * @Function: public exists($key); * @Task: Check if session variable exists * @Params: * mixed key: Key of the session array variable * @Returns: * bool **/ public function exists($key) { // Return variable $response = false; try{ // check if session is started if(empty(session_id())) throw new Exception('Session Error [0001]: Session is not started.'); // check if key is not null if(empty($key)) throw new Exception('Session Error [0002]: Session key cannot be empty.'); // get if exists $response = isset($_SESSION[$key]); } // Handle errors catch(Exception $e) { $this->_error->log($e->getMessage()); } return $response; } /** * @Function: public set_multi($params); * @Task: Set multiple session variables * @Params: * array params: Associative array of key/val pairs to be set as session variables * @Returns: * bool **/ public function set_multi($params) { // Return variable $response = false; try{ // check if session is started if(empty(session_id())) throw new Exception('Session Error [0001]: Session is not started.'); // check if key is not null if(empty($params)) throw new Exception('Session Error [0003]: Params array cannot be empty.'); $res = array(); foreach($params as $key => $val) { // check if key is not null if(empty($key)) throw new Exception('Session Error [0002]: Session key cannot be empty.'); // set session key $this->write($key, $val); $res[] = true; } // Check if all set succeded $response = count($params) == count($res); } // Handle errors catch(Exception $e) { $this->_error->log($e->getMessage()); } return $response; } /** * @Function: public get_all(); * @Task: Get all session variables * @Params: None * @Returns: * array **/ public function get_all() { // Return variable $response = false; try{ // check if session is started if(empty(session_id())) throw new Exception('Session Error [0001]: Session is not started.'); $res = array(); $keys = array_keys($_SESSION); foreach($keys as $key) { // get session key $res[$key] = $this->read($key); } // Check if all set succeded $response = $res; } // Handle errors catch(Exception $e) { $this->_error->log($e->getMessage()); } return $response; } /** * @Function: private write($key, $val); * @Task: write session variable * @Params: * mixed key: key of the session variable to be stored * mixed val: value of the session variable to be stored * @Returns: * void **/ private function write($key, $val) { $_SESSION[$key] = $val; } /** * @Function: private read($key, $default); * @Task: get session variable * @Params: * mixed key: key of the session variable to be retrieved * mixed default: default value, if session not found * @Returns: * mixed **/ private function read($key, $default = '') { if(!isset($_SESSION[$key])) return $default; else return $_SESSION[$key]; } }
求助
已尝试上述所有方案但问题仍未解决,恳请提供技术帮助,如需更多信息请告知。
内容的提问来源于stack exchange,提问作者Bence Gajdán
相关产品推荐
相关产品推荐

