• Новые темы в этом разделе публикуются автоматически при добавлении файла в менеджер ресурсов.
    Ручное создание новых тем невозможно.
Иконка ресурса

Мануал Кеширующий прокси-сервер на nginx

kick

Магистр Йода
Administrator
За веру и верность форуму
Отец-основатель
Сообщения
7 242
Розыгрыши
28
Решения
2
Репутация
6 234
Реакции
7 568
Баллы
2 718
Ситуация выглядит следующим образом: есть размещённый на нескольких серверах IIS сайт (интернет-магазин),
перед ним расположен балансировщик. Между ними решено установить nginx для уменьшения нагрузки на IIS.

Основная масса динамического контента отображается Ajax-ом, так что кеширование страниц каталога товаров вполне безопасно. Однако на них могут быть отзывы о товаре, за которые можно проголосовать — совсем как на Хабре, что тоже надо учесть.

Плюс к этому хочется поддерживать валидность популярных страниц в кеше автоматически.

Итак, сначала устанавливаем свежий nginx — без него не получится. Также нам понадобятся wget и curl.
Я не буду подробно описывать конфигурацию самого прокси, а детально остановлюсь на методах поддержания
кеша в актуальном состоянии.

Конфигурация самого nginx:
Код:
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
        # Кеш у нас большой.
    proxy_cache_path /var/cache/nginx levels=2:2 keys_zone=STATIC:512m
    inactive=24h max_size=32g;

    sendfile        on;
    keepalive_timeout  65;

    gzip                on;
    gzip_proxied        any;
    gzip_min_length     1100;
    gzip_http_version   1.0;
    gzip_buffers        4 8k;
    gzip_comp_level     9;
    gzip_types          text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;

server {

    server_name 127.0.0.1;
    listen 80;

    set $backend 127.0.0.2;
        # Меняем структуру логов. Они нам понадобятся. Разделитель полей - |
    log_format cache '$remote_addr|$time_local|$request_uri|$request_method|$status|$http_referer|$cookie___sortOrder|$IsAuth|$sent_http_content_type|$http_user_agent';
    access_log /var/log/nginx/proxy_access.log cache;
    error_log off;
        # Не кешируем полностью динамические пользовательские страницы
    location ~* /(basket.aspx|visitedgoods.aspx|users/|sale/order.aspx|sale/posted.aspx) {

         proxy_pass http://$backend;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header Host $http_host;
         proxy_pass_header Set-Cookie;
         proxy_redirect off;

         set $IsAuth 1;
         if ($cookie_AUTH = "") {
         set $IsAuth 0;

         }

    }

    location / {
        # Убираем из урлов символ | - он там не должен оказаться, но если кто-то вобъёт его руками, он попортит нам логи
         if ($args ~* (.*)|(.*)) {
            set $brand $1$2;
            rewrite ^(.*) $1?$brand? redirect;
         }

         if ($args ~* (.*)%7C(.*)) {
            set $brand $1$2;
            rewrite ^(.*) $1?$brand? redirect;
         }

         rewrite   ([a-zA-Z0-9]+)|([a-zA-Z0-9]+)  $1$2?  permanent;
         rewrite   ([a-zA-Z0-9]+)%7C([a-zA-Z0-9]+)  $1$2?  permanent;
         rewrite   (.*)%7C$  $1?  permanent;
         rewrite   (.*)|  $1?  permanent;

         proxy_pass http://$backend;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header Host $http_host;
         proxy_pass_header Set-Cookie;
         proxy_ignore_headers "Expires" "Cache-Control" "Set-Cookie";
            # Проверяем, аутентифицирован ли пользователь. Страницы у гостей и юзеров разные, эта переменная нужна для ключа кеша.
         set $IsAuth 1;
         if ($cookie_AUTH = "") {
            set $IsAuth 0;
         }
            # Проверяем, не нужно ли обновить кеш - это делается при запросе нашего робота
         set $DoBypass 0;
         if ($http_user_agent = "WGET-POST-daemon") {
            set $DoBypass 1;
         }

            # Кука __sortOrder задаёт метод сортировки товаров и тоже нужна в ключе
         proxy_cache STATIC;
         proxy_cache_key "$host$uri$is_args$args $cookie___sortOrder $IsAuth";

         proxy_cache_valid 200 301 302 304 30m;

         proxy_cache_bypass $DoBypass;

         proxy_cache_use_stale error timeout invalid_header updating;
         proxy_connect_timeout 100ms;

         proxy_redirect off;

         }

}

}

