Обработка запросов (особенно динамических), например, таким веб-сервером как Apache может быть довольно требовательным с системным ресурсам процессом. Потому не случайно все большей популярностью пользуются легковесные веб-серверы — nginx и lighttpd, которые могут работать автономно или в связке с Apache в качестве фронт-энда для статических запросов.

Однако в этой статье мы рассмотрим несколько иной подход, связанный с использованием HTTP-ускорителя Varnish, который позволяет заметно разгрузить веб-серверы и уменьшить общее время отклика.

Varnsih является бесплатным решением для кэширования как статического, так и динамического контента. Работает он как фронт-энд к любому веб-серверу или серверу приложений и изначально ориентирован на высокую производительность, многопоточность и максимально эффективное использование возможностей ОС семейства Linux. И нет ничего удивительного в том, что он активно используется такими компаниями как Facebook, Twitter и LinkedIn.

Установка и базовая настройка
Varnish без труда может найден в большинстве стандартных репозиториев, но если нужна самая свежая версия, то можно воспользоваться и официальным сайтом.

Для корректной работы предполагается настройка двух конфигурационных файлов. Первый из них

	/etc/default/varnsih (Debian)
	/etc/sysconfig/varnish (Red Hat)

довольно часто игнорируется, хотя содержит в себе ряд принципиально важных параметров демона Varnish — размер выделяемой памяти, минимальное и максимальное количество потоков, TTL по умолчанию и пр. Ниже представлен пример конфигурации данного файла:

	START=yes

	NFILES=16384
	MEMLOCK=82000

	INSTANCE=webperformance.ru
	VARNISH_VCL_CONF=/etc/varnish/webperformance.vcl

	VARNISH_LISTEN_ADDRESS=192.168.1.1
	VARNISH_LISTEN_PORT=80

	VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
	VARNISH_ADMIN_LISTEN_PORT=6028

	VARNISH_MIN_THREADS=8
	VARNISH_MAX_THREADS=256
	VARNISH_THREAD_TIMEOUT=120

	VARNISH_STORAGE_FILE=/var/lib/varnish/$INSTANCE/varnish_storage.bin
	VARNISH_STORAGE_SIZE=128M
	VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}"

	VARNISH_SECRET_FILE=/etc/varnish/secret

	VARNISH_TTL=120

	DAEMON_OPTS="\
		-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \
		-f ${VARNISH_VCL_CONF} \
		-T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \
		-t ${VARNISH_TTL} \
		-w ${VARNISH_MIN_THREADS},${VARNISH_MAX_THREADS},${VARNISH_THREAD_TIMEOUT} \
		-S ${VARNISH_SECRET_FILE} \
		-s ${VARNISH_STORAGE}"

Второй же файл (заданный ранее через переменную VARNISH_VCL_CONF) как раз и определяет процесс обработки запросов и кэширования данных. Конфигурация при этом осуществляется с помощью несложного С-подобного языка VCL.

Общую последовательность работы Varnish можно описать с помощью следующей диаграммы:

Общая последовательность работы Varnish

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

Конфигурация бэк-энда
Базовая функция-директива указывает бэк-энд, который отвечает за обработку запросов (до тех пор пока не было выполнено кэширование):

backend apache {
	.host = "127.0.0.1";
	.port = "8080";
}

Varnish также можно использовать в качестве балансировщика нагрузки при работе с несколькими бэк-эндами.

backend server1 {
	.host = "10.0.0.11";
}
backend server2 {
	.host = "10.0.0.12";
}

При этом доступен циклический

director balanced_servers round-robin {
	{
		.backend = server1;
	}
	{
		.backend = server2;
	}
}

или случайный алгоритм балансировки

director balanced_servers random {
	{
		.backend = server1;
	}
	{
		.backend = server2;
	}
}

Кроме того можно связывать серверы с обработкой различных запросов, эта процедура будет описана далее.

VCL_RECV
Данная директива отвечает за первоначальную обработку входящих запросов.

sub vcl_recv {
	/* Указывается backend */
	set req.backend = apache;

	/* Non-RFC2616 или CONNECT */
	if (req.request != "GET" &&
		req.request != "HEAD" &&
		req.request != "PUT" &&
		req.request != "POST" &&
		req.request != "TRACE" &&
		req.request != "OPTIONS" &&
		req.request != "DELETE") {
			return (pipe);
	}

	/* Нормализация  кодирования */
	if (req.http.Accept-Encoding) {
		if (req.http.Accept-Encoding ~ "gzip") {
			set req.http.Accept-Encoding = "gzip";
		}
		elsif (req.http.Accept-Encoding ~ "deflate") {
			set req.http.Accept-Encoding = "deflate";
		}
		else {
 			remove req.http.Accept-Encoding;
		}
	}
     
	/* Удаление куки для статических файлов */
	if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|txt|js|flv|swf|html|htm)$" || req.url ~ "\?ver=") {
		unset req.http.Cookie;
		return (lookup);
	}

	return (pass);
}

