Все о Linux. LinuxRSP.Ru


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


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

25 лет исполнилось ядру Linux

Релиз KDevelop 5.0

Oracle открывает код JDK9 для ARM

Выпущен Timewarrior 1.0.0

Релиз Android 7.0

Percona Memory Engine для MongoDB на базе WiredTiger

PowerShell открыт и доступен для Linux

Форк TrueCrypt: VeraCrypt 1.18

Релиз Snapcraft 2.14

Релиз Go 1.7

Стабильный выпуск рабочего стола Lumina

Вышла первая версия аналога OpenCV - DCV 0.1

Выпуск минималистичной программы для мониторинга jsonmon 3

В MIT разработали новый язык программирования

Первый релиз Qt5Gtk2

Godot 2.1 - новая версия открытого игрового движка

Свободная цифровая станция звукозаписи: Ardour 5.0

Обновление SkypeWeb Plugin for Pidgin

Вышла версия 3.0 Android File Transfer для Linux (и для OS X)

Программный аналог MIDI-контроллера для создания музыки: Launchpadd v1.3

Mozilla спонсирует поддержку Python 3.5 в PyPy

Ef 0.08 - программа для моделирования динамики заряженных частиц

Обновление текстового редактора TEA до версии 42.0.0

Релиз OpenOrienteering Mapper 0.6.4

Вышли Guix и GuixSD 0.11

Релиз Opera 39

Выпуск LibreOffice 5.2

В OpenSSH обнаружены и устранены некоторые уязвимости

Эмулятор FCEUX 2.2.3

Компания Билайн переходит на российскую СУБД с открытым исходным кодом Tarantool

Google

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

Утилиты для восстановления потерянных данных в Linux

Лучшие файловые менеджеры для Android

20 лучших бесплатных книг о Linux

Как сгенерировать открытый/закрытый SSH-ключ в Linux

Grive - клиент Google Drive для Linux с открытым исходным кодом

Протокол IPv6: варианты подключения

Сервер из образа: DHCP + TFTP + Initrd + OpenVZ

Обзор веб-панелей управления хостингом

Приёмы работы с Vim

Nginx как Reverse Proxy для сайта, использующего SSL

Разработка модулей ядра Linux

Мониторинг нагрузки http-сервера Apache 2

Перевод комментариев к файлу конфигурации Squid

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

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







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-2017 LinuxRSP