Итак, прокси готов. Теперь самое интересное.

Мы хотим иметь в кеше TOP-20000 страниц за последние сутки. Лог выглядит так:
Код:
+0400|/catalog/25/women.aspx|GET|200|http://127.0.0.1/|-|0|text/html; charset=windows-1251|Opera/9.80 (Windows NT 6.1; U; ru) Presto/2.9.168 Version/11.51

Логи обращаются каждый час и хранятся сутки. Ксожалению, logrotate не умеет обращать логи менее, чем раз в день,
поэтому применяется средней грязности хак: size 1 в файл конфигурации и logrotate -f /etc/nginx.rotate по крону раз в час.

Скрипт для создания списка наиболее посещаемых страниц:
Код:
#!/bin/bash

# Получаем собственный IP, чтобы не учитывать в статистике локальный трафик
ourIP=$(ifconfig  | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $1}')

# Получаем валидную куку аутентификации для скачки страниц, отображаемых зарегистрированным юзерам
curl -H "Cookie: BasketID=KYKY-B-PYKY" -H "Content-Type: application/json" -d "{"login":"USERNAME","password":"PASS","remeberMe":"true"}" -c /var/log/nginx/cookies1.txt http://127.0.0.1/Resources/Services/SystemService.asmx/SignIn
line=$(tail -n 1 /var/log/nginx/cookies1.txt)
AUTH=$(echo $line | awk '{wild=$7; print wild}')

# Собираем единый лог: сливаем вместе все файлы за сутки.
cp /var/log/nginx/proxy_access.log /var/log/nginx/overall_proxy

