Хотя на первый взгляд PHP-сессии кажутся довольно простыми, недооценка их сложности может привести к некоторым трудным для отслеживания проблемам.
Одна из таких вещей — механизм блокировки сессии. Их цель — защитить данные сессии от перезаписи другими экземплярами сценария или другими сценариями, работающими одновременно.
Когда вы вызываете session_start () (или сразу после запуска сценария, если включена опция session.auto_start), файл сессии блокируется от записи, и по умолчанию он разблокируется только после остановки сценария. Это означает, что любой другой сценарий, пытающийся одновременно получить доступ к этому файлу сессии, должен ждать, пока файл сессии не будет разблокирован.
Важно понимать, что каждый посетитель получает уникальный идентификатор сессии и получает отдельный файл хранения сессии.
Блокировка сессии становится проблемой в том случае, если вы выполняете огромное количество запросов AJAX. В этом случае они, в конце концов, не будут асинхронными, поскольку каждый запрос должен будет ждать, пока файл сессии не будет разблокирован предыдущим запросом.
Другая ситуация, когда блокировка сессии становится проблемой, это долго выполняющиеся сценарии. Если у вас есть сценарий, который выполняется в течение 30 секунд, важно разблокировать файл сессии, чтобы другим сценариям не приходилось ждать 30 секунд, прежде чем они смогут приступить к выполнению своей работы.
Есть много хороших статей, написанных по этому вопросу, и одну из них, написанную Маттиасом Джениаром, я считаю наиболее полной.
Если вы ищете быстрое решение и думаете: «Я просто сохраню свои сессии в memcached», вы будете разочарованы. Конфигурация memcached по умолчанию использует ту же, безопасную логику, как описано выше: сессии блокируются, как только их использует один вызов PHP.
Если вы используете расширение Memcached в PHP, вы можете установить для memcached.sess_locking значение «off», чтобы избежать блокировки сеанса. Gо умолчанию выставленно «on», что делает его действующим как обычный обработчик сессии.
Если вы используете Redis, вам повезло (нет), потому что обработчик сессий redis еще не поддерживает блокировку. С Redis в качестве бэкэнда хранилища сессий не будет блокировок. (зато будет куча геммороя. Об этом есть в статье, ссылку на которую я давал выше. см. PHP Session locking: the problem it’s trying to fix)
Если вы используете MySQL в качестве бэкэнда сессии (как это делает Drupal), у вас есть своя реализация, просто потому что нет расширения PHP, которое позволяло бы использовать MySQL в качестве хранилища сессий. Поэтому где-то в вашем PHP-коде есть вызов функции session_set_save_handler (), который описывает, какой класс или метод отвечает за чтение и запись данных вашей сессии. Это означает, что ваша реализация решает, будут ли сессии блокироваться или нет, как говорится — всё в ваших руках.
Помимо описанных выше двух проблем с которыми частенько сталкиваются, есть еще одна, не совсем очевидная проблема, об которую запинаются разработчики, и отладка которой может быть довольно сложной, если вы не знаете, где искать. Это происходит, когда ваш PHP-скрипт отправляет HTTP-запрос на тот же веб-сайт, используя свои собственные куки.
Вот пример кода (caller.php):
session_start(); $_SESSION['var1'] = 'value1'; // ... $curl = curl_init('http://samedomain.com/remote.php'); curl_setopt($curl, CURLOPT_COOKIE, http_build_query($_COOKIE)); curl_exec($curl); curl_close($curl);
Сначала все выглядит хорошо. Вашему remote.php нужны куки, поэтому вы отправляете их. Это грязный трюк, и вы никогда не должны его использовать, но некоторые люди так делают. Дьявол же кроется в деталях.
Если вы внимательно посмотрите на свои куки, вы заметите, что они содержат значение идентификатора сессии ($ _COOKIE [‘PHPSESSID’]). Скрипт remote.php принимает идентификатор сессии как свой собственный и пытается получить доступ к тому же файлу сессии, но не может, потому что caller.php все еще работает, поэтому файл сессии заблокирован.
Теперь у нас есть мексиканское противостояние: caller.php ждет ответа от local.php, который, в свою очередь, ждет caller.php, чтобы разблокировать файл сессии. Это может быть решено с помощью тайм-аута cURL, но по умолчанию оно не определено. Так что, если вы явно не установили его, вам нужно подождать, пока caller.php достигнет max_execution_time. И если лимит высок (или полностью отключен), у вас есть проблема.
Прежде всего, вы никогда не должны запускать cURL без установленного времени ожидания:
curl_setopt ($ curl, CURLOPT_TIMEOUT, 5);
Это не решит проблему, но, по крайней мере, вы получите возможность правильно обработать ошибку cURL.
Чтобы действительно решить проблему, вам нужна функция session_write_close (). Единственная цель функции — разблокировать файл сессии, поэтому, как только вы закончите запись в сессию, вам нужно будет вызвать эту функцию и позволить следующему сценарию свободно работать.
Давайте посмотрим окончательный код сейчас:
session_start(); $_SESSION['var1'] = 'value1'; session_write_close(); // ... $curl = curl_init('http://samedomain.com/remote.php'); curl_setopt($curl, CURLOPT_COOKIE, http_build_query($_COOKIE)); curl_setopt($curl, CURLOPT_TIMEOUT, 5); $result = curl_exec($curl); curl_close($curl);
Вот и всё. Просто вызовите функцию session_write_close () и расслабьтесь. Или попробуйте исправить другие проблемы в своих проектах, что на самом деле кажется более вероятным. А еще лучше — попробуйте в конце-концов выспаться ;)