Случайная потеря данных сеанса в PHP
Вот проблема, с которой мы столкнулись в течение последних нескольких недель.
1/Наша настройка
- PHP 5.4 + MySQL
- 2 выделенных сервера с балансировкой нагрузки
- Сессии реплицируются между двумя серверами с помощью memcached
- 3 приложения, работающие на этих серверах:
- Одно настраиваемое приложение, использующее настройки сеанса php по умолчанию.
- Другое настраиваемое приложение, использующее разные настройки сеанса (имя файла cookie, путь)
- Один Wordpress CMS
2/Проблема
Проблема возникает в нашем первом приложении.
Некоторые из наших пользователей сообщили, что они иногда отключаются через несколько минут (когда сеанс настроен на 3 часа). Это может произойти с ними несколько раз в тот же день, а затем без отключения в течение нескольких дней, но проблема всегда возвращается.
Пока доля влияния пользователей небольшая, но я хотел бы решить эту проблему до того, как она "распространится" на других пользователей.
Проблема, по-видимому, возникает в разных местах приложения, хотя мы определили 3 сценария, в которых происходит большинство ошибок:
- Некоторые из них включают представление формы (переменная $_SESSION изменена)
- Другие просто включают открытие всплывающей страницы без изменения данных сеанса.
Мы попытались воспроизвести различные сценарии, описанные пользователями: иногда мы были в состоянии, но большую часть времени у нас нет проблем, что затрудняет отладку.
Другие примечания:
- Проблема в последнее время, это приложение работало годами без каких-либо проблем.
- Это не похоже на нашу загрузку на сервере, потому что проблема все еще возникала во время летнего перерыва, когда наш трафик был низким.
- Это влияет только на один сеанс/пользователей за раз: все остальные пользователи, входящие в систему одновременно, не испытывают этой проблемы.
- Проблема возникла во всех браузерах (IE, Firefox, Chrome).
3/Технический анализ
Когда происходит отключение, пользователь перенаправляется на страницу "Ваш сеанс истек или у вас нет права на просмотр". Когда эта страница загружена, мы получаем техническое письмо с дампом переменной $_SESSION.
Когда сеанс заканчивается обычным способом, полученное письмо показывает, что переменная $_SESSION пуста (нормальное поведение).
Когда происходит неожиданное отключение, интересно, что $_SESSION не является полностью пустым: из 20 элементов, содержащихся в массиве, остается только один (всегда один и тот же).
Таким образом, это означает, что сеанс не истек, но недостаточно данных для "идентификации" пользователя, поэтому отображается страница "без прав". В качестве подтверждения, когда это происходит, мы можем проверить memcached, что этот сеанс по-прежнему содержит некоторые данные.
Это потенциальная проблема, которую мы выявили до сих пор, и что мы сделали, чтобы исключить их:
- Memcached указывает между 70 и 80% freespace, поэтому мы не думаем, что это проблема.
- Мы удалили Memcached и вернулись к использованию общего каталога NFS для файлов сеанса: проблема действительно ухудшилась. Это указывает на аппликативную ошибку, поскольку NFS медленнее записывает данные, потеря сеанса будет происходить чаще.
- Мы просмотрели все различные форумы (включая SO), рассказывая о потере данных сеанса PHP, и рассмотрели наш код соответственно. База кода большая, но мы использовали автоматические инструменты и скрипты, чтобы не пропускать файл.
- session_start() вызывается в начале каждой страницы.
- exit() вызывается после каждого заголовка ( "Местоположение..." )
- register_globals отключен
- Мы проверили возможные перерывы между нашими двумя другими приложениями и проблемными, хотя они не разделяют никакой обработки кода, базы данных или сеанса. Там ничего не было.
- Мы проанализировали наши журналы доступа во время разрывов, чтобы проверить шаблоны поведения: вам тоже не повезло.
Итак, мы понятия не имеем, что вызывает эту проблему, поскольку это происходит случайно, поэтому мои вопросы:
- Проблема может исходить из нашего кода: мы пропустили что-нибудь, чтобы проверить? Это решение кажется маловероятным, поскольку код работает в большинстве случаев для всех наших пользователей, но я все еще рассматриваю его.
- Проблема может возникнуть из другого приложения/процесса, который будет "пустым" частью массива переменных сеанса. Мы также рассмотрели код из других приложений, но не нашли ничего, что могло бы вызвать это.
И если другой процесс делает это, почему он должен только пустить несколько сеансов, а не все из них?
Спасибо за вашу помощь.
Ответы
Ответ 1
Я не думаю, что вы получите окончательный ответ на свой вопрос. Слишком много вероятных причин, и вы не указали какой-либо код.
Тем не менее, я предполагаю, что вы memcached.sess_locking отключены или если у вас есть пользовательская реализация сеанса - что она не вообще блокировать блокировку.
В конечном итоге это приводит к условию гонки между двумя одновременными HTTP-запросами.
Мое предположение основано на часто встречающемся плохом совете, чтобы как можно скорее отключить блокировки или освободить их, чтобы достичь более высокой производительности.
Ответ 2
Если эта проблема возникла "внезапно", проверьте, что изменилось. Выполняли ли вы какую-либо работу над приложением? Если это так, проверьте код (вы говорили об автоматизированных инструментах, поэтому я ожидаю, что там будет репозиторий, который позволит точно находить изменения кода).
Вы что-то изменили на сервере? Как обновить программное обеспечение, обновить/изменить оборудование, внести изменения в другие два приложения?
Одна вещь, которая появилась на ум, вы проверили диски, которые вы используете для кеширования? Это может быть поврежденная часть файловой системы. Это объясняет случайную часть пользователя.
Я пару вещей, которыми я всегда принадлежу:
- Попробуйте определить момент первого появления как можно более точный. В моей работе это иногда вызывает то, что кто-то говорит: "О да, может быть, это связано с тем, когда я изменил/обновил/создал то или это", чтобы это могло помочь. С другой стороны, иногда это может занять несколько дней, недель или более, прежде чем что-то заметят, поэтому начните расширять этот временной интервал, если ничего не появится.
- У вас уже есть пара сценариев, найти общий фактор. Если они не разделяют какой-либо код, прекратите смотреть туда. Если они ДОЛЖНЫ разделять поиск кода там. Конечно, совместное использование (часть) здесь может помочь нам в поиске.
- Сделайте организованный поиск. Обычно я выполняю основную проверку приложения, когда я работаю больше всего над приложением (или даже лучше, когда создаю его). Коллега проверяет окружающие приложения, которые могут повлиять на него. В вашем случае эти 2 других приложения. Наконец, наш sysadmin проверит наличие недавно установленного или обновленного программного обеспечения на сервере (серверах), и он также проверит с нашими сетевыми ребятами, если что-то изменит аппаратное или сетевое взаимодействие (для других это может быть хостинг-провайдер).
Ответ 3
Он может быть таким же простым, как плагин WordPress, который использует сеансы и вызывает либо session_name()
, либо session_id()
с другим значением, перекрывая ваши пользовательские приложения с настройками сеанса по умолчанию.
Так как WordPress сам не использует сеансы, плагины часто пишутся с точки зрения свободной воли с сеансами. Я просто выполнил поиск на тестовом сайте WordPress и нашел сеансы, используемые в плагине галереи, плагин для размещения фонового изображения на странице, плагин корзины покупок и плагин, который я писал, для переноса загруженного файла с одного admin страница к другому.