Все о Linux. LinuxRSP.Ru
Альт Линукс СПТ 6.0 Сертификат ФСТЭК

Cвежие новости Linux и BSD, анонсы статей и книг прямо в почтовый ящик!
Подписаться письмом


 Сегодняшние новости:

В GIMP наконец-то появилась обработка с точностью 16 и 32 разряда на канал

В одном окне: GIMP 2.8

Релиз OpenBSD 5.1

HTML-редактор: BlueGriffon 1.5

Релиз Xfce 4.10

Команда Chromium представила кластер для автоматизации выявления уязвимостей

В Microsoft Office 15 будет обеспечена поддержка формата ODF 1.2

Анонсирован новый свободный анимационный фильм Tube

Обновление ядра: Linux 3.0.29, 3.2.16, 3.3.3

Представлен OpenSSH 6.0

Релиз графического редактора Pinta 1.2, претендующего на роль аналога Paint.NET

Вышел Firefox 12

Google прекращает поддержку Linux-версии приложения Picasa

Эмулятор приставок: Mednafen 0.9.21-wip

Линус Торвальдс получил премию Millennium Technology Prize 2012

Canonical не заинтересована в участии в разработке ядра Linux

Распределенные вычисления: BOINC 7.0

Google

 Новые статьи :

Передача снапшота ZFS по сети

Правила хорошего тона: SSH

Некоторые рассуждения о защите от ddos на примере защиты от SSL denial of service attack

Быстрый проброс портов на шлюзе во внутреннюю сеть или на другой хост. Быстро NAT'им порты. rinetd.

NAT'им отдельные порты или "а что делать, если мой веб-сервер внезапно переехал на другую машину". Проброс портов в локалке на шлюзе наружу.

Как справляться с запланированной недоступностью веб-сайта

Архитектура Google 2011

Мониторинг Nginx с помощью MRTG

Решение проблем при использовании "1c предприятие" 8.2 в Linux

25 правил .htaccess, которые должен знать каждый web-разработчик

Централизованное резервное копирование данных Windows и *nix серверов средствами Bacula

Краткое руководство по GNOME Shell в Ubuntu 11.10

Advanced Bash-Scripting Guide Искусство программирования на языке сценариев командной оболочки

   Дистрибутивы и атрибутика



DeveloperWorks Россия





Rambler's Top100


 
 

Кеширующий прокси-сервер на nginx. Хитрая конфигурация

Уже есть несколько описаний Nginx, но, думаю, моя конфигурация тоже будет интересна.
Ситуация выглядит следующим образом: есть размещённый на нескольких серверах 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 страниц за последние сутки. Лог выглядит так:

127.0.0.3|20/Oct/2011:15:45:43 +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 упала, скорость отдачи страниц клиенту возросла. Отмечу, что все картинки сайта лежат на отдельном сервере и кешированию не подлежат.
Иcтoчник
      

Связь | О проекте LinuxRSP | Реклама | О Linux
© 1999-2012 LinuxRSP


Реклама: