Пользователь
0,0
рейтинг
5 августа 2014 в 15:03

Разработка → Сессии в PHP — подводный камушек при асинхронных запросах

Небольшая предыстория.

У меня есть хобби-проект трекер.ру
Алгоритм такой: пользователь вводит поисковый запрос, этот запрос «на лету» ищет торренты на сторонних трекерах (рутор, рутрекер, tfile и тд).
Для параллельного поиска идет одновременно несколько аякс запросов, которые должны обработаться асинхронно.
Однако, запросы выполнялись синхронно. Если какой-то трекер долго не отдавал ответ, то остальные запросы подвисали и ждали ответа от подвисшего трекера. Общее время выполнение запросов равнялось сумме всех запросов. Хотя, по моим планам общее время должно было равняться самому долгому запросу.
Долго ломал голову, почему так. Грешил на HTTP pipelining. Но, причина оказалась намного банальней. Все дело в сессиях. Дело в том, что сессии в php консистентны и php не даст обратиться другому процессу к уже занятой сессии.

Потыкать и полюбоваться результатом можно тут. Посмотрите на общее время с сессиями и без.

Чем это плохо:

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

Как лечить?

Изменение хандлеров сессии не поможет. в комментах поправили, что это не так.
Как вариант — (об этом уже писали на хабре) в быстром участке кода воспользоваться сессией, а потом выполнить session_write_close();
Олег @Voenniy
карма
20,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (21)

  • +4
    По-умолчанию сессии хранятся в файлах, и вроде он также блокирует файл в начале сессии и разблокирует когда скрипт завершится. Можно попробовать сессии хранить в memcache, если доступ позволяет и нет других подводных камней. Или закрывать сессию, а потом выполнять «тяжелый» код
    • 0
      php-memcached тоже блокирует сессии
      php-memcache нет
  • –2
    Уже давно стало традицией использовать БД для работы с сессиями. Помниться с подобной проблемой столкнулся 5 лет назад.
    В качестве БД для сессий мне по нраву Mongo.
    • +9
      Использование другого хранилища не должно повлиять на саму работу сессий.
      • –1
        Верно сказано — "не должно", но увы — либо смирись, либо переделывай сам.
    • +2
      А как решаете вопрос консистентности данных в сессии?
      • +1
        Я не использую сессию для хранения подобных данных, там только данные по авторизации пользователя и текущей сесиии. Если нужна статистика — использую модуль статистики, для работы с временными данными — memcache или тот же mongo.
  • +2
    Хорошие такие грабельки…
    • 0
      Андрей привет
      На сколько я помню, с подобным мы ещё в РИА сталкивались. Но, тогда особого значения этому не придали.
      • +1
        Да, привет) вот и у меня что-то смутное в голове шевельнулось, решил написать.

        Мы не могли столкнуться с этим в РИА, т.к. там использовался Drupal 6. который хранит сессии в БД.

        Может ты столкнулся с этим, во время работы в РИА, но в другом проекте? :)
        • +1
          Повторюсь — место хранения сессии не должно влиять на саму работу механизма. Иначе нарушается целостность.
          Надо пробовать конечно, но мы с Денисом в скайпе пришли к выводу, что хранилище не влияет.
          • 0
            Какая целостность?

            Если два процесса считали данные сессии и изменили разные части, то из-за простоты реализации сессий запишет изменения только один. Тут выбор: либо не давать читать, либо затереть другие данные (как с базой данных).
  • +11
    Этот нюанс работы с сессией в ПХП уже давно всем известен, но с периодичностью 1-2 года эта тема снова всплывает…
  • +2
    «Изменение хандлеров сессии не поможет.»
    Прекрасно помогает изменение или переписывание хэндлеров на те, которые используют неблокируемые ресурсы для хранения сессионных данных.
    Имеет также смысл подумать, а нужна ли вам именно PHP-сессия, и для чего? Есть и другие инструменты.
    • +2
      А где можно хранить сессию, чтобы она не блокировалась?
      Я так понимаю, в этом случае консистентностью придется пожертвовать?

      По поводу сессий. Используется ZF 1. Там есть авторизация для админки, плюс флеш сообщения. От сессии конечно отказаться можно, но проще хранить сессию в неблокирующих хранилищах, либо использовать другие механизмы. Пока что решил вопрос session_write_close

      Я проектом почти не занимаюсь, времени нету, поэтому приходится искать самые не затратные по времени пути для решения.
      • +1
        Сначала надо определиться — нужна ли блокировка сессии. Это зависит от того, какие данные хранятся в сессии и как они используются.
        В своё время я для своего проекта пришёл к выводу, что от блокировки можно отказаться.
        Далее пользовал встроенные механизмы memcache(d), но в итоге остановился на использовании очень простых собственных хэндлеров. Получив при этом прозрачный для меня механизм сессии, вместо «чёрного ящика». Это оказалось плюсом, т.к. проект был нагружен (более миллиона pageview в сутки).
        Вот код:

        function my_session_open($save_path, $session_name) {
          return true;
        }
        function my_session_read($id) {
          $GLOBALS['session_read_return'] = Cache_Proxy::get($id);
          return $GLOBALS['session_read_return'];
        }
        function my_session_write($id, $data) {
          if ($GLOBALS['session_read_return'] == $data) return true;  // Если в пределах этого запроса данные не менялись - не записываем их обратно
          return Cache_Proxy::set($id, $data, 0, 36000);
        }
        function my_session_destroy($id) {
          Cache_Proxy::delete($id);
          return true;
        }
        function my_session_gc($maxlifetime) {
          return true;
        }
        function my_session_close() {
          return true;
        }
        session_set_save_handler('my_session_open', 'my_session_close', 'my_session_read', 'my_session_write', 'my_session_destroy', 'my_session_gc');
        register_shutdown_function('session_write_close');
        

        В качестве кэша используется APC (apc_fetch и apc_store). Работает очень быстро при любых нагрузках. Сбором мусора занимается АРС.

        PS: если бы завтра нужно было писать новый проект с нуля, я бы вообще не стал бы использовать такой механизм как сессии. По сути вся «сессия» — это уникальная кука на клиенте + данные на стороне сервера, однозначно связанные со значением этой куки. Так зачем всё усложнять и вносить в систему такой чёрный ящик, как стандартные сессии? (прошу воспринимать это не как совет, а как личное мнение, которое вполне может быть ошибочным)
        • 0
          Вы не стали бы использовать такой механизм как «сессии» или такой механизм как «стандартные сессии»?
          • +1
            Речь, конечно, о встроенных в PHP сессиях. Ведь для авторизации в любом случае надо хранить где то на сервере соответствие «авторизационная кука»-«userid». Однако, сегодня я бы сделал это без использования $SESSION. Сегодня я бы хранил авторизационные сессии в кэше (быстро) и дублировал в БД — на случай обнуления кэша.
            А хранить в сессии какие-то другие данные? Сходу и не придумаю сценария, какие это могут быть данные и почему они должны быть именно в сессии.
  • 0
    Здесь ( goo.gl/72s8iX ) конкретный рабочий пример (кусок кода) для реализации и предотвращения подобного.
    • 0
      Нуда, последний абзац как раз об этом.
      • 0
        Там REST full-stack описание. (а не только про сессии)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.