Re: proxy cache, cache miss и byte range

c0re nginx-forum на nginx.us
Сб Ноя 26 00:16:30 UTC 2011


Доброй ночи

При возникновении подобных проблем (а
вы далеко не первый, столкнувшийся с
этой вопиющей несправедливостью),
рекомендации ведущих собаководов =] не
изобилуют разнообразием.
Как правило, во всех подобных случаях,
они являют собой предложение окунуться
с головой в чрезвычайно увлекательное
и полное опасностей приключение,
состоящее из нескольких этапов: 1)
ознакомиться с термином busylock 2)
реализовать его собственными силами
(или подождать преследующих вас
товарищей, добравшихся, также как и вы,
до второго уровня).
Если с первым этапом все, как правило,
гладко, то второй предварительно
требует начальной военной подготовки
(или наличия желания и свободного
времени реализовывать эти busylock'и у
старшего офицерского состава).
Если принять во внимание, что
интересоваться таким поведением nginx'а
начали, мягко говоря, не вчера, ровно
как и получать ответы про busylock'и, то, в
какое кол-во времени может вылиться
занятие выжидательной позиции на
втором этапе, наверное не взялась бы
предсказать даже Ванга, будь она жива.
Тут уже самое время начать грустить, но
не все так безнадежно.

К счастью, проблему можно решить проще
и силами самого nginx'а, НО... с некоторыми
оговорками.
1) С файловой системой работает только 1
nginx ( в смысле файловая система не
расшарена между несколькими серверами,
где на каждом летит свой nginx ).
2) Использовать proxy_store, вместо proxy_cache.
Это ограничивает возможности по
кэшированию. Теоретически возможно
реализовать эту схему и для proxy_cache, но
над этим надо ломать голову отдельно.
Несколько слов об этом в конце.
3) При самом первом запросе файла с
другого сервера, которого еще нет в
локальном кэше у запрашиваемого
сервера, запрещен Range:bytes= запрос.
4) Перерасход трафика между узлами для
горячего контента, если во время
начального скачивания файла в кэш,
будут поступать пареллельные запросы
на скачивание этого же файла. Этот
пункт можно обойти, но не очень
корректно. Например кормить таких
клиентов редиректами, пока файл не
окажется в локальном кэше.

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


Вот упрощенный вариант конфига: 

http {
    ...
    limit_zone perUri $uri 10m;
    limit_zone perIpUri $binary_remote_addr$uri 10m; # не
обязательно
    open_file_cache_errors off;
    proxy_temp_path /storage/cache-v2/proxy 1 2;
    ...
}

server {
    ...

    # обращение как к origin'у
    # просто отдаем локальные файлы
    location /storage/edge-current/ {
        alias /storage/local/;
    }

    # обращение к файлам, для которых мы
не origin
    # далее foreach $x in list_of_edges
    location /storage/edge-$x/ {
        root /storage/cache/edge-$x;   # путь к
локальному кэшу
        try_files $uri @proxy-edge-$x; # если в локальном
кэше запрашиваемого файла нет, то дуем
в @proxy-edge-$x
    }
    location @proxy-edge-$x {
        limit_conn perUri 1;           # только один
запрос к конкретному URI
        root /storage/cache/edge-$x;   # путь к
локальному кэшу
        proxy_set_header Range "";     # запрещаем Range:bytes=
запросы в этой секции. иначе в store
ничего не ляжет
        proxy_set_header User-Agent "Edge-$x: proxy"; #
необязательно, для дебага и статистики
        proxy_store on;
        proxy_buffering on;
        proxy_ignore_client_abort on;  # если клиент
обрывает соединение, то файл все равно
дотянется и ляжет в локальный кэш
        proxy_pass http://edge-$x.domain.com; # проксируем
на origin
        error_page 503 = @stream-edge-$x; # если сработал
limit_conn (уже выполняется скачивание
этого файла в локальный кэш),
                                          # дуем в @stream-egde-$x
    }
    location @stream-edge-$x {
        proxy_set_header User-Agent "Edge-$x: stream"; #
необязательно, для дебага и статистики
        proxy_buffering off;           # прокачиваем файл
через себя напрямую, со скоростью
клиента, proxy_temp_file не создается
                                       # с радостью
разрешаем Range:bytes=, так как чем меньше
траффика, тем лучше

        #limit_conn perIpUri 1;        # если вы уверены,
что существенная часть ваших
пользователей не сидят за NATом
                                       # и не идут с
одного и того же ip адреса, можно
ограничить кол-во сессий в этой секции
        tcp_nodelay on;
        proxy_pass http://edge-$x.domain.com; # проксируем
на origin
    }
    # /foreach

    ...
}

Для удобства, копия конфига на
http://pastie.org/private/b0bcixwojzgtp6cxiuntxw

Используем nginx 1.0.10
Собственно все просто.
A) Смотрим файл в локальном кэше, если
нашли - то отдаем.
B) Если не нашли, идем качать с origin'а на
максимальной скорости, ложить в кэш и
параллельно отдавать клиенту. HEAD
запросы проксируются напрямую и к
скачиванию файла не приводят.
C) Если во время работы 'B' поступил
запрос на этот же файл, мы просто
проксируем эти запросы напрямую
клиенту в обход буферов и кэшей.
   Вы можете задать другое поведение для
секции 'C'. Например кормить клиента
редиректами, пока не освободится
секция 'B' и потом отдать
   файл с локального кэша, если клиент
сможет дождаться.
Также можно усложнить конфиг, чтобы
файл качался не сразу с origin'а, а
поискать его сначала в локальных кэшах
на соседних узлах.

В общем, может быть решение и не
идеальное, но пока все работает почти
замечательно, что не может не радовать.
Есть один досадный момент - несмотря на
proxy_ignore_client_abort on, файлы в proxy_temp_files все
таки периодически залипают.
Поэтому приходится заботиться о
периодической чистке протухших
временных файлов: find -amin +60 вполне
справляется.
Может кто-нибудь в состоянии пролить
свет на такое поведение nginx'а? А еще
лучше workaround. Был бы весьма признателен.

Теперь о причине, почему нужно
использовать proxy_store, вместо proxy_cache.
При использовании proxy_store, мы в конфиге
nginx'а имеем возможность проверить,
закэширован ли уже такой файл в
локальном кэше.
Способов проверки силами самого nginx'а
для proxy_cache мне обнаружить не удалось.
Это можно сделать либо внешним
скриптом, либо, может быть, через
скриптовый модуль, например lua.
Если кто-то может подсказать рабочее
решение для определения, опять же буду
весьма благодарен.
proxy_store имеет как преимущества -
например горячий контент можно заранее
разложить по узлам самостоятельно
(хотя и для proxy_cache можно, но сложнее),
так и недостатки - придется
самостоятельно следить за свободным
местом в локальном кэше и чистить
наименее востребованные файлы.

Вот примерно так. Будет здорово, если
это кому-нибудь поможет.

Posted at Nginx Forum: http://forum.nginx.org/read.php?21,219025,219080#msg-219080



Подробная информация о списке рассылки nginx-ru