<rmcreative>

RSS

Блокирование сессий в PHP

21 июня 2012

Как-то я потратил довольно много времени, пытаясь понять, почему десяток асинхронных запросов к PHP-скрипту выстраивается в очередь вместо того, чтобы отрабатывать параллельно.

Чтобы показать наглядно, набросаем пару тестовых скриптов.

test1.php

<?php
session_start();
sleep(10);
echo '1';

test2.php

<?php
session_start();
echo '2';

Ничего необычного, правда?

Теперь запустим их параллельно. Воспользуемся jQuery:

$.get('/test1.php');
$.get('/test2.php');

В итоге получается вот такая картинка (это вкладка Net из Firebug):

test1.php заблокировал работу test2.php.

При использовании сессий «из коробки», данные хранятся в одном единственном файле, который оказывается заблокированным с момента вызова session_start и до окончания работы скрипта.

В том случае, если сессия вам нужна только для чтения, или есть возможность записать всё необходимое перед медленной частью скрипта, можно её закрыть явно при помощи session_write_close():

<?php
session_start();
session_write_close();
sleep(10);
echo '1';

В этом случае мы получим желаемую картину:

Если же необходимо писать в сессию после медленных операций, придётся сменить «коробочную» сессию на свою реализацию с неблокируемым хранилищем, таким, например, как база данных.

Стоит отметить, что если при этом не позаботится о race condition, можно наступить на хорошие такие грабли.

Материалы:

Комментарии RSS

  1. №6364
    Артём Курапов
    Артём Курапов 21 июня 2012 г., 14:23:58

    Правильно ли я понимаю что ниже можно опять открыть сессию и продолжить работу, при этом если несколько параллельных скриптов будут с сессией работать, то получится абракадабра в итоге (данные будут сливаться в одну сессию со всех скриптов по принципу кто последний записал, то и будет)?

  2. №6365
    Максим
    Максим 21 июня 2012 г., 14:37:31

    Из доков по пхп: 2Артем, Session data is usually stored after your script terminated without the need to call session_write_close(), but as session data is locked to prevent concurrent writes only one script may operate on a session at any time. When using framesets together with sessions you will experience the frames loading one by one due to this locking. You can reduce the time needed to load all the frames by ending the session as soon as all changes to session variables are done.

  3. №6366
    Sam
    Sam 21 июня 2012 г., 15:02:57

    Артём Курапов, да, но открывать повторно не стоит не только по этой причине. При повторном открытии сесси в рамках одного запроса засоряется cookie и, в конечном итоге, убивается один известный браузер.

  4. №6367
    Давид Мзареулян
    Давид Мзареулян 21 июня 2012 г., 15:34:17

    Собственно, правила простые: прочёл из сессии — закрыл, записал в сессию — снова закрыл. Сильно ускоряет отзывчивость сайта.

    Ни с какими «известными браузерами» проблем при этом нет.

  5. №6368
    Sam
    Sam 21 июня 2012 г., 16:00:37

    Ни с какими «известными браузерами» проблем при этом нет.

    Разве? https://bugs.php.net/bug.php?id=38104

  6. №6369
    Давид Мзареулян
    Давид Мзареулян 21 июня 2012 г., 16:13:08

    Sam Вообще да, Вы отчасти правы — я посмотрел у себя, действительно, каждый session_start вызывает выдачу новой куки. Но у меня не больше 2-3 таких операций на странице, так что проблем с браузерами это не вызывает.

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

  7. №6371
    Psih
    Psih 21 июня 2012 г., 17:01:41

    Ты можно сказать везунчик - столько в разработке и только вот наткнулся на эту фигню?

    Да, весёлая штука такая, многих ставит в тупик. Интересно, в Zend Certification нет ли такого вопроса. Очень каверзный был бы вопрос :)

  8. №6372
    Sam
    Sam 21 июня 2012 г., 18:45:34

    Psih, на самом деле наткнулся уже очень давно. Просто написал только сейчас. В Zend Certification мне не попадался и при подготовке тоже не всплывал.

  9. №6373
    faiwer
    faiwer 21 июня 2012 г., 19:59:39

    Проверил локально. Второй скрипт вернулся сразу же (5мс), первый ждал 10.01. php5.3, apache2.2, xubuntu. В конфигах ничего специфического не трогал. Однако пока писал авторизацию вручную не раз удалял сессии, и заметил что там далеко не 1 файл, а столько - ско$.get('/test1.php'); $.get('/test2.php');лько сессий.

  10. №6382
    Виталий
    Виталий 25 июня 2012 г., 18:08:46

    проблема довольно известная и решается переводом сессий на memcached или базу... через memcached делается буквально в одну строчку кода...

  11. №6384
    Sam
    Sam 26 июня 2012 г., 1:41:21

    Виталий, это если не заботится о race condition.

  12. №6385
    RusAlex
    RusAlex 27 июня 2012 г., 10:43:38

    Никто еще не написал extension для Yii ? перевод сессий в mysql.

  13. №6386
    RusAlex
    RusAlex 27 июня 2012 г., 10:44:50

    извиняюсь CDbHttpSession уже есть

  14. №8206
    Хранитель
    Хранитель 29 июля 2013 г., 11:20:36

    Стоит так же упомянуть, что хранение данных в сессиях, кроме самых простых случаев, не самое лучшее архитектурное решение, ограниченное в управляемости. Имхо, сессиям стоит оставить только id текущего пользователя.

  1. Почта опубликована не будет.

  2. Можно использовать синтаксис Markdown или HTML.

  3. Введите ответ в поле. Щёлкните, чтобы получить другую задачу.