Оптимизация WordPress на NGinx + php-fpm с помощью Varnish

Время ответа современных сайтов влияет не только на удобство для пользователей, но и повышает позицию сайта в поисковых системах. Таким образом чем быстрее пользователь или поисковый робот получит всю страничку тем лучше
Одним из решений является кеширование страниц на уровне 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 секунд
Быстрых вам всем загрузок 🙂

Добавить комментарий