FILES=/var/log/nginx/*.gz
for f in $FILES
do
    cat $f | gunzip>> /var/log/nginx/overall_proxy
done

# удаляем старые файлы команд wget-у
rm -f /var/log/nginx/wget/*

# Выбираем из лога все запросы к кеширующимся страницам (по типу содержимого - text/html), которые были запрошены НЕ с локалхоста и успешно переданы клиенту, выбираем 20000 самых популярных и создаём список команд wget-у.
awk -v ourIP="$ourIP" '{ FS="|"; ip = $1; url = $3; code = $5; catsrt=$7; isauth=$8; ct=$9; if (ip != ourIP) if (url !~ "basket.aspx" && url !~ "visitedgoods.aspx" && url !~ "users/" && url !~ "sale/order.aspx" && url !~ "sale/posted.aspx") if (code = "200") if (ct ~ "text/html;") print "http://" ourIP url "|" catsrt "|" isauth}' /var/log/nginx/overall_proxy | sort | uniq -c | sort -n -k1,6 | tail -n20000 | awk  '{print $2}' | awk -v AUTH="$AUTH" '{FS="|"}$2=="-"{$2=""} $3=="0"{$3=""} $3=="1"{$3=AUTH}{print "-b --header="Cookie: __sortOrder="$2"; AUTH="$3"" -o /dev/null -O /dev/null "$1 }'> /var/log/nginx/cache.dat

rm -f /var/log/nginx/overall_proxy

cd /var/log/nginx/wget

# Режем список команд на 10 частей - мы запустим 10 потоков закачки.
split -l 2000 /var/log/nginx/cache.dat

rm -f /var/log/nginx/cache.dat

Итак, на выходе получаем файлики с командами:

Код:
-b --header="Cookie: __sortOrder=; AUTH=" -o /dev/null -O /dev/null 127.0.0.1/catalog/25/women.aspx

Дальше каждые 20 минут мы должны пройти по этому списку и запросить каждую из страниц с сервера для валидации кеша.

Код:
#!/bin/bash

FILES=/var/log/nginx/wget/*

# Убиваем незавершённые с прошлого раза wget-ы
if [ -s /var/log/nginx/wgets.pid ]
then
    cat /var/log/nginx/wgets.pid | xargs kill
    rm -f /var/log/nginx/wgets.pid
fi

# И запускаем новые.
for f in $FILES
do
    cat $f | xargs wget & echo $! >> /var/log/nginx/wgets.pid
done

Теперь осталось только обновлять кеш страниц при голосовании за комментарий.
Этим занимается демон, постоянно мониторящий логи на предмет голосования.

Код:
#!/bin/bash

# Получаем куку аутентификации
curl -H "Cookie: BasketID=KYKY-B-PYKY-U-ATAC" -H "Content-Type: application/json" -d "{"login":"USERNAME","password":"PASS","remeberMe":"true"}" -c /var/log/nginx/cookies.txt http://127.0.0.1/Resources/Services/SystemService.asmx/SignIn

line=$(tail -n 1 /var/log/nginx/cookies.txt)
AUTH=$(echo $line | awk '{wild=$7; print wild}')

# Проверяем, не запущен ли уже демон
if [ -f /var/log/nginx/post-daemon.pid ];
then
    echo "POST-daemon already running!"
    exit 
fi

# Вешаемся на хвост логу nginx-а и ждём голосования
(/usr/bin/tail -f /var/log/nginx/proxy_access.log & echo $! >/var/log/nginx/post-daemon.pid) |
while read -r line
do
    if [[ $line =~ '/Resources/Services/SystemService.asmx/VoteToComment|POST|200' ]];
    then
              # Собираем информацию для wget - ссылку и куки.
    ref=$(echo $line | awk -F"|" '{ FS="|"; ref=$6; print ref}')
    sortOrder=$(echo $line | awk -F"|" '{ FS="|"; co=$7; print co}')
    IsAuth=$(echo $line | awk -F"|" '{ FS="|"; IsAuth=$8; print IsAuth}')
    if [[ $sortOrder == "-" ]];
    then
        sortOrder=""
    fi
              # Проверяем, какую страницу качать - для гостей или для регистрантов, и качаем. UserAgent скажет нашему nginx-у, что данный запрос должен быть обработан в обход кеша.
    if [[ $IsAuth == "0" ]];
    then
        wget --user-agent="WGET-POST-daemon" --header="Cookie: __sortOrder=$sortOrder" -o /dev/null -O /dev/null $ref
    else
        wget --user-agent="WGET-POST-daemon" --header="Cookie: __sortOrder=$sortOrder; AUTH=$AUTH" -o /dev/null -O /dev/null $ref
    fi
    fi
done
exit
Конфигурация для logrotate:
Код:
/var/log/nginx/*log {
    daily
    rotate 24
    size 1
    missingok
    notifempty
    compress
    postrotate
        /etc/init.d/nginx reload
        /etc/init.d/nginx-POSTcache restart
    endscript
}
init-скрипт для нашего демона:
Код:
#!/bin/sh
#
# This script starts and stops the nginx cache updater daemon
#
# chkconfig:   - 85 15 
#
# processname: post-daemon
# pidfile: /var/log/nginx/post-daemon.pid

. /etc/rc.d/init.d/functions

daemon="/usr/local/sbin/post-daemon.sh"
pidfile="/var/log/nginx/post-daemon.pid"
prog=$(basename $daemon)

start() {
    [ -x $daemon ] || exit 5
    echo -n $"Starting POST-daemon: "
    ($daemon &) &
    retval=$?
    echo
    [ $retval -eq 0 ]
    return $retval
}

stop() {
    echo -n $"Stopping POST-daemon: "
    pid=$(cat $pidfile)
    kill $pid
    rm -f $pidfile
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    stop
    start
}

rh_status() {
    status $prog
}

case "$1" in
    start)
        start
       ;;
    stop)
        stop
       ;;
    restart)
        restart
       ;;
    status)
        rh_status
       ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart}"
        exit 2
esac

Решение слегка костылеподобное, но рабочее. Нагрузка на IIS упала, скорость отдачи страниц клиенту возросла.
Отмечу, что все картинки сайта лежат на отдельном сервере и кешированию не подлежат.
 
Обратите внимание, что данный пользователь заблокирован! Не совершайте с ним никаких сделок! Перейдите в его профиль, чтобы узнать причину блокировки.
я бы навесил осьминога
 
Недавно вспоминал про компрессию трафика и кеширование... Спонтанно и не вникал сильно, но если это что думаю. Очень возможно, что тема снова станет актуальной. Однако нужна, скорее всего, доработка или другой подход, но то, что ностальгия посетила, это факт. Вспомнились времена спутникового интернета трафика и т.п.
 
Назад
Сверху Снизу