Портал для веб-мастера
Вход пользователей
Поиск статей
WoWeb.ru » Статьи » Web-Технологии

Создание больших web-проектов

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

Большие скорости

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

Создание модулей

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

1) сервер Apache определяет perl-скрипт для запуска, подготавливает и запускает его;
2) запуск скрипта фактически начинается с запуска perl-интерпретатора (это файл, размером около полумегабайта). Perl-интерпретатор, запустившись, размещается на 2-х мегабайтах в памяти машины, и только после этого приступает к работе с пользовательским скриптом;
3) эта работа начинается с компиляции программы. Компиляция программы - это, как правило, один из самых длительных этапов обработки программы;
4) только после предварительной компиляции (в байткод) скрипт начнет выполняться.


Статистика удручает: время, которое тратится на запуск perl-интерпретатора и компиляцию скрипта, как правило, на порядок больше времени, за которое он выполняется. 
На каждом сайте существуют узкие места - программы, которые вызываются очень часто. Например, баннерный движок. Как правило, на один просмотр страницы приходится два-три баннера, а значит и вызова программы. Понятно, что если избавиться от накладных расходов (пункты 2 и 3), работа сервера значительно ускорится. Это можно сделать двумя похожими способами.
Первый - написать модуль к Apache и вкомпилировать его в сервер. Именно так в баннерной сети Фламинго-2 (http://www.f2.ru), в создании которой я принимал участие, была реализована часть системы, которая раздавала баннеры пользователям. Это был модуль, написанный на языке C, который функционировал как часть сервера Apache и поэтому работал очень быстро.
Второй способ - использовать технологии предкомпиляции программ. Таких технологий достаточно много. Например, для perl-скриптов это могут быть FastCGI и mod_perl. Расскажу подробней о mod_perl. Это вкомпилированный (опять же в виде модуля) в Apache perl-компилятор. Во-первых, даже для простых скриптов (при надлежащей настройке) это исключает вторую стадию выполнения. Но кроме этого mod_perl дает возможность писать хэндлеры - обработчики определенных стадий выполнения запроса. Это очень мощная технология, поэтому рассмотрим ее подробнее.
Можно, например, написать хэндлер, который будет вызываться при запросе определенного URL. Делается это так. В файл httpd.conf вы прописываете следующие строки:

<Perl>

unshift(@INC, 'Путь к Вашему модулю');

@PerlModule = qw(MyHandler);

%Location = (
'/myhandler' => {
'PerlHandler' => 'MyHandler::view',
'SetHandler' => 'perl-script',
'PerlSendHeader' => 'on'
},
);

</Perl>

Тем самым вы указываете Apache и модулю mod_perl, что если пользователь запросит URL /myhandler, то для его обработки должен запуститься модуль MyHandler, а в нем процедура view. После изменения httpd.conf надо перезагрузить Apache. Кстати, все указанные в конфигурационном модуле файлы будут компилироваться при загрузке сервера, а не при первом запросе. Это в несколько раз увеличит скорость работы сервера.
Модуль MyHandler.pm может выглядеть, например, так:

package MyHandler;
use strict;

# Процедура view
sub view {
print "<HTML>\n<BODY>\nУра! Это отработал наш хэндлер!</BODY>\n</HTML>\n";
}

1;

Механизм хэндлеров обладает мощными возможностями. Фактически вы можете заменить любую стадию обработки запросов. Рассмотрим для примера создание собственного механизма проверки пароля:

package MyAuthorization;
use strict;

# Обработчик, запрашивающий пароль
sub handler {
my $r = shift;

return AUTH_REQUIRED unless $r;

my (undef, $password) = $r->get_basic_auth_pw;
my ($login) = $r->connection->user;

return AUTH_REQUIRED unless $password;

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

my $rev_login = reverse($login);

# Проверка пароля
if ($rev_login ne $passwd_sent)
{
return AUTH_REQUIRED;
} else {
return OK;
}

};

1;

В файле настроек сервера httpd.conf необходимо указать, что авторизовать пользователя мы будем сами:

%Location = (
'/myhandler' => {
'PerlHandler' => 'MyHandler::view',
'SetHandler' => 'perl-script',
'PerlSendHeader' => 'on'
'require' => 'valid-user',
'Limit' => {
'METHODS' => 'GET POST'
},
'AuthType' => 'Basic',
'AuthName' => 'PersonaUser',
'PerlAuthenHandler' => 'MyAuthorization ->handler()'
},
);

Теперь доступ к /myhandler защищен - браузер выведет пользователю стандартное окно для ввода пароля. 
Более подробно с технологией mod_perl можно познакомиться на сайте http://perl.apache.org/

Использование конвейеров

Старайтесь не производить обработку данных в интерактивных скриптах. Записывайте их в лог-файлы, а затем агрегируйте и обрабатывайте уже отдельным процессом. Например, ответ пользователя в интерактивном голосовании может вызывать у вас изменения в десятке различных параметров статистики (распределение ответов, активность пользователей, общее число проголосовавших и так далее). Не проводите их сразу. Вместо этого разбейте процедуру на две части. Первая - непосредствен- но голосование, запись результата и вывод ответной страницы пользователю. Вторая - обработка голосования, изменение статистики и т.д. 
Вообще надо стараться минимизировать количество интерактивных операций. В идеальном случае скрипт для учета голосования вообще ничего не делает, кроме записи информации в лог-файл. А для обработки данных из лог-файла можно запускать отдельный процесс-демон.
Для примера рассмотрим механизм обработки статистики в баннерной сети Фламинго-2. В ней был реализован 4-х ступенчатый конвейер:
1) Информация о каждом запросе записывалась в полный лог. Это была очень подробная информация и записывалась она без всякого сжатия, на которое потратилось бы много времени. Размер этого лога очень велик - одна запись в нем занимала 250 байт. Данные в этом логе не хранились дольше нескольких часов.
2) С периодичностью раз в 10 минут запускалась программа, которая обрабатывала полный лог и в компактном виде писала информацию в таблицы базы данных. На этой же стадии учитывались показы, изменялись временные таблицы, используемые для выдачи баннеров пользователю и для работы следующих стадий.
3) Часовой демон, который строил почасовую статистику, производил сложные географические расчеты и многое другое, запускался в конвейере один раз в час. Он уже не имел доступа к полному логу и использовал информацию исключительно из второй стадии.
4) В задачи последней стадии входила дневная ротация файлов, статистика, подведение балансов и рассылка почтовых предупреждений. Эта стадия работала каждые сутки поздно ночью, когда нагрузка на сервер была минимальной.

Как видите, механизм достаточно сложный, и наладить его корректную работу было нелегко. Чем больше стадий, тем больше проблем при их сопряжении друг с другом. Тем не менее, такая система позволяла достаточно эффективно распределять нагрузку и шустро работала на простом IDE-диске (расчетная пропускная способность была около 2-3 миллионов обращений в день при пиковой нагрузке 200 обращений в секунду). При этом система вела большое количество статистики.
Итак, резюмируем: для увеличения скорости работы программ, взаимодействующих с пользователем, разбиваем их работу на части, причем интерактивная часть должна содержать минимум расчетов и операций записи. Все необходимые расчеты можно произвести позднее, в более благоприятное с точки зрения нагрузки время и более эффективно.

Базы данных

Используйте хорошую базу данных. Какую выбрать? Единого рецепта нет. Все зависит от решаемой задачи. Если она достаточно простая и вам не требуется выполнять сложные SQL-запросы (например, вложенные), то наилучшим решением будет, пожалуй, база данных MySQL.
MySQL - один из самых простых серверов БД. Но даже в этой простой базе есть свои способы оптимизации для ускорения запросов. Например, не секрет, что INSERT - одна из самых длительных операций (вычисление физического адреса для вставки, вставка, решение проблемы фрагментации, изменение индексов и служебных таблиц). Хороший прием для ускорения работы скрипта, который вставляет данные в БД - замена операции INSERT операцией INSERT DELAYED (отложенная вставка). Обновление данных будет выполнено только тогда, когда это не приведет к замедлению работы сервера.
Другой пример: если внимательно почитать документацию MySQL, можно найти упоминание о таблицах, расположенных в памяти (HEAP tables). Очевидно, что операции с такими таблицами совершаются значительно быстрее. Heap-таблицы можно использовать для решения некоторых задач.
Существует большое количество параметров запуска сервера БД, оптимизирующих буферы сортировки, вычислений, количество детей и другие параметры. Как правило, вам заранее известно, что вы будете делать с базой, и для повышения быстродействия можно задать соответствующие параметры. Например, возьмем вполне реальную задачу: построение какого-нибудь каталога. Ясно, что это будет одна большая таблица с большим количеством индексов. Вы знаете, что будете использовать представления. Работа с этой таблицей будет заключаться в запросах по индексу без использования сортировки. Посмотрим, как можно настроить сервер БД на выполнение такой задачи (пример из MySQL 3.23.25):

  • join_buffer_size - буфер для создания представлений, по умолчанию равен 131072 байта;

  • key_buffer_size - буфер для работы с ключами и индексами. Размер по умолчанию - 1048540;

  • sort_buffer - буфер для сортировки. По умолчанию - 2097116 байт.

Скорее всего, при увеличении какого-то буфера, скорость выполнения связанной с ним задачи увеличится. Исходя из нашей задачи, мы увеличим буфер для работы с ключами (скорость выборки значений из таблицы увеличится), уменьшим буфер сортировки (уменьшится скорость сортировки) и буфер представлений (уменьшится скорость работы с представлениями).
Строка запуска демона MySQL будет выглядеть примерно так (конкретные значения зависят от количества памяти в системе):

shell>safe_mysqld -O key_buffer=8M -O sort_buffer=1M -O join_buffer=16K

Резюмируем. При использовании базы данных работу скрипта можно значительно ускорить правильной настройкой сервера БД. В руководстве базы данных MySQL есть специальный раздел, посвященный оптимизации. За более подробной информацией можно обратиться на сайты:
Разработчики MySQL - http://www.mysql.com
Разработчики PostgreSQL - http://www.PostgreSQL.org/
Оптимизация MySQL - http://www.mysql.cz/information/presentations/presentation-oscon2000-20000719/index.html и http://support.ultrahost.ru/mysql_opt.php

Большие объемы

Еще одна проблема больших сайтов - большой объем информации. Если не применять никаких ухищрений, то поддержка простого html-сайта в какой-то момент потребует слишком много времени.

Объектно-ориентированное программирование

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

Однако если перечислить сущности, с которыми имеют дело вышеперечисленные скрипты, мы получим очень интересные результаты:

  • Сущность "пользователь". Имеет свое имя, фамилию, ник, пароль, электронный адрес… Используется практически во всех скриптах в разных ипостасях.

  • Сущность "сообщение". Вы можете возразить, что сообщения везде разные. Ничего подобного! Различаются формы представления сообщений, а данные, структура полей и методы обработки - одни. Автор, заголовок, тело - и так во всех проектах.

Вот фактически и все сущности, с которыми оперирует большинство скриптов на сайте. Гостевая книга (она, кстати, сама может быть объектом в более сложных проектах) представляет собой цепочку объектов класса "сообщение". Форум или конференция - те же сообщения, организованные иерархически. Отправка письма владельцу сайта - сообщение. Рассылка анонсов - перебор объектов класса "пользователь" и отправка каждому объекта класса "сообщение".
Было бы эффективно описать все эти объекты в одном месте, а потом строить из них, как из кирпичиков, программы и скрипты, просто вставляя вызовы объектов в код. К тому же, единое пространство сообщений, пользователей и других объектов значительно расширяет поле для творчества.
В этом и есть сущность объектного подхода. Вы создаете множество объектов - кирпичиков будущих программ - и из них строите свои сайты. Кроме того, вы можете использовать такие мощные методы ООП как наследование и полиформизм, без которых уже немыслимо построение крупных проектов.

Шаблонирование

Об этом я тоже расскажу вкратце; возможно этому будет посвящена статья в одном из следующих номеров "Программиста". Вернемся к системе Фламинго. Как был организован интерфейс этой баннерной сети? 400 видов статистики соответствуют 400 страницам? Нет. Один скрипт-шаблонизатор, которому передаются параметры - номер статистики и другие данные: даты, ограничения и т.д.
По уникальному номеру статистики скрипт считывал описание, которое состояло из имени файла с псевдо-html и имен файлов с SQL-запросами. Файл с описанием выглядел так:

2:data/html/2.htx,data/queries/info.sql
9:data/html/9.htx,data/queries/ban-list-one.sql,data/queries/get-banners-list.sql
12:data/html/12.htx,data/queries/ban-getinfo.sql
38:data/html/38.htx,data/queries/acc-hosts-hits.sql 
44:data/html/44.htx,data/queries/acc-getsites-today.sql

Общая схема очень проста - выполнить все SQL-запросы и вставить результаты в псевдо-html, получив таким образом полноценную страничку, и выдать ее пользователю. Например, для вывода статистики с номером 2 (информация об аккаунте), требовалось выполнить SQL-запрос data/queries/info.sql, результаты вставить в data/html/2.htx. Результат вывести на экран.
А вот как обстояло дело подробнее. Первая задача - формирование SQL-запроса. В него нужно вставить идентификатор пользователя и другие параметры, которые переданы скрипту. Типичный пример SQL-запроса (data/queries/info.sql):

select
AccountName,
OwnerName,
OwnerEmail,
MainSite,
SiteName
from
Accounts
where
AccountId = <--AccountId-->

При разборе такого запроса значение параметра вставлялось на место строки <--ИмяПараметра-->. Существовали и специальные параметры, например - <--UserName--> - имя пользователя и <--AccountId--> - вычисленный по имени идентификатор аккаунта.
Результат выполнения полученного запроса заносился в html следующим образом. Каждое полученное из базы данных значение получало "имя", с помощью которого обозначалось его местоположение в html-шаблоне. Имя было составным. Первая часть - порядковый номер SQL-запроса, вторая часть - индекс значения в массиве результатов.
Допустим, выполнялся SQL-запрос с порядковым номером 1 (для примера рассмотрим запрос data/queries/info.sql). Запрос возвращал массив значений. Соответственно, значение AccountName, возвращенное базой данных, имело порядковый номер 0 в этом массиве. В html-шаблоне место, куда необходимо было вставить AccountName обозначалось как <--1.1-->. 

Кусочек HTML-шаблона data/html/2.htx из нашего примера:

<TABLE BORDER=0 WIDTH=460>
<TR>
<TD WIDTH="50%">
<FONT SIZE="-1">
Имя, фамилия ответственного:
</FONT>
</TD><TD>
<INPUT type="text" name="OwnerName" size=33 value="<--1.1-->">
</TD>
</TR>

<TR>
<TD>
<FONT SIZE="-1">
Электронный адрес:
</TD><TD>
<INPUT type="text" name="OwnerEmail" size=33 value="<--1.2-->">
</TD>
</TR>

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

Заключение

Я хотел бы еще раз повторить: нет решений на все случаи жизни. Каждый раз, в каждом проекте вам придется придумывать собственные методы оптимизации быстродействия и удобства работы. Я надеюсь, что приемы, о которых я рассказал, пригодятся вам. Если у вас возникнут какие-нибудь вопросы или уточнения, я готов обсудить их с вами - vbob@aha.ru

Автор: Олег Бунин · Добавлена: 2003-11-05
Просмотров: 8603 · Рейтинг: 4.1

Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]

Категории раздела
Flash
Apache
WWW
PhotoShop
Веб-дизайн
Раскрутка и реклама
Базы данных
3D графика
Хостинг
Истории веб-мастеров
Web-технологии
Сетевая безопасность
Программирование для Web
Операционные системы

Новые статьи
Лучшие статьи
Популярные статьи
Комментируемые статьи
Разделы сайта
Скрипты
Статьи
Шрифты
Флэш исходники
HTML шаблоны
Партнерки
Клипарты
Смайлы
Фоны
Гифы
Иконки
Опрос сайта
Есть ли у вас свой сайт?
Всего ответов: 140573
Наша кнопка
WoWeb.ru - портал для веб-мастера