• Записи 162
  • Теги 66
  • Комментарии 330

Компьютерное

Сессии в PHP и нагрузка на сервер

На первый взгляд кажется, что работать с сессиями в PHP предельно просто. Достаточно написать где-нибудь в начале скрипта такой код:


session_name('MySessId'); // задаем имя cookie или параметра, в котором хранится идентификатор сессии
session_set_cookies_params(24*3600,'/','xpro.su'); //если нужно, задаем домен, путь и время хранения для cookie сессии
session_start(); // а теперь запускаем саму сессию

и в глобальной переменной $_SESSION можно будет хранить нужные значения, которые будут доступны при каждом обращении пользователя к странице! На малых сайтах такое решение работает без проблем, но оно не будет масштабируемым: как только сайт достигнет нескольких десятков тысяч страниц или посещаемости в десятки тысяч пользователей в сутки, может случиться так, что сайт будет открываться весьма и весьма медленно.


Чтобы разобраться, почему такое происходит, рассмотрим, как же работают сессии в PHP. При вызове session_start в самый первый раз генерируется ее идентификатор сессии, который передается пользователю либо в виде cookie, либо в виде параметра URL, который добавляется ко всем локальным ссылкам на странице, выдаваемой пользователю. На сервере же создается файл, имя которого совпадает с этим идентификатором. В конце выполнения скрипта (или при вызове session_write_close()) данные, помещенные в $_SESSION, сериализуются и записываются в этот файл. Когда пользователь следующий раз обращается к скрипту, то от него либо в cookies, либо в параметрах запроса передается идентификатор сессии (в приведенном выше примере этот параметр будет называться MySessId), проверяется, существует ли файл сессии с таким именем, и если да, происходит его блокировка, считывание и десериализация, после чего данные, сохраненные при прошлом вызове, снова попадают в $_SESSION.


Поисковые роботы при индексации сайта не сохраняют установленные сайтом cookies или параметры с идентификатором сессии. В результате каждое обращение поискового робота к любой странице сайта будет приводить к создании новой сессии с новым идентификатором. То есть, если сайт содержит десять тысяч страниц, то будет создано десять тысяч файлов сессий! В результате даже простое открытие файла из каталога с таким количеством файлом становится весьма и весьма медленной операцией. Если проект работает на отдельном сервере, частично эту проблему можно решить, задав в настройках PHP хранение сессий не в файлах, а в СУБД или shared memory, но все равно нагрузка будет достаточно существенная из-за операций блокировки и сериализации. Также если разрешена передача идентификатора сессии через GET-запрос, поисковые системы запоминают URL с этим параметром, что тоже создает определенные проблемы (хотя Яндекс и Google вроде бы сейчас научились обнаруживать и отбрасывать идентификатор).


Но всегда ли нужно создавать сессию прямо при первом же обращении пользователя к странице? Для большинства проектов это не так. Например, на форуме необходимость сессии возникает тогда, когда пользователь решает войти под своим именем, а в Интернет-магазине — при добавлении товара в корзину. До этого для показа тем в гостевом режиме или описаний товаров сессия совершено не требуется. Отсюда вывод: сессию пользователя нужно создавать только тогда, когда в ней возникает реальная необходимость. Но с другой стороны, если пользователь вошел на сайт или добавил что-то в корзину, то нужно на каждой странице показывать ему, что форум его узнал или что в корзине у него что-то есть. Таким образом алгоритм действий получается такой: не создаем сессию, пока она нам не потребовалась, но если уж создали, то возобновляем ее (т.е. читаем ее данные в $_SESSION) при каждом обращении к скрипту. Для его реализации подойдет следующая функция:


function session($force=false) {
 $s_name='MySessId'; // имя сессии
 static $started;
 session_name($s_name);
 session_set_cookie_params(24*60*60,'/','xpro.su'); // параметры cookies
 /* Если от пользователя пришел параметр или cookie с идентификатором  сессии,
  значит, сессию уже создали и нужно ее возобновить,
если же  $force==true, то это явное указание на необходимость создания сессии */
 if (isset($_REQUEST[$s_name]) || isset($_COOKIE[$_name])  || $force) {
   /* Переменная $started нужна для избежания повторного вызова session_start
 (например, если добавляем в корзину еще один товар при уже созданной сессии),
  чтобы не генерировалось лишнее предупреждение. */
   if (!$started) { session_start(); $started=1; }
 }
}

В скрипте эту функцию нужно вызывать дважды: с параметром false в начале скрипта для восстановления сессии, если она уже была создана, и с параметром true при выполнении тех действий, которые требуют обязательного создания сессии (вход пользователя на форум, добавление товара в корзину и т.п.).


Знание таких особенностей и использование предложенной функции позволит не только снизить нагрузку при прохождении по сайту поисковых роботов, но и сэкономить при этом по несколько сотен байтов на каждый запрос, так как без необходимости не будет передаваться заголовок Set-cookie или параметры с идентификатором сессии.

2 комментария:

Нет
Гость
0

Во-первых, 'static $started;' - это омерзительная практика. Для этой цели есть 'session_status()':
php.net/manual/ru/function.session-status.php

Во-вторых, вместо страдания вокруг ручного вызова самопальной функции в сотне мест приложения достаточно в инициализационном коде вызвать session_save_path() с указанием на директорию, имя которой строится исходя из текущей даты в зависимости от загрузки ресурса. То есть, каждый час/сутки/месяц создавать новую директорию и делать хард-линки на файлы сессий из предыдущей директории. Правда, механизм очистки сессий придется реализовывать самостоятельно, но уж лучше все что касается сессии собрать в одном месте, чем размазывать по коду вызовы 'session()'
php.net/manual/ru/function.session-save-path.php

4X_Pro
0
Для этой цели есть 'session_status()':

Которая появилась только в PHP 5.4, если верить документации. (Обратите внимание на дату написания заметки.)
Во-вторых, вместо страдания вокруг ручного вызова самопальной функции в сотне мест приложения достаточно

То, что вы предлагаете, уменьшит только время торможения из-за большого количества файлов сессий в файловой системе, но не решит проблему создания ненужных сессиий (и связанных с этим затрат ресурсов и лишнего траффика на передачу cookie с ее идентификатором и т.п.), ради чего все и делалось. Одобрить это только ради того, чтобы «собрать весь код, касающийся сессии, в одном месте» я не могу. Это путь к тому, что описано вот в этой статье.
Кроме того, такой подход неприменим на shared-хостингах без доступа к crontab.
Впрочем, справедливости ради, могу заметить, что многое зависит от специфики приложения. Скажем, в движке, на котором работает этот сайт, есть всего около десятка мест, где требуется вызов этой самой session (авторизация, запоминание настроек фильтра, вывод сообщений о резульатате последнего действия при редиректе), но в других приложениях сессия может требоваться более чем в 90% действий, и тогда создание ее сразу, а не в момент возникновения необходимости, будет оправдано.
Написать комментарий


Задать вопрос