Игорь Олемской — практические заметки по системному администрированию Linux CentOS

Архив тега ‘Теория и практика’

Используем Nginx, как кеширующий сервер (перепечатка)

Комментариев нет

nginx

В этой статье рассмотрим применениt Nginx’a в качестве кеширующего сервера. Подробно о HTTP кеширования написано в статьях о продвинутом кеширующем сервере Varnish. Сразу следует отметить, что Nginx полностью не заменяет Varnish по функционалу и возможностям, но тем не менее продставляет очень хорошое решение. Учитывая великолепную работу этого Web-сервера, наличие функциональности кеширования делает возможным подключить ее к своему сайту буквально за 2 минуты.

Что кешировать?

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

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

Как кешировать с помощью Nginx?

Давайте вместе настроим кеширование для нашего блога. Для начала необходимо определить зоны кеширования. Зоны помагают категоризировать Ваши объекты в кеше для лучшей управляемости. Зона имеет имя и размер (nginx.conf):

#...
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=all:32m;
# include virtual hosts...

Так мы определили, что хотим создать зону “all” размером 32Мб, данные которой будут храниться в папке “/var/cache/nginx”. Параметр levels задает уровень вложенности файлов. В нашем случае файлы будут храниться в друхуровневой структуре папок. Подробнее про параметры можно почитать в официальной документации.

Не забываем создать папку, где будут храниться данные кеша:

mkdir /var/cache/nginx

Теперь необходимо выделить принимающий сервер и настроить на нем правила кеширования:

server {
        listen 80;
        server_name .highload.com.ua;
        location / {
                if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
                        set $do_not_cache 1;
                }
                proxy_cache_bypass $do_not_cache;
                proxy_pass http://highload.com.ua:81/;
                proxy_cache all;
                proxy_cache_valid 404 502 503 1m;
                proxy_cache_valid any 1h;
        }
}

Обслуживающий хост уедет на 81 порт. Нам необходимо, чтобы кеширование работало только для неавторизованных пользователей. Некоторые пояснения к настройкам:

В условие “if” сделана проверка на куки, которые WordPress ставит при авторизации

proxy_cache_bypass $do_not_cache — не кешировать ответ, если пользователь авторизован

proxy_cache all — этот параметр включает кеширование, в качестве аргумента принимает зону (у нас это “all”)

proxy_cache_valid 404 502 503 1m — кешируем 404, 502 и 503 ответы на 1 минуту

proxy_cache_valid any 1h — кешируем все остальные ответы на 1 час

Другие форматы

Следует отметить, что кешировать можно не только страницы HTML, но и любые другие типы данных. На одном из наших проектов мы успешно кешируем уменьшенные версии картинок с помощью Nginx’a.

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

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com
Related posts:
  1. Используем Nginx как “long polling” (Comet) сервер
  2. Nginx + Memcached + SSI — кеширование страниц и блоков (partials)

Используем Nginx как “long polling” (Comet) сервер (перепечатка)

Комментариев нет

comet

Принцип “Long Polling” (или “Comet“) позволяет сделать возможным установление постоянного соединения клиента с Web приложением и также периодичной отправки данных клиенту в рамках этого соединения (без необходимости клиента постоянно делать HTTP запросы для проверки новых данных). Другими словами, эта модель позволяет реализовать постоянные HTTP соединения для получения данных порциями. Наиболее популярное применение — чаты, твиттеры и другие live-updated потоки — клиент постоянно “слушает” сервер на предмет появления новых сообщений, и как только новые сообщения появляются — они мгновенно доставляются ему.

Сам принцип не несет в себе инноваций в HTTP протоколе, т.к. этот подход реализуем обычными средствами. Проблема заключается в том, что стандартное решение — установка постоянного соединения с обычным Web сервером (а значит и с обслуживающей платформой, например php-бекендом) — крайне ресурсоемкое и не применимо на практике. Другой подход — постоянно опрашивать приложение на предмет появления новых данных является еще более ресурсоемким.

Решением этой проблеммы стали т.н. HTTP-PUSH (или comet) сервера, позволяющие облуживать огромное количество постоянных соединений эффективно расходуя ресурсы. Как они устроены и как применяются на практике?

Принцип работы Comet-сервера

Необходимо отметить, что Comet сервер решает задачу отправки клиенту определенных данных порциями. Comet сервера реализуют т.н. HTTP Push Relay протокол (например, Basic HTTP Push Relay Protocol). Принцип работы такого протокола заключается в следующем:

  • На Web сервере создается т.н. “канал” с уникальным ключем. Это делается на стороне приложения путем посылки запроса к Comet-серверу
  • Клиент устанавливает обычное HTTP соединение с Comet-сервером, передавая в параметры ключ канала, из которого он будет получать данные. Сервер удерживает такое соединение, пока не отправит очередную порцию данных
  • Основное приложение, по определенному событию (например, кто-то написал сообщение клиенту), шлет в канал Comet сервера с нужным ключем это сообщение
  • Как только в канале появляется сообщение, Comet сервер отправляет его соответствующему клиенту и закрывает HTTP соединение (иногда соединения не закрываются, а продолжают быть активными)

Таким образом, сервер реализует “стэки” сообещений, а их раздача происходит без участия тяжелых бекендов. Бекенды вступают в силу только тогда, когда необходимо отправить сообщение.

Nginx и модуль HTTP Push

Модуль nginx_http_push_module позволяет превратить Nginx в Comet сервер, реализуя протокол Basic HTTP Push Relay Protocol. Преимущества этого модуля и протокола — в его простоте по сравнению с альтернативными решениями (например, протоколом Bayeux и сервером CometD).

Установка

Для установки модуля необходимо скачать его исходники и перекомпилировать Nginx, т.к. модуль является внешней разработкой:

wget http://pushmodule.slact.net/downloads/nginx_http_push_module-0.692.tar.gz
tar -xvf nginx_http_push_module-0.692.tar.gz
...
cd /where/nginx/sources/are
./configure \
  --add-module=/path/to/nginx/modules/sources/nginx_http_push_module-0.692/
make; make install

Конфигурация