В данном примере функция начинается с определения используемого для обработки запросов бэк-энда. Используя условные конструкции и регулярные выражения, можно назначать различные бэк-энды для соответствующего типа запросов.

Здесь же исключается обработка не поддерживаемых HTTP методов, а также проводится нормализация кодирования для сжимаемых данных.

В самой важной части данной процедуры на основе регулярного выражения (по расширению и наличию фрагмента «?ver=») удаляются куки для статических файлов. Это связано с тем, что по умолчанию Varnish не будет извлекать из кэша объекты, которые связаны с куки.

Далее управление можно перенаправить на поиск в кэше (через lookup) или непосредственно на бэк-энд (через pass).

VCL_HASH
В данной функции регламентируются правила хэширования объектов. По умолчанию объекты в кэше задаются непосредственно URL и сервером, к которому осуществлялся запрос:

sub vcl_hash {
	set req.hash += req.url;
	
	if (req.http.host) {
		set req.hash += req.http.host;
	}
	else {
		set req.hash += server.ip;
	}

	return (hash);
}

Однако бывают случаи, когда одна и та же ссылка соответствует различным файлам (например, динамически генерируемые стили). Для таких случаев можно связывать объекты в с кэше с дополнительными параметрами, в частности с типом веб-браузера:

sub vcl_hash {
	if (req.url ~ "dynamic.css") {
		set req.hash += req.http.User-Agent
	}
}

Также может быть полезным следующий трюк, когда файлы в кэше ассоциируются еще и с куки, что принципиально важно для динамических запросов. Следующий фрагмент демонстрирует добавление в хэш куки для запросов страниц относительно пути «/content/users/»:

sub vcl_hash {
	if (req.url ~ "^/content/users/") {
		set req.hash += req.http.cookie;
	}
}

Следующий же пример функции показывает, как по регулярному выражению можно удалить версию хранимого файла (для таких файлов как «script.js?1.4»):

sub vcl_hash {
	if (req.url ~ "^/stylesheets") {
		set req.url = regsub(req.url, "\?\d+\.\d", "");
	}
}

VCL_FETCH
Функция vcl_fetch определяет процесс получения данных от бэк-энда:

sub vcl_fetch {
	/* Удаление куки для статических файлов */
	if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|txt|js|flv|swf|html|htm)$" || req.url ~ "\?ver=") {
		unset beresp.http.set-cookie;
		set beresp.ttl = 1d;
	}

	if (beresp.status == 503) {
		set beresp.grace = 20s;
		restart;
	}

	return (deliver);
}

Как и ранее удаляются куки от статических файлов, но кроме того указывается время хранения объектов в кэше (TTL). Также добавлено повторное обращение к бэк-энду каждые 20 секунд на тот случай, если требуемый сервер временно недоступен.

VCL_DELIVER
Наконец, последняя функция позволяет модифицировать отправляемый пользователю респонс. К примеру, можно удалить необязательные заголовки в HTTP-ответе.

sub vcl_deliver {
	remove resp.http.X-Varnish;
	remove resp.http.X-Powered-By;
}

Очистка кэша
Для того, чтобы активизировать возможность очистки кэша без перезагрузки демона Varnish, необходимо сначала указать список адресов, от которых разрешено выполнение подобной процедуры

acl purge {
	"localhost";
	"127.0.0.1";
	"192.168.1.1";
}

а также добавить в функцию vcl_recv следующую условную конструкцию:

sub vcl_recv {
	if (req.request == "PURGE") {
		if (!client.ip ~ purge) {
			error 405 "Not allowed.";
		}
		purge("req.url == " req.url " && req.http.host == " req.http.host);
		error 200 "Purged.";
	}
}

Удаление объектов из кэша с помощью утилиты cURL будет выглядеть следующим образом:

curl -X PURGE https://webperformance.ru/wp-content/image-shards/*

Возможности настройки Varnish на этом не ограничиваются, но для большинства случаев данных набор опций будет более чем достаточным.

Метки: