Время ответа современных сайтов влияет не только на удобство для пользователей, но и повышает позицию сайта в поисковых системах. Таким образом чем быстрее пользователь или поисковый робот получит всю страничку тем лучше
Одним из решений является кеширование страниц на уровне web сервера. В этой публикации рассмотрим установку и конфигурацию программного продукта Varnish на сервере под управлением CentOS 7
Считаем, что базовая установка уже выполнена
Во всех случаях когда это возможно Varnish желательно запускать на 80 порту что бы он принимал все запросы на себя и дальше уже в зависимости от результата выдавал результат из кеша или отправлял его на бекенд. Следовательно сам бекенд (в нашем случае NGinx) нужно перекинуть на любой другой порт. Я буду использовать 8080
Для установки добавим официальный репозиторий
# vim /etc/yum.repos.d/varnish.repo [varnish-4.0] name=Varnish 4.0 for Enterprise Linux baseurl=https://repo.varnish-cache.org/redhat/varnish-4.0/el7/$basearch enabled=1 gpgcheck=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-VARNISH
После этого выпулним установку необходимых компонентов
# yum -y install varnish varnish-libs varnish-libs-devel
Конфигурационные файли размещаются в каталоге /etc/varnish/
# ls /etc/varnish/ default.vcl secret varnish.params
Первым делом правим файл конфигурации демона /etc/varnish/varnish.params
Изменяем порт на котором будет запускаться Varnish
VARNISH_LISTEN_PORT=6081
на
VARNISH_LISTEN_PORT=80
Далее выбираем где хранить кеш. В первом случае все данные будут храниться в файле на жестком диске. Этот вариант приемлем если на сервере малое количество оперативной памяти
VARNISH_STORAGE="file,/var/lib/varnish/varnish_storage.bin,1G"
Если же у нас дедик или просто мощная VPS-ка, то кеш можно разместить и в оперативке. Скорость чтения записи будет немного выше
VARNISH_STORAGE="malloc,1G"
Выбирать нужно только один вариант
Запускаем и проверяем
# systemctl enable varnish # systemctl restart varnish # netstat -lntpu | grep 80 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 5452/varnishd tcp6 0 0 :::80 :::* LISTEN 5452/varnishd
Имитируем простейший запрос
# curl localhost Error 503 Backend fetch failed Backend fetch failed Guru Meditation: XID: 3 Varnish cache server
Бекенд пока что не настроен и Varnish не получил корректного ответа
Смотрим заголовки
# curl -I localhost HTTP/1.1 503 Backend fetch failed Date: Fri, 15 Jul 2016 10:30:54 GMT Server: Varnish Content-Type: text/html; charset=utf-8 Retry-After: 5 X-Varnish: 5 Age: 0 Via: 1.1 varnish-v4 Content-Length: 278 Connection: keep-alive
Далее нужно создать(отредактировать) правила самого Varnish
Они располагаются в файле /etc/varnish/default.vcl
Минимальная рабочая конфигурация следующая
#Указываем что используется 4 версия программы vcl 4.0; #Указываем адрес бекенда backend default { .host = "127.0.0.1"; .port = "8080"; } sub vcl_recv { # Happens before we check if we have this in cache already. # # Typically you clean up the request here, removing cookies you don't need, # rewriting the request, etc. } sub vcl_backend_response { # Happens after we have read the response headers from the backend. # # Here you clean the response headers, removing silly Set-Cookie headers # and other mistakes your backend does. } sub vcl_deliver { # Happens when we have all the pieces we need, and are about to send the # response to the client. # # You can do accounting or modifying the final object here. }
Для проверки я создал простейшую страничку вывода PHPInfo с кодом
< ?php phpinfo(); ?>
После этого я подключаюсь на удалённый сервер и с помощью утилиты apache benchmark создаю небольшую нагрузку в 10000 запросов и 100 потоков параллельно 🙂
# ab -n 10000 -c 100 http://IP_address_here/ This is ApacheBench, Version 2.3 < $Revision: 1430300 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking IP_address_here (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: nginx/1.10.1 Server Hostname: IP_address_here Server Port: 80 Document Path: / Document Length: 55062 bytes Concurrency Level: 100 Time taken for tests: 16.137 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 553123039 bytes HTML transferred: 550620000 bytes Requests per second: 619.71 [#/sec] (mean) Time per request: 161.367 [ms] (mean) Time per request: 1.614 [ms] (mean, across all concurrent requests) Transfer rate: 33473.95 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 31 32 0.2 32 36 Processing: 127 129 1.1 129 140 Waiting: 32 32 0.8 32 42 Total: 159 161 1.1 160 172 Percentage of the requests served within a certain time (ms) 50% 160 66% 161 75% 161 80% 161 90% 161 95% 162 98% 164 99% 166 100% 172 (longest request)
как видно 10к запросов одной и той же странички обработаны за 16 секунд. Результат мягко говоря неплохой
Все результаты взяты из кеша, так как после настройки я открыл страничку в браузере и она закешировалась
Теперь конфигурация собранная по крупицам со многих ресурсов конкретно для WordPress. На данный момент это лучшая конфигурация. Если есть какие то замечания — милости просим в комментарии
vcl 4.0; import std; backend default { .host = "127.0.0.1"; .port = "8080"; .first_byte_timeout = 60s; .connect_timeout = 300s; } acl purge { "localhost"; "127.0.0.1"; } sub vcl_recv { set req.http.X-Actual-IP = regsub(req.http.X-Forwarded-For, "[, ].*$", ""); if (req.restarts == 0) { if (req.http.x-forwarded-for) { set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; } else { set req.http.X-Forwarded-For = client.ip; } } if (req.http.Cache-Control ~ "no-cache") { if (client.ip ~ purge || std.ip(req.http.X-Actual-IP, "1.2.3.4") ~ purge) { set req.hash_always_miss = true; } } if (req.method == "PURGE") { if (!client.ip ~ purge || !std.ip(req.http.X-Actual-IP, "1.2.3.4") ~ purge) { return(synth(405,"Not allowed.")); } return (purge); } if (req.method == "BAN") { if (!client.ip ~ purge || !std.ip(req.http.X-Actual-IP, "1.2.3.4") ~ purge) { return(synth(403, "Not allowed.")); } ban("req.http.host == " + req.http.host + " && req.url == " + req.url); # Throw a synthetic page so the # request won't go to the backend. return(synth(200, "Ban added")); } set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js)=[^;]*", ""); set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", ""); if (req.url ~ "/feed(/)?") { return ( pass ); } if (req.url ~ "wp-cron\.php.*") { return ( pass ); } if (req.url ~ "/\?s\=") { return ( pass ); } if (req.http.Accept-Encoding) { if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") { unset req.http.Accept-Encoding; } elsif (req.http.Accept-Encoding ~ "gzip") { set req.http.Accept-Encoding = "gzip"; } elsif (req.http.Accept-Encoding ~ "deflate") { set req.http.Accept-Encoding = "deflate"; } else { unset req.http.Accept-Encoding; } } if (req.method != "GET" && req.method != "HEAD" && req.method != "PUT" && req.method != "POST" && req.method != "TRACE" && req.method != "OPTIONS" && req.method != "DELETE") { return (pipe); } if (req.method != "GET" && req.method != "HEAD") { return (pass); } if ( req.http.cookie ~ "wordpress_logged_in" ) { return( pass ); } if (!(req.url ~ "wp-(login|admin)") && !(req.url ~ "&preview=true" ) ){ unset req.http.cookie; } if (req.http.Authorization || req.http.Cookie) { return (pass); } return (hash); } sub vcl_hit { return (deliver); } sub vcl_miss { return (fetch); } sub vcl_backend_response { set beresp.http.Vary = "Accept-Encoding"; if (!(bereq.url ~ "wp-(login|admin)") && !bereq.http.cookie ~ "wordpress_logged_in" ) { unset beresp.http.set-cookie; set beresp.ttl = 52w; } if (beresp.ttl < = 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") { set beresp.ttl = 120 s; set beresp.uncacheable = true; return (deliver); } return (deliver); } sub vcl_deliver { if (obj.hits > 0) { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } }
Такая конфигурация на живом сайте ускорила загрузку страниц с 3,5 секунд до 0,6-0,7 секунд
Быстрых вам всем загрузок 🙂