Для базовой конфигурации нам необходимо объявить две рабочих точки в Nginx’e: точка публикации сообщений (приватная — доступна только для самого приложения) и точка раздачи сообщений (публичная — к которой будут подключаться клиенты):

	location /publish {
		# Название переменной с идентификатором канала
		# в нашем примере "cid", т.е. запрос будет таким:
		# http://example.com/publish?cid=s42378fwe
		set $push_channel_id $arg_cid;
		push_publisher;

		# Отключаем хранение очереди (сообщение удаляется после доставки)
		push_store_messages off;
	}

	location /listen {
		push_subscriber;

		# Обслуживать только первого "слушателя"
		# Остальным отправляем 403
		push_subscriber_concurrency first;

		# Идентификатор канала
		set $push_channel_id $arg_cid;
		# Тип ответа
		default_type text/plain;
	}

После этого при отправке сообщения нужно будет посылать его в соотв. канал, делая POST запрос на “/publish”. Клиент же отправляет GET запрос на “/listen” и ждет ответа. Когда приходит ответ, клиент делает повторный запрос на “/listen” и т.д.

Все параметры конфигурации с подробным описанием смотрите на официальном сайте.

Пример

В качестве примера ниже приведен код на PHP, который используется для отправки сообщения в канал:


# Определяем ID канала
$channel_id = 12345;

# Сообщение
$message = 'Привет тебе!';
# Отправляем сообщение в канал
$c = curl_init( 'http://localhost/publish?cid=' . $channel_id );
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
curl_setopt($c, CURLOPT_POST, true);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($message));
$r = curl_exec($c);

Для получения сообщения код на Javascript будет выглядеть где-то так (на основе Jquery):

var channelId = 12345;
function check_messages() {
	$.get('/listen?cid=' + channelId, {}, function(r) {
		// Считаем, что у нас есть div c id=messages,
		// куда мы дописываем сообщения
		$('#messages').append(r);
		setTimeout(check_messages, 500);
	}, 'json');
}
check_messages();

В каких случаях Вам приходилось пользоваться comet-серверами?

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com
Related posts:
  1. Используем Nginx, как кеширующий сервер
  2. Настройка nginx
  3. Настройка nginx для отдачи файлов

NoSQL — подходы к решению типичных задач (перепечатка)

Комментариев нет

nosql

Врядли сегодня найдется тот, кто еще не слышал про key-value базы данных, или, как их называют в народе, NoSQL. Те, кто успел попробовать продукты вроде Redis, MemcacheDB и других, обнаружили некторые сложности при решении привычных задач — выборка списков и поиск.

В этой статье мы рассмотрим принципы решения типичных задач в key-value базах данных.

Основная сложность

Восновном, все key-value базы данных представляют из себя распределенные хеш-таблицы. Это самая сильная их сторона, т.к. предоставляет высокую производительность и крайне упрощает масштабирование. Тем не менее, это свойство всплывает на поверхность, когда Вам необходимо работать со списками. В MySQL следующие задачи решаются в два счета:

  • Выбрать 10 последних пользователей
  • Выбрать Самые популярные посты в блоге
  • Найти продукты, для которых оставлено более 3х комментариев
  • Полнотекстовый поиск
  • Поиск по любому свойству (найти пользователя, email которого такой-то)

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

И так, по порядку:

Поиск по ключу

Существует класс задач, когда Вам необходимо делать выборку одного объекта не по первичному, а вторичному ключу (например, поиск пользователя по email’у, поиск автора по никнейму т.п.). Принцип решения этой задачи следующий: после создания нового объекта, Вам необходимо создать ссылки на его первичный ключ для всех его свойств, по которым придется делать выборку. Например мы создаем пользователя:

user_134: {
  name: Den,
  email: golotyuk@gmail.com
}

И дублируем ключ email:

user_email_golotyuk@gmail.com: {
  id: 134
}

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

Использование РСУБД

Если Вам нужен MySQL — используйте его!

Существует класс задач, для которых можно и нужно использовать РСУБД, такие как MySQL и Postgres. Если Вам необходимо делать выборки списков с фильтрами и сортировками, то Вам следует использовать более подходящую для этого РСУБД. Хороший пример — это посты в блоге. (выборки, которые скорее всего нужно будет делать — самые новые, самые популярные, самые комментируемые записи и т.п.).

Подход довольно простой, но на практике очень часто задачи сильно переплетены, и в итоге Вы можете прийти к тому, что РСУБД возьмет все задачи обратно на себя. В этом случае следует подумать о гибридном решении:

Гибридное решение — дополнительная РСУБД

Это решение представляет из себя смесь key-value и РСУБД. Все данные Вы храните в key-value, но те свойства, по которым понадобиться делать агрегатные выборки дублируете в РСУБД (с указателем на ключ). Т.о. Вы сохраните производительность key-value базы данных и сможете решить свою задачу. Сложность тут заключается в том, что Вам придется следить за синхронизацией данных в обоих базах данных, что может сильно усложнить логику приложения.

Полнотекстовый поиск и выборки с задержками

Одна из частых задач — полнотекстовый поиск. Существуют отличные решения — Sphinxsearch, Solr и другие. К сожалению, ни одно из них еще не поддерживает индексацию key-value баз данных, но зато все предоставляют интерфейсы для ручной индексации. Вам нужно будет только описать реализацию для конкретного набора данных (например, с помощью XmlPipe в Sphinx’e).

Наряду с полнотекстовым поиском часто стоят и задачи, связанные с агрегатными выборками, которые не критичны к текущему состоянию данных. Например, когда Вы строите рейтинг пользователей (или постов в блоге, или продуктов в магазине или …), Вы можете спокойно использовать данные по состоянию на прошлый час(день/неделю/…). В качестве решения Вы можете использовать полнотекстовые серверы по их непрямому назначению. Многие из них поддерживают разнообразные фильтры и сортировки, которых достаточно для решения 90% задач подобного рода.

Распределенные выборки

Некоторые key-value базы данных позволяют делать выборки списков встроенными средствами. Масштабирование этого решения будет выглядеть как запросы ко всем узлам сети и агрегирование результатов на бекенде. В крупных масштабах это может быть очень затратная операция, поэтому это решени слеудет оптимизировать. Во многих случаях Вы можете обойтись без запросов ко всем узлам, а сделать выборку только на одном из них (например — выбрать случайное фото).

Что еще сюда можно добавить?

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com
Related posts:
  1. Key=value (ключ=значение) базы данных

memcache vs memcached — сравниваем клиенты для PHP (перепечатка)

Комментариев нет

performance

Какой клиент лучше использовать при разработке на PHP — php-memcached или php-memcache? Все зависит от того, какие особенности Вам нужны (неужели?). Давайте сравним в двух аспектах — функциональность и производительность.

Функциональность

Клиентская библиотека php-memcache была разработана еще в 2004 году и сегодня существует уже довольно стабильная версия, которая используется в 99.9% проектах (использующих сервер Memcache). Большим недостатком этой библиотеки является ее ограниченные способности — она реализует только часть протокола мемкеша, и не позволяет использовать его дополнительные возможности (какие именно — чуть ниже).

Библиотека php-memcached была разработана сравнительно недавно, но уже успешно используется на некоторых крупных проектах (например, digg.com — из которого и вышла эта разработка). Самое главное ее преимущество — это полная реализация протокола, в том числе:

  • CAS токены для версионирования ключей
  • Обратные вызовы (callbacks)
  • Метод getDelayed () позволяющий уменьшить время ожидания откладывая фактическое чтение ключей
  • Поддержка бинарного протокола
  • Возможность избежать сериализации используя igbinary

Производительность

Теперь давайте сравним производительность на практике.
Для этого напишем небольшой скрипт, который делает одинаковые операции с обоими клиентами:

<?

$ops = 10000;

$m = new Memcache();
$m->addServer('localhost');

$md = new Memcached();
$md->addServer('localhost', 11211);

echo "Test operations: {$ops}";
echo "<h3>get test</h3>";

$s = microtime(true);
for ( $i = 0; $i < $ops; $i++ ) $m->get( md5(rand(1000, 99999)) );
echo "Memcache: " . ($res['memcache']['set'] = microtime(true) - $s ) . "<br />";

$s = microtime(true);
for ( $i = 0; $i < $ops; $i++ ) $md->get( md5(rand(1000, 99999)) );
echo "Memcached: " . ($res['memcached']['set'] = microtime(true) - $s );

echo "<h3>set test</h3>";

$s = microtime(true);
for ( $i = 0; $i < $ops; $i++ ) $m->set(md5(rand(1000, 99999)), 2);
echo "Memcache: " . ($res['memcache']['get'] = microtime(true) - $s ) . "<br />";

$s = microtime(true);
for ( $i = 0; $i < $ops; $i++ ) $md->set(md5(rand(1000, 99999)), 2);
echo "Memcached: " . ($res['memcached']['get'] = microtime(true) - $s );

echo "<h3>delete test</h3>";

$s = microtime(true);
for ( $i = 0; $i < $ops; $i++ ) $m->delete(md5(rand(1000, 99999)));
echo "Memcache: " . ($res['memcache']['delete'] = microtime(true) - $s ) . "<br />";

$s = microtime(true);
for ( $i = 0; $i < $ops; $i++ ) $md->delete(md5(rand(1000, 99999)));
echo "Memcached: " . ($res['memcached']['delete'] = microtime(true) - $s );

echo "<h3>combined test</h3>";

$s = microtime(true);
for ( $i = 0; $i < $ops; $i++ )
{
	$key = md5(rand(1000, 99999));
	$m->set($key, 2);
	$m->get($key);
	$m->delete($key);
}
echo "Memcache: " . ($res['memcache']['combined'] = microtime(true) - $s ) . "<br />";
$s = microtime(true);
for ( $i = 0; $i < $ops; $i++ )
{
	$key = md5(rand(1000, 99999));
	$md->set($key, 2);
	$md->get($key);
	$md->delete($key);
}
echo "Memcached: " . ($res['memcached']['combined'] = microtime(true) - $s );

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

Результаты работы теста

Как видно из графика, производительность клиента php-memcache почти втрое выше, чем php-memcached. Следует учесть, что показатели при работе с бинарным протоколом почти не отличалась по времени от ASCII протокола.

Обновление

Благодаря внимательному читателю этого блога удалось выяснить, что сильные отличия в показателях тестов связаны с конкретными версиями и настройками библиотек. Проверка на других платформах дает отличие в производительности не более 10%.

В качестве вывода следует сказать, что клиент php-memcache лучше использовать, если функционала приложению полностью хватает (лучше, т.к. он быстрее). Если нужны расширенные возможности, то нужно использовать php-memcached. Но в этом случае нужно учесть понижение производительности.

Хотя важно заметить: даже не смотря на трехкратное различие в скорости работы, операции общения с сервером Memcache обычно занимают доли процента от времени исполнения приложения. Различие в скорости работ библиотек может быть заметно только при массовой работе с кешом, или же на огромных объемах данных.

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com
Related posts:
  1. Memcached Multi-Get, зачем?
  2. Memcached — настройка под малые объекты
  3. Nginx + Memcached + SSI — кеширование страниц и блоков (partials)

Делаем ленту обновлений на MongoDB + PHP (перепечатка)

Комментариев нет

Делаем ленту обновлений на MongoDB + PHP

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

На первый взгляд все очень просто, а на практике все сложно. Давайте разберемся детально (на примере MongoDB + PHP, заодно посмотрим на эту новую шумную СУБД).

Что такое эта “лента обновлений”?

лента обновлений

На этом скрине Вы видите ленту обновлений или “news feed” с фейсбука (facebook.com). Там представлены все события Ваших друзей.

Как будем делать? На первый взгляд все кажется довольно просто. У нас есть ограниченный набор действий (загрузить фото, обновить статус и т.п.). Для реализации подобной ленты новостей нужно выбрать все такие действия для каждого друга и отсортировать их по времени. Но сразу же становится видна недальновидность такого подхода:

  • Очень тяжелые выборки в нескольких таблицах (выбрать все фотки, все статусы и т.п.)
  • Требуется дополнительная сортировка и агрегирование данных на стороне системы (а не СУБД)
  • Кеширование будет неоправданным, т.к. сделает ленту неактуальной
  • Решение не масштабируется (не возможен шардинг)

Проведем небольшой анализ:

  1. Новые события в ленте могут появляться довольно динамично и сразу должны становиться доступны друзьям (загрузил фотку — друзья сразу об этом узнали). Тем не менее короткие задержки тут не критичны (в пределах минут).
  2. Продвинутый функционал ленты обычно включает в себя управление приватностью (кто из друзей и какие действия может видеть)
  3. Решение должно горизонтально масштабироваться. Учитывая то, что в больших системах количество генерируемых событий будет огромным, это самый важный момент

Архитектурное решение

Для начала уйдем от необходимости собирать все события с разных таблиц. Создадим одну таблицу типа “обновления” и будем складывать туда все события с такой информацией:

  • кто совершил действие (автор)
  • время действия
  • доп. данные о действии (напр., список загруженных фоток)

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

SELECT * FROM updates WHERE user_id IN (1,2,3,4) ORDER BY time DESC

Тут 1,2,3,4 — это ID друзей пользователя

Уже лучше. Сортировку можно делать прямо на стороне СУБД, не нужно делать дополнительную агрегацию. Но остаются следующие проблемы:

  • Таблица не шардится (ее невозможно разделить по какому-либо критерию для горизонтального масштабирования ввиду неопределенности списков друзей)
  • Учитывая возможный большой размер таблицы, запросы к ней могут быть очень медленными
  • Если понадобится учитывать приватность сообщений, запросы будут крайне тяжелыми

Опять не подойдет, думаем дальше:

Генерация пользовательской ленты

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

  1. Выбераем список друзей пользователя
  2. Проверяем правила приватности для каждого из них
  3. Вставляем событие в персональную ленту тем, кому нужно

В этом случае мы избавляемся от проблемы медленной генерации ленты, т.к. ее теперь не нужно генерировать. Мы просто читаем ее из СУБД (элементарный запрос, никаких преобразований). Самое важное — пользовательская лента теперь на 100% персональная (из нее даже можно что-нибудь удалить, не затронув остальных пользователей).

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

  1. Пользователь загружает фотку
  2. Обрабатываем его запрос, сохраняем фото и отправляем пользователю ответ
  3. В фоне (асинхронно) выполняем обработку события и обновляем ленты его друзей

Платформа

Для управления лентами нам понадобятся простые операции работы со списками: добавить/удалить элемент, сортировка и возможно фильтрация.
Понятно, что использовать тяжелую СУБД, такую как MySQL, нецелесообразно для таких простых операций. Очень хорошо в этом случае подойдет, например, Redis или же MongoDB.

В этом примере мы рассмотрим реализацию на основе MongoDB. Для хранения ленты будем использовать внутренний массив документа. В качестве клиентской реализации используем PHPMongo.

Для выполнения задач на фоне можно использовать любую удобную систему очередей (например, RabbitMQ).

Пример

Для начала опишем главную функцию — вывод ленты обновлений (функцию get_feed () опишем позже):

<?
# Получаем ленту
# $user_id содержит идентификатор текущего пользователя
$feed = get_feed($user_id);

echo "<h3>Что делают Ваши друзья</h3>";
# Выводим все события из ленты
foreach ( $feed as $feed_item )
{
	# Время, имя пользователя и текст события
	echo "{$feed_item['time']} :: {$feed_item['user_name']} {$feed_item['text']}<br />";
}

Обновление ленты

Теперь посмотрим, как будет выглядет типичное действие, которое обновляет ленты друзей пользователя. Например, пользователь меняет свой статус. Обработчик будет выглядеть следующим образом:

<?
$status = $_POST['status'];

# коннектимся к Mongo (по умолчанию - localhost:27017)
$m = new Mongo();

# Обновляем статус пользователя (БД user, коллекция status)
# "'upsert' => true" - вставить запись, если ее нет
$m->user->status->update(
	array('user_id' => $user_id),
	array('$set' => array('status' => $status)),
	array('upsert' => true)
);
# Регистрируем событие для лент (реализация дальше)
update_feed($user_id, 'обновил статус: ' . $status );

Регистрация события

В предыдущем кусочке кода функция update_feed () регистрировала событие для добавления в ленты друзей. Реализация этой функции будет следующая:

<?
function update_feed($user_id, $text )
{
	# Получаем список друзей пользователя
	$friends = get_friends($user_id);

	$m = new Mongo();	

	# Вставляем событие каждому другу в ленту
	foreach ( $friends as $id )
	{
		# Получаем имя друга
		$user_name = get_user_name($id);

		# $data = array(
			'time' => date('H:i (m.d)'),
			'user_name' => $user_name,
			'text' => $text
		);
		# Вставляем событие в массив Mongo-объекта "list"
		# БД user, коллекция feed
		$m->user->feed->update(
			array('user_id' => $id),
			array('$push' => array('list' => $data)),
			array('upsert' => true)
		);
	}
}

Чтение ленты

Осталось описать функцию чтения ленты для пользователя:

<?
function get_feed($user_id)
{
	$m = new Mongo();

	# Выбираем события из БД
	$data = $m->user->feed->findOne( array('user_id' => $user_id) );
	return $data['list'];
}

Некоторые замечания

Нужно следить, чтобы количество элементов в массиве не росло очень сильно (например, сотни тысяч), иначе объекты будут очень тяжелыми. Массивы необходимо периодически обрезать (пока в MongoDB нет удобного механизма это делать — надеюсь, появится).

Для реализации приватности необходимо в функцию update_feed () добавить функционал по проверке прав доступа для каждого пользователя.

В MongoDB пока довольно ограниченный набор функционала с вложенными массивами, поэтому фильтрацию ленты (если понадобится) придется делать на стороне PHP. Будем следить за развитием MongoDB.

Не забудьте поставить индексы на колонки по которым делаете выборки в Mongo (как это делать?).

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com
Related posts:
  1. Полнотекстовый поиск в MongoDB используя Sphinx
  2. Твиттер на основе MemcacheDB и PHP
  3. PHP + Redis платформа “Ключ=Значение”

Nginx + Memcached + SSI — кеширование страниц и блоков (partials) (перепечатка)

Комментариев нет

nginx-logo

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

Зачем нужно кешировать страницы?

Довольно детально принципы кеширования страниц и блоков были рассмотрены в предыдущей статье (Varnish + ESI).

В качестве краткого повторения, несколько основных идей и преимуществ кеширования на уровне странц:

  • Кешируя страницы на отдающем сервере, Вы освобождаете ощутимое количество ресурсов на бекендах
  • В случае медленных страниц, значительно уменьшается время ожидания страниц Вашего сайта (ускоряется его работа)
  • Как показывает практика, во многих случаях внедрять систему кеширования странц в готовый проект легче, чем систему кеширования запросов

Nginx и memcached

Nginx позволяет читать данные из Мemcached — для этого Вам необходим модуль HttpMemcachedModule.

Самый простой пример — проверяем есть ли страница в кеше и, если есть, достаем из кеша, иначе — делаем запрос к бекенду.

server {
  location / {
    set $memcached_key $uri; # Ключ для проверки в memcached
    memcached_pass     127.0.0.1:11211; # Параметры подключения
    default_type       text/html; # Заголовок по умолчанию
    error_page         404 = @fallback; # 404 - данные в кеше не найдены
  }
  location @fallback {
    proxy_pass backend; # Бекенд
  }
}

В данный момент Nginx не умеет сохранять значения в memcached — только читать данные. Значит задача по сохранению страниц в кеш ложится на бекенд. Выглядит это приблизительно так (PHP):

<?
$memcache = new Memcache();
# какой-то код

ob_start();
# визуализация страницы - html
$html = ob_get_clean();
$memcache->set($_SERVER['REQUEST_URI'], $html);
echo $html;
?>

Кеширование блоков и SSI

Если у Вас не самый примитивный сайт (а он у Вас не такой), логика кеширования страниц будет несколько усложнена различными динамическими элементами страницы (блок авторизации, динамическое меню и т.п.). Подобные задачи решаются c помощью SSI — server side includes. Кроме всего прочего использование SSI экономит память, т.к. для каждой страницы Вы не будете хранить ее исходник целиком, а только внутреннюю (изменяемую) часть.

Синтаксис SSI очень простой и выглядит так:

<!--# include virtual="/authentication.php" -->

Следует отметить, что SSI — это уровень Web сервера. Для прилжения этот механизм полностью прозрачен. В приведенном примере, на месте вызова SSI, Web сервер (nginx) просто сделает еще один запрос к бекенду. Например, если у Вас два SSI вызова на страницу, то клиентский запрос к каждой странице будет генерировать три запроса к бекенду. Конечно, само по себе это бессмысленно, но в связке с кешированием представляет из себя мощный инструмент оптимизации систем.

Практический пример

Теперь соберем все выше изложенное в последовательный рецепт решения рассматриваемой задачи.

Для начала Вам необходимо выделить блоки в Вашем базовом (глобальном) шаблоне и заменить их вызовами SSI. Пример на PHP:

<html>
<body>

<h1>Тестируем nginx + memcached + ssi</h1>

<div class="auth">

<!--# include virtual="/authentication.php" -->
</div>


<!--# include virtual="/somepage.php" -->
</body>
</html>

Как видно, на странице есть два блока — блок авторизации и контентный блок (содержимое странички). В нашем примере блок авторизации будет персональным (допустим, после входа, там будет имя пользователя). Блок содержимого не будет персональным (т.е. он будет общим для всех пользователей).

Теперь необходимо настроить nginx для обработки SSI вызовов и использованию memcached:

# Upstream бекенда
upstream backend {
    server 127.0.0.1:8081;
}

server {
	listen 80;
	server_name test.com;

	root /var/www/test;
	index index.php;

	location / {
		# Все POST запросы отправляем на бекенд (не кешируя)
		if ($request_method = POST) {
			proxy_pass http://backend;
			break;
	        }

		# Включаем обработку SSI
		ssi on;
		default_type text/html;

		# Проверяем в мемкеше
		set $memcached_key "$uri";
		memcached_pass localhost:11211;
		proxy_intercept_errors  on;
		error_page 404 502 = @process;
	}

	# Сюда запрос приходит, если его небыло в кеше
	location @process
	{
		proxy_pass http://backend;
		ssi on;
	}
}

# Обычный бекенд
server {
	listen 8081;
	location ~* \.(php)$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /var/www/test$fastcgi_script_name;
    }
}

Следующим шагом в приложении необходимо будет реализовать сохранение исходников страниц в memcached.

Кеширование персонализированных блоков

Ясно, что для кеширования персонализированных блоков необходимо добавить идентификатор пользователя (не вздумайте использовать внутренний ID пользователя) в ключ memcached. SSI вызов необходимо пометить, чтобы отличать персональные блоки от общих. Т.е. персональный вызов SSI будет иметь такой вид:


<!--# include virtual="/uid/authentication.php" -->
</div>

Для генерации memcached ключей с идентификатором пользователя можно использовать куки (устанавливать его будет приложение при авторизации пользователя):

set $memcached_key "$uri:$cookie_uid";

Соответственно, необходимо в nginx добавить обработчик для “/uid” запросов:

        location /uid {
                ssi on;
                default_type text/html;
                set $memcached_key "$uri:$cookie_uid";
                memcached_pass localhost:11211;
                proxy_intercept_errors  on;
                error_page 404 502 = @process;
        }

Естественно, в самом приложении все “/uid” запросы должны кешироваться с идентификатором пользователя:

$m->set($_SERVER['REQUEST_URI'] . ':' . $_COOKIE['uid'], $html);

Следует обратить внимание

Недавно появился продукт, который должен стать типичным решением при кешировании страниц — Twicecache. Пока он на очень ранней стадии и доступны только его исходники. Будем внимательно следить.

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com
Related posts:
  1. Кеширование страниц — ускоряем сайт в 100 раз (Varnish + ESI)
  2. Настройка nginx
  3. Ресайзинг картинок в nginx

Кеширование страниц — ускоряем сайт в 100 раз (Varnish + ESI) (перепечатка)

Комментариев нет

varnish-logo-red-64

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

Если на Вашем сайте практически нет динамики (например, новостной сайт или блог), то Вы легко можете складывать все его страницы в кеш и практически не делать запросов к бекенду. Но что делать если на сайте есть авторизация, и зависящая от этого логика?

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

ESI

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


<HTML>
<BODY>

Всем привет!

<esi:include src="/news.php?UID"/>

Всем пока!!!
</BODY>
</HTML>

Веб сервер, поддерживаюий ESI вызовы, встретив ESI вызов, просто сделает дополнительный запрос, а результат вставит на его место. Допустим наш скрипт “news.php” содержит такой код:

<?
echo "<ul><li>News title 1</li></ul>";

После обработки первого примера, клиенту вернется такая страница:


<HTML>
<BODY>

Всем привет!

<ul><li>News title 1</li></ul>

Всем пока!!!
</BODY>
</HTML>

Зачем это нужно, если это только увеличивает количество запросов к серверу?
Все просто, если использовать кеширование — у Вас появляется удобное средство кешировать различные куски страниц (с разной логикой и временем жизни кеша). В нашем примере мы могли бы, например, закешировать главную страницу на 1 день, а скрипт новостей на 1 час. Тогда в сутки мы бы получали только 25 запросов к PHP. Это позволило бы значительно разгрузить бекенд, а также увеличить скорость загрузки страниц.

Как все работает?

Web сервер, который поддерживает ESI, делает запрос к бекенду (в нашем случае, PHP). Далее получив страницу, обрабатывает все ESI вызовы, делая на каждый из них дополнительный запрос для получения содержимого. Далее, следуя правилам кеширования, складывает (или не складывает) все это в кеш и генерирует страницу. Последующие вызовы страницы приведут к тому, что она будет отдана клиенту из кеша.

Допустим на странице три ESI вызова (например, шапка, подвал и блок новостей). В этом случае первое обращение к серверу будет выглядеть где-то так:

esi11

Обратите внимание, что один запрос генерирует еще 3 запроса к бекенду (т.к. у нас три ESI вызова на странице). После первого запроса все части закешировались, и теперь (при последующих запросах) картинка будет выглядеть так:

esi21

Как видите, второй запрос (и каждый последующий) не вызвали обращений к бекенду, а все нужные куски страниц были отданы из кеша.

Что необходимо сделать, чтобы все это заработало:

  1. Выделить блоки на странице, сделать их доступными по отдельным HTTP запросам
  2. Описать ESI вызовы на месте выделенных блоков
  3. Описать логику кеширования на сервере (время жизни кеша, параметры кеширования и т.п.)

Персональные данные

Самая сложная часть в частичном кешировании, это выделение персональных частей. Например: блок логина после авторизации превращается в блок с персональными ссылками (типа “выйти”, “настройки” и т.п.). Для того, чтобы получить правильный результат нужно для начала разделить страницу на такие блоки. Важно понять отличие персонализированных блоков (например, блок с фоткой, именем и фамилией залогиненного пользователя) от блоков для авторизированных пользователей (например, кнопка “выйти” вместо формы логина). В первом случае объект нужно сохранить в кеш для каждого пользователя, а во втором у объекта будет всего две версии в кеше (для залогиненных пользователей и нет).

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

Пример

Для примера возьмем такую задачу. У нас есть сайт с новостями. Новости обновляются каждый час. На сайте также есть блок авторизации и скрытые ссылки для авторизированных пользователей. Исходя из этого логическое разбиение на блоки будет следующим:

  • Блок авторизации
  • Меню
  • Блок новостей

Исходный код стартовой страницы:

<HTML>
<BODY>

<h1>Тестируем ESI</h1>

<esi:include src="/app/auth.php?UID"/>
<esi:include src="/app/menu.php?AUTH"/>

<h3>Новости</h3>
<esi:include src="/app/news.php"/>
</BODY>
</HTML>

Все скрипты для ESI вызовов будут находиться в папке app. Их исходники:

Скрипты бекенда

Скрипт авторизации

<?

session_start();

if ( $_POST['user'] )
{
	$_SESSION['user'] = $_POST['user'];
	header('Location: /'); exit;
}

$user = $_SESSION['user'];

?>
<? if ( $user ) { ?>
	<div>Привет, <b><?=$user?></b>!</div>
<? } else { ?>
	<form method="post" action="/app/auth.php">
		Войдите в систему
		<input type="text" name="user" />
		<input type="submit" name="Войти">
	</form>
<? } ?>

Скрипт меню

<? session_start(); ?>
<ul>
	<? if ( $_SESSION['user'] ) { ?>
		<li><a href="#">Пункт меню только для пользователей</a></li>
	<? } ?>
	<li><a href="#">Публичный пункт меню</a></li>
</ul>

Скрипт новостей

<?

$rss = file_get_contents('http://feeds.nytimes.com/nyt/rss/HomePage');
$xml = simplexml_load_string($rss);
echo "<ul>";
foreach ( $xml->channel->item as $item )
{
	echo "<li><a href=\"{$item->link}\">{$item->title}</a>";
}
echo "</ul>";

Вся логика крайне упрощена, но тем не менее сохраняет суть стандартных задач подобного рода.

Настройка Nginx

Для обслуживания будем использовать Nginx. Конфигурация включает два виртуальных сервера. Первый (80 порт) принимает и обрабатывает запросы от клиента. Все запросы проксирует на Varnish. Второй сервер (порт 8090) нужен для запросов от Varnish’a:

server {
    listen 80;
    server_name esi.dev;
}

server {
    listen 8090;

    root /home/golotyuk/www/localhost/esi;

    # Если включен gzip, обязательно нужно выключить!
    gzip off;

    location / {
        index index.php;
    }
    location ~* \.(php)$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME  /home/golotyuk/www/localhost/esi/$fastcgi_script_name;
    }
}
Настройка varnish + esi

Перед конфигурацией varnish’a определяем логику кеширования:

  • Главную страницу кешируем на 24 часа, блоки — на 1 час
  • Кешировать будем все запросы кроме POST
  • Для кеширования персонального блока будем использовать значение сессионных cookie (PHPSESSID) — для установки ключа кеширования
  • Для разделения персонализированных блоков от обычных блоков для авторизированных пользователей будем использовать соотв. приставки к запросам: UID (персонализированные блоки) и AUTH (обычные блоки, учитывающие только статус пользователя)

Конфигурация:

# Бекенд сервер - виртуальный сервер Nginx
backend default { .host = "127.0.0.1"; .port = "8090"; }

# Процедура формирования ключа для кеширования
sub vcl_hash {
       # Стандартные параметры - имя сервера и URL
        set req.hash += req.url;
        set req.hash += req.http.host;

       # Если установлена сессионная кука, сохраняем ее значение в переменную
        if( req.http.cookie ~ "PHPSESSID" ) {
            set req.http.X-Varnish-Hashed-On =
                regsub( req.http.cookie, "^.*?PHPSESSID=([^;]*?);*.*$", "\1" );
        }

        # Если в строке запроса мы находим "UID", то необходимо добавить
        # значение сессии в параметры кеширования
        if( req.url ~ "/app/.*UID" && req.http.X-Varnish-Hashed-On ) {
             set req.hash += req.http.X-Varnish-Hashed-On;
        }

        # Если в строке запроса мы находим "AUTH", то необходимо добавить
        # флаг статуса (logged in) в параметры кеширования
        if( req.url ~ "/app/.*AUTH" && req.http.X-Varnish-Hashed-On ) {
            set req.hash += "logged in";
        }

        hash;
}

sub vcl_recv {
        # Если тип запрос не POST, то ищем объект в кеше
        if ( req.request != "POST" )
        {
                lookup;
        }
}

sub vcl_fetch {
    # Для запроса "/" используем обработку esi и кешируем на 1 сутки
    if (req.url == "/") {
        esi;
        set obj.ttl = 24h;
    }
    # Для запросов "/app" (ESI вызовы) кешируем результат на 1 час
    elseif (req.url ~ "^/app/") {
        set obj.ttl = 1h;
    }
    deliver;
}

После конфигурации и запуска, получим приблизительно следующее:

esi-screen-shot

Для анализа производительности воспользуемся утилитой ab (из пакета apache2-utils):

ab -n 100 -c 5 http://esi.dev/

Результат:


Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       0
Processing:     2    5   3.8      3      18
Waiting:        2    5   3.8      3      18
Total:          2    5   3.8      4      19
Percentage of the requests served within a certain time (ms)
  50%      4
  66%      4
  75%      5
  80%      8
  90%     12
  95%     15
  98%     17
  99%     19
 100%     19 (longest request)

Для сравнения запустим тест на аналогичном скрипте без ESI, который содержит всю туже логику внутри и каждый раз вызывает PHP:

ab -n 100 -c 5 http://127.0.0.1:8090/index_standard.php

Результаты:

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:   354  579 666.2    458    5484
Waiting:      354  579 666.2    458    5483
Total:        354  579 666.2    458    5484
Percentage of the requests served within a certain time (ms)
  50%    458
  66%    492
  75%    517
  80%    539
  90%    602
  95%    667
  98%   3572
  99%   5484
 100%   5484 (longest request)

Как видим скорость обслуживания отличается в 100 раз!

Возможно тесты покажуться слишком хитрыми (новости мы получаем из внешнего источника, что очень медленно). Но следует понимать, что пример содержит очень мало логики и для симуляции реального примера был использован 1 внешний запрос. По существу это тоже самое, что несколько десятков запросов к MySQL + несколько сотен запросов к memcached + логика приложения + логика отображения.

Итог

Применение принципа частичного кеширования страниц позволяет кешировать страницы с динамическими и персональными данными с нетривиальной логикой. Плюсы такого подхода:

  1. Существенно увеличивается скорость отдачи страниц
  2. Сильно разгружаются бекенд сервера от повторяющейся логики
Текущие проблемы
  • Применение подобного подхода требует достаточно серъезных изменений в структуре приложения
  • Очень большая персонализация страниц на больших объемах будет требовать больших объемов оперативной памяти
  • Данный подход требует редко изменяющейся разметки данных, т.к. частое ее изменение будет приводить к постоянному сбросу кеша, что малоэффективно

В качестве альтернативных решений следует привести связку Nginx + SSI + Memcache, которая будет рассмотрена в будущих статьях.

Ссылки

Описание работы ESI в Varnish
Исходники примеров

Какие еще есть решения частичного кеширования страниц?

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com
Related posts:
  1. Nginx + Memcached + SSI — кеширование страниц и блоков (partials)
  2. Varnish — быстрый старт
  3. Varnish

Cистема хостинга медиа (перепечатка)

Комментариев нет

flash_logo

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

Что такое подсистема хостинга медиа вообще? Это часть системы, которая отвечает за загрузку, сохранение, преобразование (транскодирование) и отдачу медиа файлов. Зачастую эта система является наиболее ресурсоемкой ввиду больших объемов данных и процессорных затрат.

Самое главное

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

Оборудование

Как всегда перед Вами выбор — покупать дорогой комплекс (файловый супер сервер) либо строить систему из дешевых серверов. Как всегда выбор падает на второй вариант, так как:

  • Решение обладает бесконечной масштабируемостью
  • Отсутствует единая точка сбоя (SPOF), делая это решение очень устойчивым к сбоям
  • Динамику наращивания железа легко контролировать
  • Дешевое оборудование — дешевое все остальное (коммутаторы, поддержка, замена и т.п.)

По сравнению с дорогостоящим комплексом, такое решение обладает единственным недостатком: реализацию самой подсистемы распределения файлов придется разрабатывать самим. При грамотном подходе это вовсе не большой кусок работы. На connect.ua переход на такое решение занял около недели (на тот момент у нас было 2.5 Тб данных).

Система репликации позволяет избежать установки дорогостоящих RAID контроллеров и уменьшает вероятность сбоя системы практически до нуля.

Архитектура

Перед нами стоят следующие задачи:

  • Загрузка файлов
  • Траскодирование файлов
  • Распредение файлов по серверам
  • Выдача файлов

В общем плане схема работы подсистемы такова:

media-hosting

Теперь детальнее. У нас есть ряд серверов-хранилищ, каждый из которых ничего не знает о всех остальных. Все они доступны извне под определенным адресом (например, m1.connect.ua, m2.connect.ua и т.п.). После получения файла (загрузка пользователя) на бекенд, мы выбираем из набора серверов тот, на который мы сохраним файл. Выбор делается на основе предпочитаемого критерия (случано; учитывая свободное место; учитывая загрузку процессора и т.п.).

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

Репликация

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

Распределенные файловые системы

Обычно медиа-подсистемы характеризуются огромными объемами отдачи данных. Минус использования распределенных файловых систем в нашем случае в том, что Вы получаете огромный внутрисетевой трафик, и это выливается в малую эффективность при отдаче файлов. После ухода от GlusterFS на connect.ua наш исходящий медиа трафик вырос со 150 до 800 Мб/с.

Изоляция крупных и мелких файлов

Обязательно разносите мелкие и крупные файлы на разные физические хранилища (например, нужно разнести видео ролики и скриншоты к видео). Если они будут обслуживаться на одном сервере, то Ваша система будет медленнее черепахи. Это происходит потому, что чтение больших файлов не дает эффективно использовать дисковый кеш для мелких, и поэтому у Вас будет огромное количество рандомных чтений с диска. А это выльется в очень медленную отдачу файлов.

Cloud hosting

Сегодня большую популярность набирают cloud хостинги. Это решение имеет смысл, когда Вы планируете разработать и вывести прототипное решение.

Поскольку наращивание мощностей на таком хостинге экспоненциально влияет на его стоимость, то на определенном этапе следует делать выбор в сторону своей системы хранения. Но следует отметить, что cloud хостинги также обеспечивают высокую надежность и доступность (за что собственно и нужно платить), а это иногда немаловажный фактор.

Какие технологии Вы используете для обмена файлами между серверами? Используете ли распределенные файловые системы?

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com
Related posts:
  1. Отдача и ресайзинг фотографий

Оптимизация баннерной рекламы (перепечатка)

Комментариев нет

new-banners
Рекламной моделью сегодня пользуется большинство интернет проектов, тем более крупных. А это значит, что практически все пользуются баннерными системами.

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

Почему так происходит и что делать?


Почему баннеры делают сайт медленнее

Как обычно работают системы открутки баннерной рекламы? Зачастую это просто JavaScript код, который вставляется в нужное место на странице (туда где баннер будет). Далее по мере загрузки страницы, выполняется этот самый код, который делает запрос к баннерной системе. Далее она (баннерная система) генерирует код баннера, который и отрисовывается на страничке. Только после этого продолжается загрузка страницы.

Зачастую отрисовка баннера проходит не через одну баннерную систему, а через несколько. Это еще больше замедляет загрузку страницы, т.к. все время, пока не отрисутеся баннер, страница дальше не грузится.

Учитывая это, а также то, что баннеров на странице обычно несколько, эффект от влияния рекламы на скорость загрузки страницы может быть очень негативным.

Для оптимизации нужно сделать следующее:

  1. Выбрать быструю и стабильную баннерную систему (!)
  2. Отсрочить загрузку баннеров до окончания загрузки всей страницы
  3. Оптимизировать загрузку самих баннеров

Далее — подробнее.

Баннерные системы

В качестве системы открутки баннерной рекламы есть выбор из двух вариантов: ставить свою собственную и использовать hosted (внешнее) решение.

Ставить свою собственную систему открутки крайне не выгодно. Ни одно из современных решений не является эффективным, а проблем с производительностью будет масса. Мы когда-то использовали OpenX, которая хорошо масштабируется, но для этого ей нужны “хорошие масштабы железа”.

В качестве внешних решений, есть целый ряд предложений. Мы в качестве такого решения используем Google Ad Manager. Продукты компании Google — это хороший выбор в терминах надежности и стабильности, как и в быстродействии.


Настоятельно рекомендую выбирать внешнее (но надежное) решение. Исключение могут составлять ситуации, когда рынок проекта накладывает ограничения на такой выбор (например, слабое развитие услуг доступа к Интернет, дорогие зарубежные каналы и т.п.).

Отложенная загрузка баннерной рекламы

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


Большинство баннерных кодов используют JS вызов document.write (). Это делает невозможным прямой запуск такого кода на событии body.onLoad ().



Тем не менее есть два варианта откладывания загрузки баннеров до полной загрузки страницы:

  1. Все баннерные коды нужно вставлять в скрытые блоки (со стилем style=”display: none”). Современные браузеры имеют встроенные механизмы оптимизации, поэтому всё, что находится в скрытых элементах не отрисовывается вообще. Далее на событие body.onload () нужно повесить вызов, который покажет все скрытые баннерные блоки. Пример с использование jQuery:
    ...
    Баннерный блок:
    <div style="display: none;" class="banner"><script>Тут баннерный код</script></div>
    ...
    <script>$(document).ready(function() { $('.banner').show(); });</script>
    ...
    
  2. Выносите баннеры в iframe. Формально это не отложит загрузку, но баннеры будут грузится параллельно с загрузкой страницы, а не последовательно. Это позволяет добиться максимального эффекта. В этом случае единственное неудобство — фиксированный размер фрейма.

Оптимизация загрузки баннеров

Очень часто рекламные кампании предусматривают показ одного рекламного сообщения по несколько раз каждому посетителю. Это значит, что не следует пренебрегать клиентской оптимизацией самих баннеров (сжатие, HTPP заголовки управления кешом и .т.п) — детальнее в статье “Оптимизация клиентской части“.

Сталкивались ли Вы с проблемами производительности баннерной рекламы, и как их решали?

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com
Related posts:
  1. Оптимизация картинок для Web
  2. Google analytics — отслеживание скорости загрузки страниц