72  /  97

Блокировки сессий в веб-проектах

Просмотров: 26784
Дата последнего изменения: 23.09.2021
Сложность урока:
3 уровень - средняя сложность. Необходимо внимание и немного подумать.
1
2
3
4
5

Введение

В связи с бурным ростом и усложнением Front-End аяксами, табами в браузере и тому подобным, все чаще проявляется проблема блокировки сессий во время эксплуатации сайтов на PHP. PHP по умолчанию создает для сессии файл и процесс эксклюзивно его блокирует. Остальные процессы, пытающиеся открыть сессию - выстраиваются в очередь. Не всегда логика приложения, особенно если она сложная, позволяет эффективно ограничить время блокировки конкурирующих за сессию процессов. Ситуация усугубляется еще тем, что 3-5 подобных клиентов способны быстро забить зависшими и простаивающими в ожидании процессами PHP-worker'ы и сайт "повисает".

К сожалению, разработчики/сисадмины не всегда могут сразу понять, что дело в блокировке сессии — и ищут проблемы в других частях проекта, теряя время.

  Диагностика

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

<?php
session_start();
sleep(30);// только для одного скрипта
?>

Страницы будут дожидаться освобождения сессии (30 секунд) и займет это много времени, при этом будут забиты слоты веб-сервера. Примерно то же самое случается, когда AJAX запускает в сессии веб-клиента тяжелую задачу и остальные AJAX и другие элементы интерфейса зависают в ожидании (либо когда открывается несколько вкладок под одной авторизацией).

Процессы веб-сервера, в данном случае httpd, но то же самое происходит и с PHP-FPM — пытаются эксклюзивно заблокировать файл сессии, что видим с помощью lsof:

lsof -n | awk '/sess_/'
httpd      7079    nobody   52uW     REG           8,1        2216             809832 /tmp/sess_f629a13b4b0920a21042c86d17f4a6a6
httpd     10406    nobody   52u      REG           8,1        2216             809832 /tmp/sess_f629a13b4b0920a21042c86d17f4a6a6
httpd     10477    nobody   52u      REG           8,1        2216             809832 /tmp/sess_f629a13b4b0920a21042c86d17f4a6a6
httpd     10552    nobody   52u      REG           8,1        2216             809832 /tmp/sess_f629a13b4b0920a21042c86d17f4a6a6
httpd     11550    nobody   52u      REG           8,1        2216             809832 /tmp/sess_f629a13b4b0920a21042c86d17f4a6a6
httpd     11576    nobody   52u      REG           8,1        2216             809832 /tmp/sess_f629a13b4b0920a21042c86d17f4a6a6

Обратите внимание на 4 колонку. Число - это номер дескриптора файла в процессе, а дальше - тип блокировки. "uW" - веб-сервер заблокировал файл эксклюзивно для записи. Остальные - в ожидании. Как только процесс 7079 закончит свою работу, блокировку "uW" возьмет другой процесс. В это время, понятно, выстраивается очередь и веб-интерфейс заметно тормозит. Если несколько процессов заблокируют сессию на единицы секунды - интерфейс вообще "замрёт".

Посмотрим теперь с другой стороны, чем занимаются процессы:

ps -e -o pid,comm,wchan=WIDE-WCHAN-COLUMN | grep httpd
7079 httpd           -
10406 httpd           flock_lock_file_wait
10477 httpd           flock_lock_file_wait
10552 httpd           flock_lock_file_wait
11550 httpd           flock_lock_file_wait
11576 httpd           flock_lock_file_wait

Во второй колонке видим, что все, кроме одного, заняты в функции flock_lock_file_wait.

strace -p 10406
Process 10406 attached - interrupt to quit
flock(52, LOCK_EX)

Они заняты в системном вызове c запросом эксклюзивной блокировки.

LOCK_EX  Place an exclusive lock.  Only one process may hold an
                    exclusive lock for a given file at a given time.

  Практика

Чтобы постоянно отслеживать на веб-серверах появление такого "паровозика", забивающего PHP-воркеры, можно использовать скрипт, написанный на AWK:

/sess_/ {
    load_sessions[$9]++;
    if (load_sessions[$9]>max_sess_link_count){
        max_sess_link_count = load_sessions[$9];
        max_sess_link_name = $9;
    };

    if ($4 ~ /.*uW$/ ){ locked_id[$9]=$2 };
}

END {

    print max_sess_link_count, max_sess_link_name,locked_id[max_sess_link_name];

    if (locked_id[max_sess_link_name] && max_sess_link_count>3) {
        #    r=system("kill "locked_id[max_sess_link_name]);
        #    if (!r) print "Locking process "locked_id[max_sess_link_name]" killed"
        system("ls -al "max_sess_link_name);
    }

}

Запускается так:

lsof -n | awk -f sess_view.awk
5 /tmp/sess_f629a13b4b0920a21042c86d17f4a6a6 24830

Скрипт отображает длину "паровозика" и процесс — создающий затор.

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

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

5
Курсы разработаны в компании «1С-Битрикс»

Если вы нашли неточность в тексте, непонятное объяснение, пожалуйста, сообщите нам об этом в комментариях.
Развернуть комментарии