Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Оптимизация кеша контента. Блокировки flock() в PHP.  (Прочитано 13220 раз)
0 Пользователей и 1 Гость смотрят эту тему.
RXL
Технический
Администратор

Offline Offline
Пол: Мужской

WWW
« : 28-12-2012 10:03 » 

Как обычно работает кеш контента: берем данные по ключу из кеша и, если их нет, генерируем новый контент и сохраняем в кеше.
Недостаток: если во время генерации нового контента те же данные понадобятся другому процессу, он повторит ту же процедуру.
Если за время генерации контента поступит много запросов, все они, кроме первого, будут делать пустую работу и мешать друг другу, занимая ресурсы, напрягая БД, обращаясь на другие серверы и т.д.
Чтобы избежать этого, надо при промахе кеша дать понять остальным процессам, что контент скоро будет — надо немного подождать. Я реализовал вот такую логику:

Код: (PHP)
class Cache
{
    protected $cache;
    protected $locker;
    protected $defaultTtl = 120;
    protected $magicTtl = 30;
    protected $magicText = '<!-- magic string-->';
    protected $loopDelay = 0.1;

// ...........

    public function fetch($key)
    {
        while (true)
        {
            if ($this->cacher->exists($key))
            {
                $data = $this->cacher->read($key);

                if ($data != $this->magicText)
                    return $data; // данные есть к кеше - выходим

                usleep(1E6 * $this->loopDelay); // разгрузим процессор
            }
            else
            {
                $this->locker->lock();

                if (!$this->cacher->exists($key)) // тест, что между cacher->exists и locker->lock не вклинился кто-то другой
                {
                    $this->cacher->write($key, $this->magicText, $this->magicTtl); // застолбил ключ
                    $this->locker->unlock();
                    return null; // выходим для генерации нового контента
                }

                $this->locker->unlock();
            }
        }
    }
}

Недостаток в том, что на практике она приводит к блокировке процессов. Причем не сразу, а в часы наибольшей нагрузки. Не могу понять, как это происходит — не вижу изъяна в логике.
Класс блокировки такой:

Код: (PHP)
class FileLocker extends BaseLocker // BaseLocker - абстрактный класс
{
    protected $fd;
    protected $locked;

    public function __construct($object = false)
    {
        $lockfile = $object ? $object : __FILE__; // выбираем файл для блокировки

        if (!file_exists($lockfile))
            file_put_contents($lockfile, 'lockfile'); // новый файл

        $this->fd = fopen($lockfile, 'r'); // открываем для чтения
        $this->locked = false;
    }

    public function lock()
    {
        flock($this->fd, LOCK_EX); // эксклюзивная блокировка
        $this->locked = true;
    }

    public function unlock()
    {
        flock($this->fd, LOCK_UN); // снятие блокировки
        $this->locked = false;
    }

    public function isLocked()
    {
        return $this->locked;
    }

    public function __destruct()
    {
        if ($this->locked)
            $this->unlock(); // принудительное снятие блокировки

        fclose($this->fd); // закрываем файл
        $this->fd = null;
    }
}

Проверил: файл создается. Проверка по lsof показывает, что все заблокированные процессы держат этот файл открытым.

Прошу посмотреть свежим взглядом — мой уже замылился. Что смог, уже исправил.
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #1 : 28-12-2012 10:49 » 

Переведи

Код:
                if ($data != $this->magicText)
                    return $data; // данные есть к кеше - выходим

                usleep(1E6 * $this->loopDelay); // разгрузим процессор

Кто изменит $this->magicText, в какой момент?
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
RXL
Технический
Администратор

Offline Offline
Пол: Мужской

WWW
« Ответ #2 : 28-12-2012 11:22 » 

magicText никто не меняет — меняется результат запроса данных из кеша. Считай, что magicText — константа.
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #3 : 28-12-2012 11:38 » 

вот я вижу тонкое место
 
Код:
       while (true)
        {
            if ($this->cacher->exists($key))
            {
                $data = $this->cacher->read($key);

                if ($data != $this->magicText)
                    return $data; // данные есть к кеше - выходим

                usleep(1E6 * $this->loopDelay); // разгрузим процессор

Т.е. ждем и спим пока не появится $data
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
RXL
Технический
Администратор

Offline Offline
Пол: Мужской

WWW
« Ответ #4 : 28-12-2012 12:24 » 

Да, логика такая. Временная запись (magicText) в кеше имеет TTL = 30. Т.е. если ожидание по какой-то причине было напрасное, то не позднее чем через 30 секунд запись удалится и кто-то из ожидавших должен заняться генерацией контента. Т.е. тут нет блокировки, а есть добровольное ожидание готового контента. Еще одним предохранителем является ограниченное время на работу PHP.

Вторая часть этой функции — выбор единственного исполнителя. Тут есть блокировка в ядре, но по логике она кратковременная.
« Последнее редактирование: 28-12-2012 12:39 от RXL » Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
RXL
Технический
Администратор

Offline Offline
Пол: Мужской

WWW
« Ответ #5 : 28-12-2012 12:48 » 

Еще одна часть программы: кеш APC.

Код: (PHP)
class APCCacher extends BaseCacher
{
    public function exists($key)
    {
        return apc_exists($key);
    }

    public function read($key)
    {
        $success = false;
        $data = apc_fetch($key, $success);
        return $success ? $data : null;
    }

    public function write($key, $data, $ttl)
    {
        return apc_store($key, $data, $ttl);
    }
}
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #6 : 28-12-2012 13:40 » 

del
« Последнее редактирование: 28-12-2012 13:43 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
RXL
Технический
Администратор

Offline Offline
Пол: Мужской

WWW
« Ответ #7 : 28-12-2012 14:09 » 

Dimka, в смысле?
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
RXL
Технический
Администратор

Offline Offline
Пол: Мужской

WWW
« Ответ #8 : 28-12-2012 14:18 » 

Встроил в класс логирование. Предварительный анализ говорит, что дело не в самом классе, а во внешнем к нему приложении. Ситуация следующая: данные по одному ключу из кеша запрашиваются, но не записываются. Соотв., места в программе, где эти данные запрашиваются, обречены на ожидание данных, которые никто не запишет. Когда magicTtl истекает, один из ожидающих процессов выходит из ожидания, а остальные продолжают ждать.

Логично, что использование такого класса с ожиданием требует специфичного подхода.
« Последнее редактирование: 28-12-2012 14:24 от RXL » Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
RXL
Технический
Администратор

Offline Offline
Пол: Мужской

WWW
« Ответ #9 : 02-01-2013 21:37 » new

Пять дней — полет нормальный.
Вот так порой ищешь проблему там, где ее нет...
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines