Asp разработка масштабируемых компонентов
Уже немало слов было сказано по этой теме как в моем блоге, так и за его пределами. Мне кажется настал подходящий момент для того, чтобы перейти от частного к общему и попытаться взглянуть на данную тему отдельно от какой-либо успешной ее реализации.
Для начала имеет смысл определиться с тем, о чем мы вообще будем говорить. В данном контексте перед веб-приложением ставятся три основные цели:
- масштабируемость — способность своевременно реагировать на непрерывный рост нагрузки и непредвиденные наплывы пользователей;
- доступность — предоставление доступа к приложению даже в случае чрезвычайных обстоятельств;
- производительность — даже малейшая задержка в загрузке страницы может оставить негативное впечатление у пользователя.
Основной темой разговора будет, как не трудно догадаться, масштабируемость, но и остальные цели не думаю, что останутся в стороне. Сразу хочется сказать пару слов про доступность, чтобы не возвращаться к этому позднее, подразумевая как «само собой разумеется»: любой сайт так или иначе стремится к тому, чтобы функционировать максимально стабильно, то есть быть доступным абсолютно всем своим потенциальным посетителям в абсолютно каждый момент времени, но порой случаются всякие непредвиденные ситуации, которые могут стать причиной временной недоступности. Для минимизации потенциального ущерба доступности приложения необходимо избегать наличия компонентов в системе, потенциальный сбой в которых привел бы к недоступности какой-либо функциональности или данных (или хотябы сайта в целом). Таким образом каждый сервер или любой другой компонент системы должен иметь хотябы одного дублера (не важно в каком режиме они будут работать: параллельно или один «подстраховывает» другой, находясь при этом в пассивном режиме), а данные должны быть реплицированы как минимум в двух экземплярах (причем желательно не на уровне RAID, а на разных физических машинах). Хранение нескольких резервных копий данных где-то отдельно от основной системы (например на специальных сервисах или на отдельном кластере) также поможет избежать многих проблем, если что-то пойдет не так. Не стоит забывать и о финансовой стороне вопроса: подстраховка на случай сбоев требует дополнительных существенных вложений в оборудование, которые имеет смысл стараться минимизировать.
Масштабируемость принято разделять на два направления:
Вертикальная масштабируемость Увеличение производительности каждого компонента системы c целью повышения общей производительности. Горизонтальная масштабируемость Разбиение системы на более мелкие структурные компоненты и разнесение их по отдельным физическим машинам (или их группам) и/или увеличение количества серверов параллельно выполняющих одну и ту же функцию.
Так или иначе, при разработке стратегии роста системы приходится искать компромис между ценой, временем разработки, итоговой производительность, стабильностью и еще массой других критериев. С финансовой точки зрения вертикальная масштабируемость является далеко не самым привлекательным решением, ведь цены на сервера с большим количеством процессоров всегда растут практически экспоненциально относительно количества процессоров. Именно по-этому наиболее интересен горизонтальный подход, так как именно он используется в большинстве случаев. Но и вертикальная масштабируемость порой имеет право на существование, особенно в ситуациях, когда основную роль играет время и скорость решения задачи, а не финансовый вопрос: ведь купить БОЛЬШОЙ сервер существенно быстрее, чем практически заново разрабатывать приложения, адаптируя его к работе на большом количестве параллельно работающих серверов.
Закончив с общими словами давайте перейдем к обзору потенциальных проблем и вариантов их решений при горизонтальном масштабировании. Просьба особо не критиковать — на абсолютную правильность и достоверность не претендую, просто «мысли вслух», да и даже упомянуть все моменты данной темы у меня определенно не получится.
Серверы приложений
В процессе масштабирования самих приложений редко возникают проблемы, если при разработке всегда иметь ввиду, что каждый экземпляр приложения должен быть непосредственно никак не связан со своими «коллегами» и должен иметь возможность обработать абсолютно любой запрос пользователя вне зависимости от того где обрабатывались предыдущие запросы данного пользователя и что конкретно он хочет от приложения в целом в текущий момень.
Далее, обеспечив независимость каждого отдельного запущенного приложения, можно обрабатывать все большее и большее количество запросов в единицу времени просто увеличивая количество параллельно функционирующих серверов приложений, участвующих в системе. Все достаточно просто (относительно).
Балансировка нагрузки
Следущая задача — равномерно распределить запросы между доступными серверами приложений. Существует масса подходов к решению этой задачи и еще больше продуктов, предлагающих их конкретную реализацию.
Оборудование Сетевое оборудование, позволяющее распределять нагрузку между несколькими серверами, обычно стоит достаточно внушительные суммы, но среди прочих вариантов обычно именно этот подход предлагает наивысшую производительность и стабильность (в основном благодаря качеству, плюс такое оборудование иногда поставляется парами, работающими по принципу HeartBeat). В этой индустрии достаточно много серьезных брендов, предлагающих свои решения — есть из чего выбрать: Cisco, Foundry, NetScalar и многие другие. Программное обеспечение В этой области еще большее разнообразие возможных вариантов. Получить программно производительность сопоставимую с аппаратными решениями не так-то просто, да и HeartBeat придется обеспечивать программно, но зато оборудование для функционирования такого решения представляет собой обычный сервер (возможно не один). Таких программных продуктов достаточно много, обычно они представляют собой просто HTTP-серверы, перенаправляющие запросы своим коллегам на других серверах вместо отправки напрямую на обработку интерпретатору языка программирования. Для примера можно упомянуть, скажем, nginx с mod_proxy . Помимо этого имеют место более экзотические варианты, основанные на DNS, то есть в процессе определения клиентом IP-адреса сервера с необходимым ему интернет-ресурсов адрес выдается с учетом нагрузки на доступные сервера, а также некоторых географических соображений.
Каждый вариант имеет свой ассортимент положительных и отрицательных сторон, именно по-этому однозначного решения этой задачи не существует — каждый вариант хорош в своей конкретной ситуации. Не стоит забывать, что никто не ограничивает Вас в использовании лишь одного из них, при необходимости может запросто быть реализована и практически произвольная комбинация из них.
Ресурсоемкие вычисления
Во многих приложениях используются какие-либо сложные механизмы, это может быть конвертирование видео, изображений, звука, или просто выполнение каких-либо ресурсоемких вычислений. Такие задачи требует отдельного внимания если мы говорим о Сети, так как пользователь интернет-ресурса врядли будет счастлив наблюдать за загружающейся несколько минут страницей в ожидании лишь для того, чтобы увидеть сообщение вроде: «Операция завершена успешно!».
Для избежания подобных ситуаций стоит постараться минимизировать выполнение ресурсоемких операций синхронно с генерацией интернет страниц. Если какая-то конкретная операция не влияет на новую страницу, отправляемую пользователю, то можно просто организовать очередь заданий, которые необходимо выполнить. В таком случае в момент когда пользователь совершил все действия, необходимые для начала операции, сервер приложений просто добавляет новое задание в очередь и сразу начинает генерировать следущую страницу, не дожидаясь результатов. Если задача на самом деле очень трудоемкая, то такая очередь и обработчики заданий могут располагаться на отдельном сервере или кластере.
Если результат выполнения операции задействован в следующей странице, отправляемой пользователю, то при асинхронном ее выполнении придется несколько схитрить и как-либо отвлечь пользователя на время ее выполнения. Например, если речь идет о конвертировании видео в flv, то например можно быстро сгенерировать скриншот с первым кадром в процессе составления страницы и подставить его на место видео, а возможность просмотра динамически добавить на страницу уже после, когда конвертирование будет завершено.
Еще один неплохой метод обработки таких ситуаций заключается просто в том, чтобы попросить пользователя «зайти попозже». Например, если сервис генерирует скриншоты веб-сайтов из различных браузеров с целью продемонстрировать правильность их отображения владельцам или просто интересующимся, то генерация страницы с ними может занимать даже не секунды, а минуты. Наиболее удобным для пользователя в такой ситуации будет предложение посетить страницу по указанному адресу через столько-то минут, а не ждать у моря погоды неопределенный срок.
Сессии
Практически все веб-приложения каким-либо образом взаимодействуют со своими посетителями и в подавляющем большинстве случаев в них присутствует необходимость отслеживать перемещения пользователей по страницам сайта. Для решения этой задачи обычно используется механизм сессий, который заключается в присвоении каждому посетителю уникального идентификационного номера, который ему передается для хранения в cookies или, в случае их отсутствия, для постоянного «таскания» за собой через GET. Получив от пользователя некий ID вместе с очередным HTTP-запросом сервер может посмотреть в список уже выданных номеров и однозначно определить кто его отправил. С каждым ID может ассоциироваться некий набор данных, который веб-приложение может использовать по своему усмотрению, эти данные обычно по-умолчанию хранятся в файле во временной директории на сервере.
Казалось бы все просто, но. но запросы посетителей одного и того же сайта могут обрабатывать сразу несколько серверов, как же тогда определить не был ли выдан полученный ID на другом сервере и где вообще хранятся его данные?
Наиболее распространенными решениями является централизация или децентрализация сессионных данных. Несколько абсурдная фраза, но, надеюсь, пара примеров сможет прояснить ситуацию:
Централизованное хранение сессий Идея проста: создать для всех серверов общую «копилку», куда они смогут складывать выданные ими сессии и узнавать о сессиях посетителей других серверов. В роли такой «копилки» теоретически может выступать и просто примонтированная по сети файловая система, но по некоторым причинам более перспективным выглядит использование какой-либо СУБД, так как это избавляет от массы проблем, связанных с хранением сессионных данных в файлах. Но в варианте с общей базой данных не стоит забывать, что нагрузка на него будет неуклонно расти с ростом количества посетителей, а также стоит заранее предусмотреть варианты выхода из проблематичных ситуаций, связанных с потенциальными сбоями в работе сервера с этой СУБД. Децентрализованное хранение сессий Наглядный пример — хранение сессий в memcached, изначально расчитанная на распределенное хранение данных в оперативной памяти система позволит получать всем серверам быстрый доступ к любым сессионным данным, но при этом (в отличии от предыдущего способа) какой-либо единый центр их хранения будет отсутствовать. Это позволит избежать узких мест с точек зрения производительности и стабильности в периоды повышенных нагрузок.
В качестве альтернативы сессиям иногда используют похожие по предназначению механизмы, построенные на cookies, то есть все необходимые приложению данные о пользователе хранятся на клиентской стороне (вероятно в зашифрованном виде) и запрашиваются по мере необходимости. Но помимо очевидных преимуществ, связанных с отсутствием необходимости хранить лишние данные на сервере, возникает ряд проблем с безопасностью. Данные, хранимые на стороне клиента даже в зашифрованном виде, представляют собой потенциальную угрозу для функционирования многих приложений, так как любой желающий может попытаться модифицировать их в своих интересах или с целью навредить приложению. Такой подход хорош только если есть уверенность, что абсолютно любые манипуляции с хранимые у пользователей данными безопасны. Но можно ли быть уверенными на 100%?
Статический контент
Пока объемы статических данных невелики — никто не мешает хранить их в локальной файловой системе и предоставлять доступ к ним просто через отдельный легковесный веб-сервер вроде lighttpd (я подразумеваю в основном разные формы медиа-данных), но рано или поздно лимит сервера по дисковому пространству или файловой системы по количеству файлов в одной директории будет достигнут, и придется думать о перераспределении контента. Временным решением может стать распределение данных по их типу на разные сервера, или, возможно, использование иерархической структуры каталогов.
Если статический контент играет одну из основных ролей в работе приложения, то стоит задуматься о применении распределенной файловой системы для его хранения. Это, пожалуй, один из немногих способов горизонтально масштабировать объем дискового пространства путем добавления дополнительных серверов без каких-либо кардинальных изменений в работе самого приложения. На какой именно кластерной файловой системе остановить свой выбор ничего сейчас советовать не хочу, я уже опубликовал далеко не один обзор конкретных реализаций — попробуйте прочитать их все и сравнить, если этого мало — вся остальная Сеть в Вашем распоряжении.
Возможно такой вариант по каким-либо причинам будет нереализуем, тогда придется «изобретать велосипед» для реализации на уровне приложения принципов схожих с сегментированием данных в отношении СУБД, о которых я еще упомяну далее. Этот вариант также вполне эффективен, но требует модификации логики приложения, а значит и выполнение дополнительной работы разработчиками.
Альтернативой этим подходам выступает использование так называемых Content Delievery Network — внешних сервисов, обеспечивающих доступность Вашего контента пользователям за определенное материальное вознаграждение сервису. Преимущество очевидно — нет необходимости организовывать собственную инфраструктуру для решения этой задачи, но зато появляется другая дополнительная статья расходов. Список таких сервисов приводить не буду, если кому-нибудь понадобится — найти будет не трудно.
Кэширование
Кэширование имеет смысл проводить на всех этапах обработки данных, но в разных типах приложений наиболее эффективными являются лишь некоторые методы кэширования.
СУБД Практически все современные СУБД предоставляют встроенные механизмы для кэширования результатов определенных запросов. Этот метод достаточно эффективен, если Ваша система регулярно делает одни и те же выборки данных, но также имеет ряд недостатков, основными из которых является инвалидация кэша всей таблицы при малейшем ее изменении, а также локальное расположение кэша, что неэффективно при наличии нескольких серверов в системе хранения данных. Приложение На уровне приложений обычно производится кэширование объектов любого языка программирования. Этот метод позволяет вовсе избежать существенной части запросов к СУБД, сильно снижая нагрузку на нее. Как и сами приложения такой кэш должен быть независим от конкретного запроса и сервера, на котором он выполняется, то есть быть доступным всем серверам приложений одновременно, а еще лучше — быть распределенным по нескольким машинам для более эффективной утилизации оперативной памяти. Лидером в этом аспекте кэширования по праву можно назвать memcached, о котором я в свое время уже успел подробно рассказать. HTTP-сервер Многие веб-серверы имеют модули для кэширования как статического контента, так и результатов работы скриптов. Если страница редко обновляется, то использование этого метода позволяет без каких-либо видимых для пользователя изменений избегать генерации страницы в ответ на достаточно большую часть запросов. Reverse proxy Поставив между пользователем и веб-сервером прозрачный прокси-сервер, можно выдавать пользователю данные из кэша прокси (который может быть как в оперативной памяти, так и дисковым), не доводя запросы даже до HTTP-серверов. В большинстве случаев этот подход актуален только для статического контента, в основном разных форм медиа-данных: изображений, видео и тому подобного. Это позволяет веб-серверам сосредоточиться только на работе с самими страницами.
Кэширование по своей сути практически не требует дополнительных затрат на оборудование, особенно если внимательно наблюдать за использованием оперативной памяти остальными компонентами серверами и утилизировать все доступные «излишки» под наиболее подходящие конкретному приложению формы кэша.
Инвалидация кэша в некоторых случаях может стать нетривиальной задачей, но так или иначе универсального решения всех возможных проблем с ней связанных написать не представляется возможным (по крайней мере лично мне), так что оставим этот вопрос до лучших времен. В общем случае решение этой задачи ложится на само веб-приложение, которое обычно реализует некий механизм инвалидации средствами удаления объекта кэша через определенный период времени после его создания или последнего использования, либо «вручную» при возникновении определенных событий со стороны пользователя или других компонентов системы.
Базы данных
На закуску я оставил самое интересное, ведь этот неотъемлемый компонент любого веб-приложения вызывает больше проблем при росте нагрузок, чем все остальные вместе взятые. Порой даже может показаться, что стоит вообще отказаться от горизонтального масштабирования системы хранения данных в пользу вертикального — просто купить тот самый БОЛЬШОЙ сервер за шести- или семизначную сумму не-рублей и не забивать себе голову лишними проблемами.
Но для многих проектов такое кардинальное решение (и то, по большому счету, временное) не подходит, а значит перед ними осталась лишь одна дорога — горизонтальное масштабирование. О ней и поговорим.
Путь практически любого веб проекта с точки зрения баз данных начинался с одного простого сервера, на котором работал весь проект целиком. Затем в один прекрасный момент наступает необходимость вынести СУБД на отдельный сервер, но и он со временем начинает не справляться с нагрузкой. Подробно останавливаться на этих двух этапах смысла особого нет — все относительно тривиально.
Следующим шагом обычно бывает master-slave с асинхронной репликацией данных, как работает эта схема уже неоднократно упоминалось в блоге, но, пожалуй, повторюсь: при таком подходе все операции записи выполняются лишь на одном сервере (master), а остальные сервера (slave) получают данные напрямую от «мастера», обрабатывая при этом лишь запросы на чтение данных. Как известно, операции чтения и записи любого веб-проекта всегда растут пропорционально росту нагрузки, при этом сохраняется почти фиксированным соотношение между обоими типами запросов: на каждый запрос на обновление данных обычно приходится в среднем около десятка запросов на чтение. Со временем нагрузка растет, а значит растет и количество операций записи в единицу времени, а сервер-то обрабатывает их всего один, а затем он же еще и обеспечивает создание некоторого количества копий на других серверах. Рано или поздно издержки операций репликации данных станут быть настолько высоки, что этот процесс станет занимать очень большую часть процессорного времени каждого сервера, а каждый slave сможет обрабатывать лишь сравнительно небольшое количество операций чтения, и, как следствие, каждый дополнительный slave-сервер начнет увеличивать суммарную производительность лишь незначительно, тоже занимаясь по большей части лишь поддержанием своих данных в соответствии с «мастером».
Временным решением этой проблемы, возможно, может стать замена master-сервера на более производительный, но так или иначе не выйдет бесконечно откладывать переход на следующий «уровень» развития системы хранения данных: «sharding», которому я совсем недавно посвятил отдельный пост «Сегментирование баз данных». Так что позволю себе остановиться на нем лишь вкратце: идея заключается в том, чтобы разделить все данные на части по какому-либо признаку и хранить каждую часть на отдельном сервере или кластере, такую часть данных в совокупности с системой хранения данных, в которой она находится, и называют сегментом или shard’ом. Такой подход позволяет избежать издержек, связанных с реплицированием данных (или сократить их во много раз), а значит и существенно увеличить общую производительность системы хранения данных. Но, к сожалению, переход к этой схеме организации данных требует массу издержек другого рода. Так как готового решения для ее реализации не существует, приходится модифицировать логику приложения или добавлять дополнительную «прослойку» между приложением и СУБД, причем все это чаще всего реализуется силами разработчиков проекта. Готовые продукты способны лишь облегчить их работу, предоставив некий каркас для построения основной архитектуры системы хранения данных и ее взаимодействия с остальными компонентами приложения.
На этом этапе цепочка обычно заканчивается, так как сегментированные базы данных могут горизонтально масштабироваться для того, чтобы в полной мере удовлетворить потребности даже самых высоконагруженных интернет-ресурсов. К месту было бы сказать пару слов и о собственно самой структуре данных в рамках баз данных и организации доступа к ним, но какие-либо решения сильно зависят от конкретного приложения и реализации, так что позволю себе лишь дать пару общих рекомендаций:
Денормализация Запросы, комбинирующие данные из нескольких таблиц, обычно при прочих равных требуют большего процессорного времени для выполнения, чем запрос, затрагивающий лишь одну таблицу. А производительность, как уже упоминалось в начале повествования, чрезвычайно важна на просторах Сети. Логическое разбиение данных Если какая-то часть данных всегда используется отдельно от основной массы, то иногда имеет смысл выделить ее в отдельную независимую систему хранения данных. Низкоуровневая оптимизация запросов Ведя и анализируя логи запросов, можно определить наиболее медленные из них. Замена найденных запросов на более эффективные с той же функциональностью может помочь более рационально использовать вычислительные мощности.
В этом разделе стоит упомянуть еще один, более специфический, тип интернет-проектов. Такие проекты оперируют данными, не имеющими четко формализованную структуру, в таких ситуациях использование реляционных СУБД в качестве хранилища данных, мягко говоря, нецелесообразно. В этих случаях обычно используют менее строгие базы данных, с более примитивной функциональностью в плане обработки данных, но зато они способны обрабатывать огромные объемы информации не придираясь к его качеству и соответствию формату. В качестве основы для такого хранилища данных может служить кластерная файловая система, а для анализа же данных в таком случае используется механизм под названием MapReduce, принцип его работы я расскажу лишь вкратце, так как в полном своем масштабе он несколько выходит за рамки данного повествования.
Итак, мы имеем на входе некие произвольные данные в не факт что правильно соблюденном формате. В результате нужно получить некое итоговое значение или информацию. Согласно данному механизму практически любой анализ данных можно провести в следующие два этапа:
Map Основной целью данного этапа является представление произвольных входных данных в виде промежуточных пар ключ-значение, имеющих определенный смысл и формально оформленных. Результаты подвергаются сортировке и группированию по ключу, а после чего передаются на следующий этап. Reduce Полученные после map значения используются для финального вычисления требуемых итоговых данных.
Каждый этап каждого конкретного вычисления реализуется в виде независимого мини-приложения. Такой подход позволяет практически неограниченно распараллеливать вычисления на огромном количестве машин, что позволяет в мгновения обрабатывать объемы практически произвольных данных. Для этого достаточно лишь запустить эти приложения на каждом доступном сервере одновременно, а затем собрать воедино все результаты.
Примером готового каркаса для реализации работы с данными по такому принципу служит opensource проект Apache Foundation под названием Hadoop, о котором я уже неоднократно рассказывал ранее, да и статейку в Википедию написал в свое время.
Вместо заключения
Если честно, мне с трудом верится, что я смог написать настолько всеобъемлющий пост и сил на подведение итогов уже практически не осталось. Хочется лишь сказать, что в разработке крупных проектов важна каждая деталь, а неучтенная мелочь может стать причиной провала. Именно по-этому в этом деле учиться стоит не на своих ошибках, а на чужих.
Хоть может быть этот текст и выглядит как некое обобщение всех постов из серии «Архитектуры высоконагруженных систем», но врядли он станет финальной точкой, надеюсь мне найдется что сказать по этой теме и в будущем, может быть однажды это будет основано и на личном опыте, а не просто будет результатом переработки массы полученной мной информации. Кто знает.
Масштабируемая Веб-архитектура и распределенные системы
1.3. Структурные компоненты быстрого и масштабируемого доступа к данным
Рассмотрев некоторые базовые принципы в разработке распределенных систем, давайте теперь перейдем к более сложному моменту — масштабирование доступа к данным.
Самые простые веб-приложения, например, приложения стека LAMP, схожи с изображением на рисунке 1.5.
Рисунок 1.5: Простые веб-приложения
С ростом приложения возникают две основных сложности: масштабирование доступа к серверу приложений и к базе данных. В хорошо масштабируемом дизайне приложений веб-сервер или сервер приложений обычно минимизируется и часто воплощает архитектуру, не предусматривающую совместного разделения ресурсов. Это делает уровень сервера приложений системы горизонтально масштабируемым. В результате использовании такого дизайна тяжёлый труд сместится вниз по стеку к серверу базы данных и вспомогательным службам; именно на этом слое и вступают в игру настоящие проблемы масштабирования и производительности.
Остальная часть этой главы посвящена некоторым наиболее распространенным стратегиям и методам повышения производительности и обеспечения масштабируемости подобных типов служб путем предоставления быстрого доступа к данным.
Рисунок 1.6: Упрощенное веб-приложение
Большинство систем может быть упрощено до схемы на рисунке 1.6,
которая является хорошей отправной точкой для начала рассмотрения. Если у Вас есть много данных, можно предположить, что Вы хотите иметь к ним такой же легкий доступ и быстрый доступ, как к коробке с леденцами в верхнем ящике вашего стола. Хотя данное сравнение чрезмерно упрощено, оно указывает на две сложные проблемы: масштабируемость хранилища данных и быстрый доступ к данным.
Для рассмотрения данного раздела давайте предположим, что у Вас есть много терабайт (ТБ) данных, и Вы позволяете пользователям получать доступ к небольшим частям этих данных в произвольном порядке. (См. рисунок 1.7.)
Схожей задачей является определение местоположения файла изображения где-нибудь на файловом сервере в примере приложения хостинга изображений.
Рисунок 1.7: Доступ к определенным данным
Это особенно трудно, потому что загрузка терабайтов данных в память может быть очень накладной и непосредственно влияет на количество дисковых операций ввода-вывода. Скорость чтения с диска в несколько раз ниже скорости чтения из оперативной памяти — можно сказать, что доступ к памяти с так же быстр, как Чак Норрис, тогда как доступ к диску медленнее очереди в поликлинике. Эта разность в скорости особенно ощутима для больших наборов данных; в сухих цифрах доступ к памяти 6 раз быстрее, чем чтение с диска для последовательных операций чтения, и в 100,000 раз — для чтений в случайном порядке (см. «Патологии Больших Данных», http://queue.acm.org/detail.cfm? >
К счастью существует много подходов, которые можно применить для упрощения, из них четыре наиболее важных подхода — это использование кэшей, прокси, индексов и балансировщиков нагрузки. В оставшейся части этого раздела обсуждается то, как каждое из этих понятий может быть использовано для того, чтобы сделать доступ к данным намного быстрее.
Кэширование дает выгоду за счет характерной черты базового принципа: недавно запрошенные данные вполне вероятно потребуются еще раз. Кэши используются почти на каждом уровне вычислений: аппаратные средства, операционные системы, веб-браузеры, веб-приложения и не только. Кэш походит на кратковременную память: ограниченный по объему, но более быстрый, чем исходный источник данных, и содержащий элементы, к которым недавно получали доступ. Кэши могут существовать на всех уровнях в архитектуре, но часто находятся на самом близком уровне к фронтэнду, где они реализованы, чтобы возвратить данные быстро без значительной нагрузки бэкэнда.
Каким же образом кэш может использоваться для ускорения доступа к данным в рамках нашего примера API? В этом случае существует несколько мест, подходящих размещения кэша. В качестве одного из возможных вариантов размещения можно выбрать узлы на уровне запроса, как показано на
рисунке 1.8.
Рисунок 1.8: Размещение кэша на узле уровня запроса
Размещение кэша непосредственно на узле уровня запроса позволяет локальное хранение данных ответа. Каждый раз, когда будет выполняться запрос к службе, узел быстро возвратит локальные, кэшированные данные, если таковые существуют. Если это не будет в кэше, то узел запроса запросит данные от диска. Кэш на одном узле уровня запроса мог также быть расположен как в памяти (которая очень быстра), так и на локальном диске узла (быстрее, чем попытка обращения к сетевому хранилищу).
Рисунок 1.9: Системы кэшей
Что происходит, когда вы распространяете кеширование на множество узлов? Как Вы видите в рисунке 1.9, если уровень запроса будет включать множество узлов, то вполне вероятно, что каждый узел будет и свой собственный кэш. Однако, если ваш балансировщик нагрузки в произвольном порядке распределит запросы между узлами, то тот же запрос перейдет к различным узлам, таким образом увеличивая неудачные обращения в кэш. Двумя способами преодоления этого препятствия являются глобальные и распределенные кэши.
Глобальный кэш
Смысл глобального кэша понятен из названия: все узлы используют одно единственное пространство кэша. В этом случае добавляется сервер или хранилище файлов некоторого вида, которые быстрее, чем Ваше исходное хранилище и, которые будут доступны для всех узлов уровня запроса. Каждый из узлов запроса запрашивает кэш таким же образом, как если бы он был локальным. Этот вид кэширующей схемы может вызвать некоторые затруднения, так как единственный кэш очень легко перегрузить, если число клиентов и запросов будет увеличиваться. В тоже время такая схема очень эффективна при определенной архитектуре (особенно связанной со специализированными аппаратными средствами, которые делают этот глобальный кэш очень быстрым, или у которых есть фиксированный набор данных, который должен кэшироваться).
Есть две стандартных формы глобальных кэшей, изображенных в схемах. На рисунке 1.10 изображена ситуация, когда кэшируемый ответ не найден в кэше, сам кэш становится ответственным за получение недостающей части данных от базового хранилища. На рисунке 1.11 проиллюстрирована обязанность узлов запроса получить любые данные, которые не найдены в кэше.
Рисунок 1.10: Глобальный кэш, где кэш ответственен за извлечение
Рисунок 1.11: Глобальный кэш, где узлы запроса ответственны за извлечение
Большинство приложений, усиливающих глобальные кэши, склонно использовать первый тип, где сам кэш управляет замещением и данными выборки, чтобы предотвратить лавинную рассылку запросов на те же данные от клиентов. Однако, есть некоторые случаи, где вторая реализация имеет больше смысла. Например, если кэш используется для очень больших файлов, низкий процент удачного обращения в кэш приведет к перегрузке кэша буфера неудачными обращениями в кэш; в этой ситуации это помогает иметь большой процент общего набора данных (или горячего набора данных) в кэше. Другой пример — архитектура, где файлы, хранящиеся в кэше, статичны и не должны быть удалены. (Это может произойти из-за основных эксплуатационных характеристик касательно такой задержки данных — возможно, определенные части данных должны оказаться очень быстрыми для больших наборов данных — когда логика приложения понимает стратегию замещения или горячие точки лучше, чем кэш.)
Распределенный кэш
В распределенном кэше (рисунок 1.12), каждый из его узлов владеет частью кэшированных данных, поэтому если холодильник в продуктовом магазине сравнить с кэшем, тогда распределенный кэш походит на хранение вашей еды в нескольких удобных для доступа местах — холодильнике, стойках и коробке для завтрака, что избавляет необходимости совершать путешествия на склад. Обычно кэш сегментирован при помощи непротиворечивой хеш-функции. Если узел запроса ищет определенную часть данных, он может быстро узнать, куда смотреть в распределенном кэше, чтобы определить, доступны ли эти данные. В этом случае каждый узел поддерживает маленькую часть кэша и сначала отправляет запрос данных другому узлу прежде, чем обращается к источнику. Поэтому, одно из преимуществ распределенного кэша — расширяемое пространство кэша, что достигается простым добавлением узлов к пулу обработки запросов.
Недостаток распределенного кэширования — работа в условиях недостающих узлов. Некоторые распределенные кэши обходят эту проблему, храня избыточные копии данных на множестве узлов; однако, можно представить, насколько быстро логическая структура такого кэша может сложной, особенно в условиях добавления или удаления узлов из уровня запроса. Стоит отметить — даже если узел исчезает, и часть кэша будет потеряна, последствия необязательно окажутся катастрофическими — запросы просто получат данные непосредственно от источника!
Рисунок 1.12: Распределенный кэш.
Большим преимуществом кэшей является увеличение скорости работы системы (безусловно, только при правильной реализации!) Выбранная методология позволяет ускорить этот процесс для еще большего количества запросов. Однако, использование кэширования предполагает определенные затраты на поддержание дополнительного пространства обычно дорогостоящей памяти. Кэши замечательно подходят не только для общего увеличения производительности системы, но и обеспечения ее функциональность при нагрузке такого высокого уровня, которая в обычной ситуации привела бы к полному отказу в обслуживании.
Одним из популярных примеров кэша с открытым исходным кодом можно назвать Memcached (), который может работать как локальным, так и распределенным кэшем); кроме того, есть много других вариантов (включая специфичные для определенного языка или платформы).
Memcached используется во многих больших веб-сайтах, и даже при том, что он может быть очень мощным, представляет собой просто хранилище типа ключ-значение в оперативной памяти, оптимизированного для произвольного хранения данных и быстрых поисков (O(1)).
Facebook использует несколько различных типов кэширования, чтобы добиться высокой производительности своего сайта (см., «Facebook: кэширование и производительность» ). Они используют $GLOBALS и APC, кэширующие на уровне языка (представленные в PHP за счет вызова функции), который способствует ускорению промежуточных вызовов функции и получению результатов. (Большинство языков оснащены этими типами библиотек для улучшения производительность веб-страницы, и они почти всегда должны использоваться.) Кроме того Facebook использует глобальный кэш, который распределен на множество серверов (см. » Масштабирование memcached в Facebook» ), таким образом, что один вызов функции, получающий доступ к кэшу, мог параллельно выполнить множество запросов для данных, хранящихся на различных серверах Memcached. Такой подход позволяет добиться намного более высокой производительности и пропускной способности для данных профиля пользователя, и создать централизованную архитектуру обновления данных. Это важно, так как, при наличии тысяч серверов, функции аннулирования и поддержания непротиворечивости кэша могут вызывать затруднение.
Далее речь пойдет об алгоритме действий в случае отсутствия данных в кэше.
Asp разработка масштабируемых компонентов
Как Создать Высоконагруженный Сайт и его Архитектуру
Если вы решили создать крупный проект, перед началом проектирования подумайте о масштабировании. Это позволит избежать многих проблем. Английская.
Full stack web developer
Содержание
Если вы решили создать крупный проект, перед началом проектирования подумайте о масштабировании. Это позволит избежать многих проблем. Английская версия статьи how build scalable web applications. Если вы планируете разработать крупный проект, тогда вам обязательно нужно подумать о правильном масштабировании. Этот вопрос лучше решать перед началом проектирования, так как изменения в уже готовом проекте потребует намного больше усилий.
Масштабирование веб приложения – это способность проекта увеличивать свои возможности за счёт повышения количества функциональных модулей, которые выполняют одни и те же задачи. Как правило, о масштабировании начинают думать когда текущая система не выдерживает существующие нагрузки. Преимущественно это крупные проекты такие как социальные сети типа Вконтакте или криптовалютные биржи, которые имеют высокие нагрузки. Самое простое решение – повысить мощность сервера. Для этого достаточно заказать обновление железа у провайдера.
Это позволит кратковременно решить проблему, но в глобальном смысле, этого недостаточно. Реконструкция существующего сайта требует много ресурсов и усилий, потому, компания Мерехеад, рекомендует перед началом разработки обдумать варианты масштабироваться. Это позволит построить стабильную и гибкое веб приложение. Каждый сайт имеет стандартную архитектура взаимодействия, но многие компании предпочитают разделять проект на несколько основных модулей. В основном это: front-end, back-end и database.
Вы хотите разработать проект?
Свяжитесь с нами и мы Вам поможем.
Это дает огромное преимущество в дальнейшей разработке. Благодаря такой архитектуре, упрощается навигация между файлами и система становится намного гибче для будущего масштабирования. Кроме того желательно использовать лучшие языки программирования в 2020 году, которые обеспечивают необходимую производительность. Например, наша компания отдает предпочтение Apache серверу, который обрабатывает каждый http запрос как отдельный процесс. На это требуются вычислительные ресурсы и время. При большой нагрузке, в основном, не выдерживает база данных. Она требует больше ресурсов. Чтобы обеспечить стабильную работу, можно вынести базу данных на отдельный сервер. Это существенно повысит производительность текущего веб приложения.
На втором этапе можно сделать масштабирование back-end части. Это также позволит существенно повысить скорость работы. Как правило front-end, остается на том же сервере, так как он использует минимальную нагрузку и его разделение не приведет к столь существенному повышению производительности. Кроме того можно вынести на отдельный сервер хранилище файлов, это также позволить снизить нагрузку на основной сервер.
Масштабирование back-end
Как правило крупные проекты (такие как крупные интернет-магазины) имеют сильную back-end часть. На нее возложена обработка сложных вычислений. Очень часто back-end является причиной медленной работы веб приложения. В процессе разработки сложных проектов, следует уделить внимание оптимизации запросов и рациональном использовании производительности сервера. Если Ваш проект имеет хорошо разработанную back-end часть, но нагрузка продолжает расти и это приводит к нестабильной работе, мы рекомендуем сделать масштабировать.
Если нагрузка временная, можно увеличить мощность сервера. В ином случае следует разделить back-end на несколько серверов. Достаточно перенести часть скриптов и модулей на второй сервер. После этого, необходимо распределить запросы равномерно между ними. Это даст возможность снять нагрузку с основного сервера и перенаправить на другие машины. В основном это делается на front-end стороне.
Масштабирование database
Если база данных имеет огромное количество данных и нагружает отдельный сервер, можно масштабировать данный кластер. Существует два подхода масштабирования базы данных:
-Распределение вычислительных процессов.
-Разделение базы данных.
Распределение вычислительных процессов в базе данных
Если Вы имеете не большую базу данных, но много сложных вычислений, которые нагружают сервер, рекомендуем разделить именно процесс вычислений. Таким образом, каждый сервер будет иметь полную копию базы данных и выполнять только свой ограниченный пул работы. Это более сложная задача, которая требует больше усилий для реализации. В первую очередь это связано с синхронизацией данных между серверами, чтобы держать каждую копию базы данных актуальной. Существует несколько походов для решение этой проблемы:
— Синхронизация на уровне приложений. В этом случае скрипты автоматически записывают изменения в каждую копию базы данных. Это подход имеет много рисков, так как любой сбой может нарушить синхронизацию данных.
— Репликация. Позволяет делать изменения на одном «главном» сервере, после этого происходит автоматическое изменения на остальных серверах.
— Multi-master репликация. Очень похож на репликацию, только в этом случае скрипт может обращаться к любому серверу. Репликация будет распространена на остальные сервера.
Разделение базы данных
Если у Вас не много вычислений, но база данных состоит из множество записей (часто такие проблемы имеют крупные e-commerce сайты), в этом случае Вам нужно разделить базу данных на несколько серверов. Каждый сервер будет иметь часть данных и взаимодействовать с другими серверами. Существует несколько схем распределения данных:
—Вертикальное (vertical partitioning). Суть данной схемы заключается в том что некоторые таблицы перемещаются на другой сервер. В таком случае теряется гибкость, так как нет возможности сделать сложные SQL запросы. Если провести правильное и логическое разделение базы, этот недостаток можно свести к минимуму.
—Горизонтальное (horizontal partitioning). В данной схеме база данных хранить одну и туже таблицу, только с разными частями. Например, у Вас есть таблица с 100 миллионами записями пользователей. Мы ее раздели на 2 сервера, на одном сервере будут хранится записи только мужчины, а на втором – только женщины. В этом случае необходимо внести изменения в back-end чтобы правильно распределить запросы между серверами.
Распределение запросов между серверами
Как только ваша система будет имеет несколько серверов, перед Вами появится вопрос правильного распределение запросов между серверами. Существует два метода распределения запросов (балансировки):
—Балансирующий узел. Это простой методы распределение нагрузки между запросами. Клиент взаимодействует с основным сервером, который самостоятельно определяет к какому серверу обратится для обработки запроса. Основная проблема состоит в надежности данного метода, так как в одной точке находится вся цепная логика. Если этот узел выйдет из стоя, это остановит работу всей системы.
—Балансировка на стороне клиента. Более безопасный и более гибкий метод распределения запросов между серверами. Идея заключается в получении параметров пользователя. После этого, на основе этих данных, происходит автоматическое распределение запросов. Очень часто для этого используются данные IP и страны пользователя. Зная это, происходит автоматическое взаимодействие пользователя с нужными серверами. Таким подходом пользуются крупные компании, например Facebook.
Вывод
Крупные проекты с большой нагрузкой требует определенных знаний и опыта. Масштабирование проекта это сложный процесс, который нужно учитывать перед началом проектирования. Это позволит сэкономить деньги и время в будущем. В целом существует много схем и методов масштабирования: front-end, back-end, database. Они дают возможность разработать структуру, которая выдержит сотни миллионов пользователей в месяц.
Чтобы получить качественный результат, необходимо разработать правильную архитектуру и использовать надежные сервера. Для масштабируемых проектов, мы рекомендуем использовать Amazon Elastic Compute Cloud (Amazon EC2). Наша компания, Merehead, имеет опыт разработки сложных масштабируемых проектов с высокой нагрузкой. Если у Вас возникли вопросы, Вы можете связаться с нами и мы будем рады Вам помочь.
Библиотека Интернет Индустрии I2R.ru
Малобюджетные сайты.
Продвижение веб-сайта.
Контент и авторское право.
Компоненты ASP. Службы COM+ в ASP-компонентах и ASP-приложениях
Создавать и успешно использовать ASP-компоненты можно и без прямого обращения к каким-либо службам COM+. Однако именно эти службы могут внести ту «изюминку», которая превратит просто удовлетворительно работающее ASP-приложение в хорошо масштабируемое и соответствующее предъявляемым ему требованиям, независимо от того, каковы эти требования.
Службы COM+, к которым, прежде всего, стоит обратиться в разработке ASP-компонентов, — это управление транзакциями (transaction management), активация по требованию (just-in-time activation, JIT) и резервирование (pooling). Кроме того, можно использовать новые интерфейсы, которые были созданы для поддержания большей части возможностей этих служб. Однако перед рассмотрением всех этих новшеств обратимся к применению компонентов в приложениях.
Разработка систем на основе компонентов
Именно система, построенная на основе компонентов, выделяет индивидуальные процессы в многократно используемые порции кода и данных, а затем использует один или несколько этих компонентов для сознания полноценных приложений. Среди нескольких типов приложений различают клиент-серверную (client/server), распределенную (distributed) и многоярусную (n-tier) системы. В клиент-серверной системе (client-server system) процессы распределяются между клиентом и сервером, причем клиент отвечает за все взаимодействия с пользователем, отображение данных и подтверждение полномочий клиента, а сервер обрабатывает большую часть обращений к базам данных, проверяет полномочия сервера и контролирует бизнес-правила. В распределенной системе (distributed system) компоненты приложений могут располагаться на разных машинах и географически находиться в разных местах. Кроме того, для обработки множественных запросов может быть создано несколько экземпляров одного и того же компонента, таким образом, появляется возможность предоставлять одну и ту же услугу сразу множеству клиентов. И наконец, многослойная система (n-tier system) объединяет в себе элементы как клиент-серверной, так и распределенной систем. Здесь, как и в клиент-серверной системе, присутствует иерархия компонентов, однако сами по себе компоненты могут дублироваться для распределения загрузки процессов, а также распределяться на большом количестве машин и мест, как это происходит в распределенной системе. Стандартная n-ярусная система может состоять из клиента, выполняющего все взаимодействия с пользователем, проверку полномочий клиента и отображение данных; бизнес-уровня, контролирующего бизнес-правила, общие вопросы управления транзакциями и проверку полномочий, и уровня данных, обрабатывающего все прямые обращения к данным.
Достоинства применения компонентов в распределенных системах, прежде всего, состоят в том, что компоненты получаются небольшими, компактными и переносимыми (поскольку хост предоставляет компоненту потребную для него среду). Если запрос, поступивший на север, начинает снижать общую производительность машины, компонент или группа компонентов с легкостью переносятся на другой сервер без какого-либо изменения кода компонента. Вдобавок, поскольку компоненты разбиты на модули, один или несколько модулей могут перемещаться на другие серверы, до достижения баланса нагрузки на процессоры всех серверов. Приложения, создаваемые без разбиения на модули, не могут быть разделены на части, а, тем более, не могут быть разнесены по разным машинам.
Другим преимуществом компонентов является тот факт, что гораздо больше родовых функций (generic functions) может быть разделено на отдельные компоненты и, таким образом, использовано во всей системе. Кроме того, применение компонентов упрощает модель и конструкцию n-ярусной системы. В качестве примера можно привести систему, в которой интерфейсный компонент проверяет достоверность адресной информации. Проверка достоверности достаточно характерна: она просто подтверждает тот факт, что все необходимые поля, такие как «Город» или «Почтовый индекс», заполнены. Тогда бизнес-уровень сможет обрабатывать для приложения адресную информацию, исходя из типа бизнеса. На основе адресной информации бизнес-уровень может вести поиск, например, зон отгрузки для компонента с помощью соответствующего приложения или зон доставки для виртуальной системы заказов. Компонент может обратиться к уровню данных для сохранения адресной информации, извлечения дополнительной информации или даже для запуска других виртуальных бизнес-компонентов, выполняющих какую-либо еще обработку информации. Уровень данных сам по себе может быть разделен по отдельным машинам, отдельным базам данных или даже различным типам хранилищ данных, вместе с некоторой информацией, идущей на долговременное или кратковременное хранение (если информация может потребоваться лишь в некоторой специфической задаче, а затем должна быть удалена).
Вне зависимости от того, как компоненты выполняют поставленную задачу, основная идея состоит в том, чтобы отделить как можно больше родовых функций (таких как обращение к адресной информации и проверка ее достоверности), необходимых для многих приложений, от большинства специфичных для бизнеса функций, таких как поиск виртуальных магазинов для приложений, позволяющих делать покупки в Интернете. Кроме того, n-ярусное приложение следит за отделением компонентов пользовательского интерфейса, в обязанности которых входит обеспечение успешного получения нужной информации, от бизнес-уровня, сравнивающего полученную информацию с уже имеющейся для обеспечения бизнес-функций. Бизнес-уровень отделен от уровня данных, заботящегося о наличии достаточного количества информации для успешного совершения транзакций данных и совершенно не думающего том, каким образом была получена информация, или о хотя бы о ее назначении.
ASP-компоненты принимают активное участие в этом типе систем, обеспечивая работу бизнес-уровня или уровня данных. Интерфейс пользователя при этом поддерживается браузером, обращающимся к ASP-приложению.
В Windows 2000 работа с компонентами существенно облегчается службами COM+, выполняющими для компонентов такие действия, как например резервирование и транзакции. Именно для реализации этих задач традиционные интерфейсы COM были дополнены совершенно новыми интерфейсами, о которых вы узнаете в следующем разделе.
Интерфейсы COM+
Большинство важнейших интерфейсов COM, таких как, например, IUnknown и IDispatch, с появлением среды COM+ не претерпело каких-либо изменений и по сию пору решает все те же знакомые нам задачи. Главное отличие между COM и COM+ состоит в том, что та функциональность, которая в Windows NT и Windows 98 выполнялась средствами MTS, нынче полностью интегрирована в архитектуру COM+. Такого рода интеграция не только принесла с собой новые возможности, предоставляемые службами COM+, но и позволила существенно улучшить характеристики компонентов.
Разумеется, разработчики COM+ не забыли о необходимости поддержки новых служб COM+, а также нескольких новых интерфейсов уже имеющимися средствами, реализованными в MTS. В следующих нескольких разделах нами будут рассмотрены несколько ключевых интерфейсов, без знания которых, как нам кажется, разработка ASP-компонентов была бы бессмысленна.
Интерфейс IObjectContext
В главе 4 мы обсуждали концепцию контекстов (contexts) как совокупностей объектов, отвечающих одинаковыми требованиями. Среди таких требований может быть, например, возможность резервирования (pooling) для компонентов или их совместное участие в транзакциях.
В Windows 2000 контекст компонентов представляет собой набор свойств времени выполнения (runtime properties), непосредственная работа с которым может вестись с помощью объекта ObjectContext, управляющего контекстной информацией для компонента. Для доступа к свойствам объекта ObjectContext используется интерфейс IObjectContext, доступный, в свою очередь, с помощью библиотеки типов служб COM+.
Ответ на вопрос о том, каким именно образом осуществляется доступ к объекту ObjectContext, зависит от того, каким языком программирования вы пользуетесь. Например, если взять Visual Basic, то здесь сначала производится импорт ссылки на библиотеку типов служб COM+ в рабочий проект, и только потом можно создать ссылку на объект ObjectContext и вызвать для создания экземпляра метод GetObjectContext:
Dim objContext As ObjectContext
Set objContext = GetObjectContext
В Visual С++ под Windows NT для доступа к объекту ObjectContext также можно применить метод GetObjectContext:
CComPtr m_spObjectContext;
hr = GetObjectContext(&m_spObjectContext);
В то же время, в Visual C++ под Windows 2000 для передачи интерфейсу идентификатора GUID уже используется метод CoGetObjectContext:
hr = CoGetObjectContext(IID_IObjectContextInfo,
(void **)&m_spObjectContext);
Независимо от того, применяется ли метод GetObjectContext или метод CoGetObjectContext, результаты будут одними и теми же, поскольку вызов метода CoGetObjectContext в COM+ является оболочкой для вызова метода GetObjectContext. В Visual С++ придется также добавить в путь библиотеки компонента ссылку на заголовочный файл comsvcs.h служб COM+ и ссылку на файл связанного объекта comsvcs.lib.
Получив ссылку на интерфейс IObjectContext, можно вызывать его методы, перечисленные в табл. 1.
табл. 1. Методы интерфейса IObjectContext
Метод | Описание |
CreateInstance | Создает экземпляр объекта |
DisableCommit | Показывает, что компонент не готов к выполнению транзакции |
EnableCommit | Показывает, что компонент все еще обрабатывается, но транзакция может быть выполнена |
IsCallerInRole | Показывает, соответствует ли вызывающий определенной роли (с учетом ролевой безопасности) |
IsInTransaction | Показывает, участвует ли компонент в транзакции |
IsSecurityEnabled | Показывает, включена ли защита |
SetAbort | Показывает, что компонент завершил работу и транзакция прервана |
SetComplete | Показывает, что компонент завершил работу и транзакция готова к принятию |
Интерфейс обладает следующими свойствами:
- ContextInfo — возвращает ссылку на объект контекстной информации, связанный с компонентом;
- Count — показывает число именованных свойств объекта;
- Item — содержит имена свойств;
- Security — возвращает ссылку на объект Security, связанный с объектом ObjectContext.
Интерфейс информации контекста IObjectContextInfo более подробно будет рассмотрен в следующем разделе. Для обращения к встроенным ASP-объектам применяется коллекция Item. Получить доступ к ней можно непосредственно из Visual Basic:
Dim oc As ObjectContext
Dim app As Application
Set oc = GetObjectConext
Set app = oc.Item(«Application»)
Получить доступ к ASP-объектам можно и через объект ObjectContext:
Set app = oc(«Application»)
Из других программных языков для обращения к объектам ASP придется применить другие технологии. В C++, например, для получения доступа к определенному ASP-объекту нужно послать запрос экземпляру интерфейса IGetContextProperties:
CComPtr pProps; //свойства контекста
//получение ObjectContext
hr = CoGetObjectContext(IID_IObjectContext,
(void **)&m_spObjectContext);
if (FAILED(hr)) return hr;
//получение свойств контекста
hr = m_spObjectContext ->
QueryInterface( IID_IGetContextProperties,
(void**)&pProps );
if (FAILED(hr)) return hr;
получение свойства Response
bstrProp = «Response»;
hr = pProps->GetProperty( bstrProp, &vt ) ;
if (FAILED(hr)) return hr;
p > hr = piDispatch->QueryInterface( IID_IResponse,
(void**)&m_piResponse );
В документации к интерфейсу IGetContextProperties сообщается, что он действует только в среде Windows NT, но и в Windows 2000 с его помощью также можно обращаться к встроенным ASP-объектам.
Интерфейс IObjectContextInfo
Интерфейс IObjectContextInfo служит для получения относящейся к текущему компоненту информации о транзакциях, активности и контексте. С помощью этого интерфейса можно получить доступ к указателю на интерфейс ITransaction. В табл. 2 приведены методы интерфейса IObjectContextInfo.
табл. 2. Методы интерфейса IObjectContextInfo
Метод | Описание |
GetActivityId | Возвращает идентификатор текущей активности |
GetContextId | Возвращает идентификатор текущего контекста |
GetTransaction | Возвращает указатель на интерфейс ITransaction |
GetTransactionId | Возвращает идентификатор текущей транзакции |
IsInTransaction | Показывает, участвует ли компонент в транзакции |
Если в компонентах COM+ осуществляется синхронизация (описываемая позже), метод GetActivityId возвращает идентификатор текущей активности, в противном случае будет возвращено значение Null.
Метод GetTransaction возвращает указатель на интерфейс ITransaction. С помощью этого интерфейса можно принять или прервать транзакцию, хотя реализуются эти функции с помощью объекта ObjectContext или интерфейса IContextState, рассматриваемых далее.
Интерфейс IContextState
Интерфейс IContextState предоставляет возможность более четкого управления транзакциями и активацией, чем это допустимо в интерфейсе IObjectContext. Например, в интерфейсе IObjectContext с помощью метода SetComplete можно отметить компонент, как завершивший работу и ожидающий принятия транзакции, а с помощью метода SetAbort отметить его как завершивший работу и ожидающий прерывания текущей транзакции.
На практике есть два условных бита, устанавливаемых при работе с методами SetComplete и SetAbort. Первый из их — бит завершения (done bit), его установка указывает COM+, что обработка компонента завершена. Второй — бит согласованности (consistency bit). Установка этого бита определяет, может ли быть транзакция принята или она должна быть прервана.
Оба метода, SetComplete и SetAbort, интерфейса IObjectContext одновременно устанавливают оба бита, причем оба устанавливают бит завершения в значение True, указывая, что обработка компонента завершена. В то же время, с помощью интерфейса IContextState можно идентифицировать факт окончания работы компонента отдельно от статуса транзакции.
В табл. 3 приведены четыре метода интерфейса IContextState.
табл. 3. Методы интерфейса IContextState
Метод | Описание |
GetDeactivateOnReturn | Получает состояние бита завершения |
GetMyTransactionVote | Получает состояние бита согласованности |
SetDeactivateOnReturn | Сигнализирует о завершении работы компонента |
SetMyTransactionVote | Идентифицирует возможность принятия либо прерывания транзакции |
Бит завершения можно получать и устанавливать с помощью методов SetDeactivateOnReturn и GetDeactivateOnReturn. Компонент деактивируется по завершении методов только при значении бита завершения, установленном в True.
Чтобы убедиться во всех этих возможностях, создайте компонент Visual Basic, реализующий для перехвата событий JIT интерфейс IObjectControl. (Интерфейс IObjectControl подробно описывается в следующем разделе.) У этого метода будет два компонента, каждый из которых вызывает метод SetDeactivateOnReturn интерфейса IContextState. Первая функция будет вызывать метод, передавая логическое значение False, вторая — True.
Создайте проект Visual Basic и назовите его asp0501, а класс генерируемого компонента — done. Присоедините к проекту библиотеки типов служб COM+ и Microsoft Active Server Pages. Коль скоро библиотеки типов добавлены как ресурсы, реализуйте, как показано в листинге 5.1, JIT-методы Activate, Deactivate и CanBePooled интерфейса IObjectControl.
Листинг 1. Реализация JIT-функций интерфейса IObjectControl
Dim objResponse As Response
Private Sub ObjectControl_Activate()
Set objResponse = GetObjectContext().Item(«Response»)
objResponse.Write «
Активирован
Private Sub ObjectControl_Deactivate()
objResponse.Write «
Деактивирован
Private Function ObjectControl_CanBePooled() As Boolean
ObjectControl_CanBePooled = False
End Function
В методе Activate созданная ссылка на встроенный ASP-объект Response используется для отображения сообщения о том, что метод активирован. В методе Deactivate на web-страницу выводится сообщение о том, что компонент деактивирован.
Далее, добавьте две функции компонента, вызываемые ASP-страницей. Первая, под именем function1, обращается к интерфейсу IContextState и вызывает его метод SetDeactivateOnReturn, передавая значение False. Вторая функция, function2, также вызывает метод SetDeactivateOnReturn, но на сей раз передается значение True. В листинге 5.2 приведен программный код обеих функций, который и нужно добавить к нашему компоненту.
Листинг 2. Подпрограммы, вызывающие метод SetDeactivateOnReturn
Dim iCntxt As IContextState
Set iCntxt = GetObjectContext
iCntxt.SetDeactivateOnReturn False
End Sub
Dim iCntxt As IContextState
Set iCntxt = GetObjectContext
iCntxt.SetDeactivateOnReturn True
End Sub
После того как проект компонента скомпилирован и добавлен к приложению COM+, протестируйте новый компонент с помощью ASP-сценария примерно такого содержания (как в странице asp0501.asp):
Первой вызывается функция function2, деактивирующая компонент при возвращении значения функцией. Благодаря этому при обращении к функции компонента будет отображаться сообщение «Активирован», а при возвращении функцией значения будет отображаться сообщение «Деактивирован» еще до сообщения «Вызов function 1».
Следует заметить, что при вызове первой функции, не деактивирующей компонент, сообщение «Деактивирован» при возвращении функцией значения не появится.
Наконец, при повторном вызове функции function2, должны быть отображены оба сообщения, «Активирован» и «Деактивирован», при этом генерируется web-страница со следующими сообщениями:
Вызов function 2
Вызов function 1
Вызов function 2
Как можно видеть, в интерфейсе IContextState управлять активацией компонента можно, не оказывая влияния на транзакцию компонента, независимо от того, участвует компонент в транзакции или нет.
Интерфейс IObjectControl
В этом разделе показано, как можно управлять временем жизни (lifetime) компонента с помощью интерфейса IContextState. В примере также используется активация по требованию (just-in-time activation, JIT) — для управления созданием экземпляра компонента и записи на web-страницу с помощью методов Activate и Deactivate интерфейса IObjectControl сообщений об активации и деактивации компонента.
Разрешение активации по требованию компонента означает, что компонент активируется, только когда он реально требуется, а не при первоначальном создании на ASP-странице. Кроме того, компонент деактивируется, только когда он помечается как готовый к деактивации, удаляется внутри ASP-страницы (например, установлен в Nothing, если вы работаете с VBScript) или процесс выходит за пределы страницы.
Процесс активации и деактивации управляется COM+ с помощью программных ключей, указываемых разработчиками, как, например, тот, что показан в этом разделе для вызова метода SetDeactivateOnReturn интерфейса IContextState.
Как разработчик компонента, вы можете перехватывать моменты активации и деактивации компонента путем реализации методов Activate и Deactivate интерфейса IObjectControl. В этом случае вы не зависите от ресурсов (типа ссылки на объект Response в листинге 5.1) в период простоя компонента и ожидания вызова его методов.
При реализации интерфейса IObjectControl вы должны реализовать и методы Activate и Deactivate — а кроме того, еще и метод CanBePooled. Этот последний метод определяет, находится ли компонент в состоянии, когда он может быть резервирован (pooled). Позднее в этой главе при описании служб COM+ мы рассмотрим резервирование (pooling) компонентов более подробно.
Чтобы воспользоваться преимуществами активации по требованию, компонент должен быть установлен в приложение COM+, а активация по требованию разрешена. Впрочем, поддержка активации по требованию в приложении COM+ для всех компонентов включена по умолчанию.
Интерфейсы IObjectControl и IObjectContext были реализованы в Windows NT и управляются посредством MTS. Фактически же, ASP-компоненты, созданные для Windows NT, за исключением использующих специфические службы NT, можно без проблем перенести на Windows 2000 и COM+, что и обсуждается в следующем разделе.
ПРИМЕЧАНИЕ
В Windows NT приходилось избегать применения обработчиков событий Initialize и Terminate, поскольку они не позволяли обратиться к экземпляру объекта ObjectContext. Это ограничение в Windows 2000 снято. И все же, по-прежнему важно рассмотреть реализацию интерфейса IObjectControl и перехват (trapping) событий Activate и Deactivate для обеспечения доступа к глобальным ресурсам и их высвобождения.
Перенос пакетов MTS в приложения COM+
В Windows NT (or Windows 9x) компонентами можно было управлять как частью пакетов (packages) MTS. Будучи частью пакета MTS, компонент мог воспользоваться некоторыми возможностями MTS, например, выполнением транзакций и активацией по требованию. Те же и многие другие возможности доступны также и в COM+, и обратиться к ним из существующих компонентов можно, перенеся в новую среду пакеты MTS, компоненты или то и другое.
Для переноса существующего пакета MTS нужно, прежде всего, экспортировать пакет в файл с расширением .pak (файл пакета MTS). Это можно сделать, щелкнув правой кнопкой мыши на существующем приложении MTS и выбрав в меню команду Export (Экспорт). Далее нужно просто следовать указаниям по экспорту в файл пакета MTS, решив, в том числе, вопрос об экспорте ролей вместе с пакетом.
Можно относительно просто перенести пакеты MTS в COM+, создав с помощью консоли служб компонентов (Component Services Console) новое приложение COM+ и установив переключатель Install Pre-built Application(s) (Установить готовое приложение). Когда мастер установки приложения COM (COM Application Install Wizard) запросит имя существующего приложения, найдите или выберите созданный ранее PAK-файл.
При импорте существующего пакета MTS в новое приложение COM+ диспетчер служб компонентов (Component Services Manager) импортирует также роли (roles) и пытается сопоставить существующую функциональность MTS новой функциональности служб COM+ — включая настройки транзакций и активации по требованию.
Вместо того чтобы сначала экспортировать пакеты MTS, а затем импортировать их в приложение COM+, можно создать пустое приложение COM+, затем воссоздать любые существующие роли и добавить их в любые существующие компоненты. В этом случае вы сможете убедиться, что свойства приложения COM+ определены в соответствии с вашими предпочтениями.
В следующем разделе обсуждаются некоторые настройки приложений COM+, влияющие на ASP-приложения.
СОВЕТ
За дополнительной информацией о работе с консолью служб компонентов (Component Services Console) обратитесь к справочной системе Windows 2000.
Активация приложений COM+
Приложения COM+ вносят дополнительные возможности для поддержки активации, транзакций и защиты компонентов, включенных в состав приложений. В частности, приложения COM+ добавляют поддержку безопасности на уровнях метода и компонента, а также поддержку внутренней и внешней активации процесса.
ПРИМЕЧАНИЕ
В этом разделе рассмотрены не все возможные службы COM+, а лишь самые необходимые для разработки ASP-компонентов, например, безопасность, транзакции и объединение объектов. Активация по требованию подробно рассмотрена в разделе «Интерфейс IObjectControl».
Управлять безопасностью ASP-приложения на уровне страницы или ресурса можно с помощью средств безопасности NTFS или средств безопасности IIS. С помощью средств ролевой безопасности приложений COM+ можно добавить и еще один уровень безопасности.
При реализации ролевой безопасности внутри приложения COM создается роль, и для этой роли добавляются пользователи. После этого при обращении к компоненту или методу компонента COM+ проверяет полномочия безопасности роли, членом которой является пользователь, в соответствии с требованиями по безопасности компонента или метода компонента, и отказывает в доступе, если полномочия пользователя этим требованиям не отвечают.
Приложение COM+ можно создать по двум различным схемам активации: активации внутри клиентского процесса и активации в изолированном процессе.
Если приложение COM+ создано как серверное, обращение к компоненту с ASP-страницы генерирует отдельную программу dllhost.exe для этого компонента и любого другого, являющегося частью приложения COM+. Поскольку компонент находится в отдельном процессе, все вызовы компонента с ASP-страницы должны маршализироваться. Вместе с тем, если компонент некачественный и генерирует нечто опасное для программного обеспечения, это нечто останется внутри файла dllhost.exe и не сможет неблагоприятно повлиять на IIS или остальные ASP-приложения.
Если создать приложение COM+ как библиотечное, при обращении к компоненту с ASP-страницы экземпляр этого компонента (и любого другого внутри приложения COM+) создается внутри того же процесса, что и ASP-страница — если только поточная модель компонента совместима. Поскольку приложения COM+ допускают апартаментно-поточную, произвольно-поточную и нейтрально-поточную модели, экземпляр компонента приложения COM+ должен создаваться внутри процесса ASP-страницы, а все вызовы между сценарием страницы и компонентом должны выполняться в том же потоке.
Службы COM+
Компонент, с помощью диспетчера служб COM+ (COM+ Services Manager) установленный как часть приложения COM+, называется сконфигурированным компонентом (configured component). Такой компонент может воспользоваться преимуществами служб COM+, например, поддержкой транзакций и активацией по требованию. Компонент, не установленный как часть приложения COM+ и зарегистрированный с помощью инструментов разработки (например, VB) или утилиты regsvr32.exe, называется несконфигурированным компонентом (unconfigured component). Этот тип компонента использовать в ASP-приложениях можно, но реализовать интерфейс IObjectControl, воспользоваться преимуществами активации по требованию или применить объект ObjectContext для поддержки транзакций нельзя. Можно, однако, как и прежде, обратиться с помощью объекта ObjectContext к встроенным объектам ASP.
Одним из основных доводов в пользу добавления компонента в приложение COM+ является желание применить компонент в транзакциях. Это момент обсуждается в следующем разделе.
Поддержка транзакций
Одна из проблем при работе с компонентами, построенными на основе технологий COM/DCOM, состоит в том, что связь компонентов, особенно удаленная связь, весьма непроста. Выяснение того, успешно ли завершена обработка компонента, что делать дальше и как восстановить задачу, если один из компонентов не смог ее выполнить, а остальные — смогли, может превратить распределенную систему в плохо реализуемую и трудно управляемую.
Для упрощения этого процесса был разработан сервер MTS, осуществляющий большую часть администрирования распределенной системы. Такой подход гарантирует, что транзакция будет либо целиком успешно завершена, либо, наоборот, целиком не завершена. MTS также управляет процессами и потоками приложения, то есть тем, что становится очень критичным, когда некоторый компонент вызывает методы другого, а тот, в свою очередь, методы еще одного компонента и так далее:
Кроме того, MTS предоставляет менеджеров (managers) и диспетчеров (dispensers) ресурсов, которые управляют данными хранилищ, например, данными баз данных. По сути, любая система баз данных, поддерживающая транзакции OLE, скажем, SQL Server, может участвовать в транзакциях, управляемых MTS. Это означает, что если компонент, участвующий в транзакции, выполняется с ошибкой, то можно отозвать (вернуть к прежнему состоянию) не только его действия и действия других компонентов, но и любое действие, произведенное базой данных.
Все возможности MTS по работе с транзакциями реализованы в службах COM+ и доступны для сконфигурированных компонентов. Отличие состоит в том, что в COM+ (и Windows 2000) присутствует интерфейс IContextState, позволяющий отделить активацию компонента от управления транзакциями. Этот интерфейс способен сигнализировать, собирается ли компонент принять или прервать транзакцию, при этом компонент остается активированным, как было ранее показано в листинге 5.1.
Что такое транзакции?
Если вы имели опыт работы с коммерческими базами данных, например, с Oracle, Sybase и Microsoft SQL Server, то, возможно, знаете, что представляют собой транзакции. Транзакция (transaction) — это одна или несколько задач, некоторым образом логически сгруппированных, причем все задачи, объединенные в данную группу, либо все вместе успешно завершаются, либо все вместе не выполняются, если хотя бы одна из задач внутри транзакции не выполнена. Если транзакция не состоялась, никаких изменений данных, связанных с данной транзакцией, не делается. Если же выполнение транзакции происходит успешно, все изменения данных принимаются. В качестве примера транзакции с несколькими задачами можно привести перевод денег со сберегательного на расчетный счет. Хотя это и похоже на одну транзакцию, в действительности, происходят сразу две транзакции. Первая транзакция состоит в снятии ваших денег со сберегательного счета (то есть они дебетуются на счет), вторая транзакция заключается в переведении этих денег на расчетный счет (то есть они кредитуются на расчетный счет) . Если операция снятия денег со сберегательного счета прошла успешно, а операция по переводу денег на расчетный счет не была выполнена, вы, весьма вероятно, захотите, чтобы целиком вся транзакция была отозвана и начата заново. Транзакции очень существенны для систем, обновляющих несколько структур данных, например, баз данных. Такие обновления, даже основанные на одной операции, должны быть успешно завершены сразу для всех структур данных, именно для того, чтобы обеспечить успешное завершение одной этой операции.
В модели COM+ эти представления расширены благодаря введению в методику разработки компонентов концепции управления транзакциями. Кроме того, благодаря обработке большинства сообщений об успехе или неудаче транзакции, процесс разработки приложений, основанных на распределенных компонентах, существенно упрощается.
Участие компонентов в транзакциях
Компонент может воспользоваться возможностями транзакций COM+, если он удовлетворяет следующим условиям. Во-первых, компонент должен быть внутрипроцессным сервером (то есть DLL). Во-вторых, он не должен быть свободно-поточным. Далее, если компонент создан в Visual С++, он должен реализовать фабрику классов (class factory) и использовать стандартную маршализацию. Кроме того, для компонента нужно будет создать библиотеку типов.
Если компонент удовлетворяет минимальным требованиям, чтобы получить поддержку транзакций, он должен быть зарегистрирован в COM+. В процессе регистрации тип транзакции, в которой участвует компонент, устанавливается в качестве свойства компонента. Например, на рис. 3 показано окно свойств компонента, для которого требуется транзакция, время ожидания завершения которой определено в 10 секунд.
Чтобы начать транзакцию на ASP-странице, поместите первой строкой страницы следующую директиву:
Для работы с транзакциями компонент должен ссылаться на экземпляр объекта ObjectContext (или интерфейса IObjectContext) или экземпляр интерфейса IContextState. Пометить компонент как готовый к принятию транзакции можно с помощью метода SetComplete объекта ObjectContext или метода SetMyTransactionVote интерфейса IContextState:
Пометить транзакцию для отзыва можно с помощью тех же интерфейсов:
Транзакции COM+ в действии показаны в примерах главы 9, посвященной созданию ASP-компонентов промежуточного слоя с помощью ADO. В этих примерах транзакции выполняются для обновления баз данных — и это их традиционное применение. Транзакции с базами данных можно выполнять непосредственно (не прибегая к транзакциям COM+) с помощью объекта ADO Connection. Тем не менее, примеры этой главы продемонстрируют, что гораздо удобней наладить взаимодействие компонентов и баз данных посредством транзакций COM+.
Транзакции COM+ можно также использовать для управления взаимодействием с другими ресурсами, например, с сообщениями в очереди сообщений. В главе 13, посвященной работе с компонентами MSMQ, транзакции управляют непрерывным удалением сообщений из очереди по мере получения к ним доступа.
Помимо поддержки транзакций и активации по требованию, службы COM+ могут также обеспечивать резервирование (pooling) компонентов.
Резервирование компонентов
Ранее уже отмечалось, что у интерфейса IObjectControl есть три метода: Activate, Deactivate и CanBePooled. У вас уже была возможность поработать с методами Activate и Deactivate, но для резервирования компонентов важен именно метод CanBePooled.
В случае резервирования объектов при пуске приложения COM+ создается минимальный резерв, или пул (pool), компонентов, и все запросы компонента обслуживаются из этого пула посредством менеджера пула (pool manager). Если менеджер получает запрос большего, чем имеется в пуле, числа экземпляров компонента, он создает другой экземпляр компонента, добавляя его в пул — до достижения максимального размера пула. Если же максимальный размер пула достигнут, запрос ставится в очередь до тех пор, пока компонент не станет доступным.
Компоненты, объединяемые в пул, должны соответствовать определенному набору условий. Во-первых, компонент не должен быть отдельно-поточным. Далее, компонент должен поддерживать агрегирование (aggregation), однако при этом свободно-поточный составитель (Free-Threaded Marshaler, FTM) использоваться не может.
Если компонент участвует в транзакциях, придется вручную привлечь ресурсы и вручную отключить автоматическое привлечение ресурсов (процесс, отличающийся от управления ресурсами и в этой книге не рассматриваемый).
Если компонент обращается к ресурсам, он должен реализовать три метода интерфейса IObjectControl — Activate, Deactivate и CanBePooled. Обратиться к ресурсу можно с помощью метода Activate (когда компонент создается на клиентской ASP-странице). Этот ресурс может быть высвобожден при высвобождении компонента клиентом (с помощью метода Deactivate). Кроме того, компонент обязан проверить, находятся ли его ресурсы в состоянии, когда их можно резервировать при деактивации компонента. Если резервирование возможно, метод компонента CanBePooled должен возвратить значение True, в противном случае, компонент должен возвратить значение False, чтобы предотвратить возврат компонента в пул.
Наконец, состояние компонента не должно зависеть от сеанса.
Создать резервируемый компонент можно с помощью Visual C++ (или любого другого языка, поддерживающего свободно-поточную или произвольно-поточную модели). Для демонстрации резервирования объектов создайте с помощью мастера ATL COM AppWizard новый проект Visual С++ и назовите его asp0502. Оставьте установленные по умолчанию параметры для MTS, MFC, заместителя (proxy) и исполнителя (stub).
После генерации файлов проекта добавьте новый компонент с помощью мастера ATL Object Wizard. (Подробно работа с этим мастером рассмотрена в главе 14.) Выберите тип Simple Object (Простой объект). Назовите компонент pooledComponent, на вкладке Attributes (Атрибуты) выберите произвольно-поточную модель и поддержку агрегирования (но не поддержку FTM).
Для поддержки интерфейса IObjectControl добавьте в определения класса в файле pooledComponent.h следующую строку:
Добавьте в данные COM ссылку на карту COM (COM map):
При работе с компонентом вам придется обращаться к интерфейсу IObjectContext и встроенному объекту ASP Response, поэтому добавьте в заголовочные файлы библиотеки типов службы COM+ и ASP следующее:
Следует также добавить прототипы метода интерфейса IObjectControl и ссылку на два закрытых (private) члена данных типа интерфейсов IObjectContext и IResponse (встроенного объекта ASP Response). Полный программный код заголовочного файла для резервируемого компонента показан в листинге 5.3.
Листинг 3. Заголовочный файл резервируемого компонента
// pooledComponent.h : объявление CpooledComponent
#ifndef __POOLEDCOMPONENT_H_
#define __POOLEDCOMPONENT_H_
#include «resource.h» // основные символы
#include
#include
/////////////////////////////////////////////////////////////////
// CpooledComponent
class ATL_NO_VTABLE CpooledComponent :
public CComObjectRootEx ,
public CComCoClass ,
public IObjectControl,
public IDispatchImpl
<
public:
CpooledComponent()
<
>
BEGIN_COM_MAP(CpooledComponent)
COM_INTERFACE_ENTRY(IpooledComponent)
COM_INTERFACE_ENTRY(IObjectControl)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// IpooledComponent
public:
STDMETHOD(Activate)();
STDMETHOD_(BOOL, CanBePooled)();
STDMETHOD_(void, Deactivate)();
private: CComPtr m_spObjectContext;
CComPtr m_piResponse;
>;
Далее, с помощью вкладки Link (Связь) диалогового окна Project Settings (Настройки проекта) добавьте в список связанных библиотек проекта библиотеку служб COM+ (comsvcs.lib).
Вам придется реализовать три метода интерфейса IObjectControl. В CPP-файле компонента добавьте код, показанный в листинге 5.4. В этом коде экземпляры IObjectContext и IResponse создаются в методе Activate и высвобождаются в методе Deactivate. Кроме того, метод CanBePooled возвращает значение True и помечает, что компонент можно резервировать (помещать в пул).
Листинг 4. Реализация компонентом методов интерфейса IObjectControl
HRESULT CpooledComponent::Activate()
<
HRESULT hr;
CComBSTR bstrProp;
CComVariant vt;
CComPtr pProps; //Свойства контекста
// получение ObjectContext
hr = CoGetObjectContext(IID_IObjectContext,
(void **)&m_spObjectContext);
if (FAILED(hr)) return hr;
// получение ContextProperties
hr = m_spObjectContext
->QueryInterface( IID_IGetContextProperties,
(void**)&pProps );
if (FAILED(hr)) return hr;
// получение свойства Response
bstrProp = «Response»;
hr = pProps->GetProperty( bstrProp, &vt ) ;
if (FAILED(hr)) return hr;
p > hr = piDispatch->QueryInterface( IID_IResponse,
(void**)&m_piResponse );
return hr;
>
void CpooledComponent::Deactivate()
<
m_piResponse.Release();
m_spObjectContext.Release();
>
BOOL CpooledComponent::CanBePooled()
<
return TRUE;
>
Откомпилируйте теперь компонент, чтобы убедиться, что необходимый программный код и библиотеки добавлены.
С помощью вкладки ClassView (Просмотр классов) добавьте новому компоненту не принимающий никаких параметров метод testPooledComponent. Этот метод прост: он выдает с помощью объекта Response сообщение для web-страницы. Добавьте компоненту программный код метода, показанный в листинге 5.5.
Листинг 5. Единственный метод резервированного компонента
STDMETHODIMP CpooledComponent::testPooledComponent()
<
// печать сообщения
CComVariant vt(«Привет от резервированного компонента»); m_piResponse->Write(vt);
Чтобы воспользоваться резервированием компонентов, компонент нужно добавить в приложение COM+. Создайте новое приложение — с помощью консоли управления службами компонентов (Component Services Management Console) — или воспользуйтесь существующим приложением COM+ и добавьте компонент в приложение.
Добавив компонент в приложение COM+, откройте с помощью контекстного меню диалоговое окно Properties (Свойства) и перейдите на вкладку Activation (Активация). Установите флажок объединения компонентов, задайте минимальный размер пула 10, максимальный — 20.
При первом запуске приложение COM+ создает пул из 10 уже созданных компонентов. При обращении с ASP-страниц к этим компонентам они извлекаются из этого пула до тех пор, пока все не будут активированы. После этого все новые вызовы компонентов будут добавлять экземпляры этих компонентов в пул, пока не будет достигнут максимальный размер пула в 20 экземпляров.
Протестируйте компонент с помощью следующей тестовой ASP-страницы (asp0502.asp):
На этой странице создаются 20 экземпляров компонента и вызывается метод каждого из компонентов. Результатом обращения к этой ASP-странице будет список сообщений со строкой «Привет от резервированного компонента». Первые 10 экземпляров были созданы при запуске приложения COM+, а последние 10 — при обращении к ASP-странице.
Измените теперь число экземпляров на 21 и обратитесь к ASP-странице. Похоже, она виснет. Это происходит потому, что менеджер пула выдал все компоненты из доступного пула, вследствие чего достигнут максимальный размер пула. Запрос 21-го компонента ставится в очередь до тех пор, пока какой-нибудь компонент не освободится. Но поскольку до завершения страницы все компоненты остаются недоступными, а запрос компонента происходит на той же самой странице, экземпляр 21-го компонента не может быть создан, и страница зависает. Фактически страница будут продолжать висеть до истечения времени ожидания запроса компонента. После этого страница выведет следующее сообщение об ошибке («активация COM+ не удалась, поскольку она не может быть завершена в течение заданного времени»):
COM+ activation failed because the activation could not be
completed in the specified amount of time.
Как было показано, резервирование объектов может служить весьма мощным ускорителем производительности, но оно же может и ограничить масштабируемость (scalability) или истощить ресурсы (поскольку резервируемые компоненты удерживаются в памяти). Пользуйтесь этой службой COM+ с осторожностью.
Создание серверных компонентов для ASP-приложений
ASP (Active Server Pages) — технология создания Web-страниц, содержащих код, выполняемый Web-сервером (подробнее об истории и современном состоянии этой технологии см. в статье Алексея Федорова «Технология ASP+», № 9’2000, а о практических вопросах применения ASP см. в цикле статей Рубена Садояна , № 9-11’2000. — Прим. ред.). Современная версия данной технологии позволяет использовать в ASP-страницах сторонние серверные компоненты, созданию которых и посвящена данная статья.
В качестве средства разработки ASP-объектов будет использоваться Borland Delphi 5, Enterprise-версия которого позволяет это делать. К сожалению, в документации, поставляемой с Delphi 5, крайне скупо сказано о назначении, последовательности создания и тестирования серверных объектов ASP; кроме того в комплекте поставки Delphi 5 отсутствуют примеры работающих ASP-объектов. Настоящая публикация частично восполняет эти пробелы.
Клиентское приложение, использующее ASP-объекты, представляет собой HTML-документ (этот документ может также включать клиентский и серверный коды на скриптовых языках. — Прим. ред.), который в принципе можно прочесть с помощью любого Web-браузера. Обычно такие HTML-документы размещаются на каком-либо Web-сервере (как правило, это Microsoft Internet Information Server версии 3.0 и выше). Web-сервер, получив требование о предоставлении документа, считывает его из локального хранилища и передает клиенту, при этом часть информации вносится в документ Web-сервером динамически; сам же Web-сервер может обращаться к ASP-объектам, входящим в комплект поставки Internet Information Server или созданным сторонними разработчиками. Обычно Web-документы, содержащие обращения к ASP-объектам, имеют расширение *.asp. Примеры подобных документов можно найти в каталогах, создаваемых при установке Internet Information Server.
Некоторые Web-дизайнеры полагают, что технология ASP заключается, грубо говоря, в замене расширения *.htm в HTML-файле на расширение *.asp. Действительно, при работе с Internet Information Server изменение расширения файла позволит корректно отображать находящийся в нем HTML-документ. Однако на самом деле технология ASP — это обращение к методам специальных объектов, называемых ASP-объектами и представляющих собой COM-серверы. Типичный пример обращения к ASP-серверу из HTML-документа, представляющий собой фрагмент кода на языке VBScript, выглядит следующим образом:
Несмотря на наличие кода на скриптовых языках (VBScript или JavaScript), ASP-страница может быть доступна клиентам, работающим в других операционных системах, например в UNIX. На первый взгляд это может показаться странным, поскольку UNIX-компьютеры не используют ни Basic, ни тем более VBScript. Но дело в том, что скрипты, содержащиеся в ASP-документах, выполняются на сервере, а клиент получает HTML-документ, который является результатом выполнения этого скрипта.
Серверные объекты ASP выполняются в адресном пространстве Internet Information Server (Internet Information Services), работающего под управлением операционной системы Windows NT (Windows 2000) либо Windows 98.
По существу ASP-сервер представляет собой сервер автоматизации, в котором предопределено несколько интерфейсов; среди них — IRequest и IResponse. Интерфейс IRequest содержит методы, вызов которых позволяет передать параметры, введенные пользователем и заполненные на клиенте (об этом будет рассказано ниже). IResponse содержит методы, вызов которых приводит к формированию HTML-документа и передаче данного документа пользователю. Наличие этих признаков делает ASP-сервер похожим на CGI-приложения и ISAPI/NSAPI DLLl (далее — Web-приложения). Идеология выполнения методов в ASP-объекте и Web-приложениях также аналогична: сначала анализируется запрос клиента, затем динамически формируется отклик. Различие заключается в том, что Web-приложения формируют HTML-документ целиком, в то время как отклик ASP-объекта вставляется в исходную HTML-страницу. Например, если документ ASP представлен в виде:
и результат выполнения метода ScriptContent возвращает строку ‘First call to ASP server’, то пользователь, получивший данный документ, увидит следующее (рис. 1).
Иными словами, отклик ASP-объекта добавляется к HTML-документу. В одном документе допустимо обращение к нескольким ASP-объектам, и результат их отклика формируется в единый документ — этого невозможно достичь при использовании Web-приложений. Впрочем, имеется одно ограничение: набор ASP-серверов, к которым производится обращение из одного документа, должен быть зарегистрирован на одном и том же Internet Information Server — нельзя обращаться по различным адресам для формирования одного HTML-документа.
Как было сказано выше, Enterprise-версия Delphi 5 содержит эксперт для создания ASP-объектов. Для запуска этого эксперта следует выбрать пункт главного меню File|New среды разработки Delphi, а затем со страницы ActiveX репозитария объектов — пиктограмму Active Server Object.
ASP-сервер, содержащий ASP-объекты, реализуется в виде как исполняемых файлов *.exe, так и библиотек *.dll — это разрешается при создании серверов автоматизации. ASP-сервер, реализованный в виде исполняемого файла, запускается каждый раз в ответ на запрос клиента. При использовании внутренних (in-process) ASP-серверов, выполненных в виде динамически загружаемых библиотек, один экземпляр DLL, загруженный в адресное пространство Internet Information Server, способен обслуживать одновременно нескольких клиентов. При этом возможно либо создание отдельного экземпляра COM-объекта для каждого клиента, либо обслуживание нескольких клиентов единственным экземпляром COM-объекта. Это зависит от модели работы в потоках (Threading Model), выбранной при заполнении диалога, который появляется при запуске эксперта создания ASP-объектов.
Рассмотрим теперь, каким образом работает ASP-сервер на конкретном примере создания внутреннего (in-process) ASP-сервера. Для простоты ограничимся сервером, который выполняет один запрос. Итак, выберем пункт главного меню File|New среды разработки Delphi, со страницы ActiveX репозитария объектов — пиктограмму ActiveX Library и нажмем кнопку OK. В итоге получим новый проект, который сохраним, например, под именем ASP01. Теперь снова выберем пункт главного меню File|New среды разработки Delphi, со страницы ActiveX репозитария объектов — пиктограмму Active Server Object. В появившемся диалоге определим имя будущего COM-класса, например Test.
Поскольку мы создаем in-process-сервер, параметр Instancing не имеет значения — он важен только для исполняемых файлов. Зато в данном случае серьезную роль играет параметр Threading Model. При значении этого параметра равным Single работа сервера неэффективна, поскольку при одновременном обращении к нему нескольких клиентов сервер выполняет запросы последовательно, и если один из клиентов обращается с длительным запросом, то остальные вынуждены ожидать его окончания, даже если их запросы не требуют большого количества времени для выполнения. При этом у пользователей создается впечатление «зависания» браузера, что зачастую приводит к попыткам разными методами прервать задачу. Значение Apartment приводит к разделению запросов клиентов по потокам, причем для каждого клиента будет создан свой экземпляр COM-объекта, в данном примере — класса TTest. При этом при написании методов COM-класса не требуется защиты переменных класса внутри потоков — клиент может свободно модифицировать их, что упрощает разработку кода приложения. Недостаток данной модели состоит в следующем: проект оказывается ресурсоемким и переменные класса инициализируются при каждом обращении, что увеличивает время отклика на запрос. Этих недостатков лишена модель Free, в которой единственный экземпляр COM-объекта обслуживает нескольких клиентов. Однако возможность изменения данных внутри COM-объекта влечет за собой необходимость защиты общих переменных от их изменений из разных потоков, что существенно усложняет процедуру реализации кода приложения и является потенциальным источником трудноуловимых ошибок. Как правило, эту модель используют в ASP-серверах, которые только предоставляют данные, но не позволяют клиенту их модифицировать.
Группа элементов управления Active Server Type дает возможность выбрать назначение ASP-сервера. Если сервер планируется использовать под управлением Internet Information Server версий 3 или 4, то выбирается опция Page Level Events Methods. Объекты, созданные с применением этой опции, можно будет использовать и с Internet Information Services 5.0, но в этом случае опция Object Context позволит создать объект, работающий более эффективно. Эту же опцию следует выбирать, если работой ASP-сервера управляет Microsoft Transaction Server (Windows NT) или Component Services (Windows 2000). Фактически Internet Information Services 5.0 также управляет этим сервером при помощи Component Services, так как оба этих продукта тесно интегрированы.
Опцию Generate a Template Test Script for this object следует оставлять всегда включенной. В этом случае Delphi создаст небольшой HTML-документ, который можно использовать для тестирования ASP-сервера.
Заполнив опции диалога, необходимо нажать кнопку OK, после чего будет создан файл реализации интерфейсов Unit1.pas, который следует сохранить (например, под именем U1_01). Кроме того, будет создана библиотека типов, появятся ее редактор и файл, описывающий библиотеку типов, в данном примере — TEST_TLB.pas.
Если была выбрана опция Page Level Events Methods (как в нашем примере), то библиотека типов будет содержать два предопределенных метода — OnStartPage и OnEndPage. Также будет создан файл Test.asp, который содержит HTML-документ с заготовками на языке VBScript для тестирования сервера. Если заглянуть в файл реализации (U1_01.pas) , то можно увидеть, что класс ТТest является потомком класса TASPObject.
Если была выбрана опция Object Context, библиотека типов не будет содержать предопределенных методов, а сам класс Ttest будет являться потомком класса TASPMTSObject. Оба класса-предка TTest содержат абсолютно одинаковые методы и свойства, но класс TASPObject дополнительно содержит пару методов интерфейса IASPObject — OnStartPage и OnEndPage.
Далее создадим метод, который будет заполнять HTML-документ. Для этого в редакторе библиотеки типов (рис. 2) отметим интерфейс ITest и вызовем команду New Method нажатием кнопки 1.
Назовем вновь созданный метод ScriptContent, отредактировав его название, заданное по умолчанию. Данный метод не должен иметь параметров. Затем вызовем команду Refresh нажатием кнопки 2. После этого в модуле реализации (U1_01.pas) появится заготовка, где следует описать реализацию. Метод реализуем следующим образом:
В данном примере происходит обращение к методу Write интерфейса IResponse. Проверка Assigned(Response) гарантирует, что в момент записи сообщений имеется ссылка на интерфейс.
После этого следует модифицировать созданный Delphi HTML-документ для тестирования сервера, хранящегося в файле Test.asp. В этом документе имеется следующий фрагмент кода на языке VBScript:
В таком виде этот скрипт работать не будет. Необходимо заменить фразу в фигурных скобках
Теперь наш проект необходимо скомпилировать, после чего можно приступить к тестированию созданного ASP-объекта. Для этого необходимо создать виртуальную директорию Internet Information Server, которая обязана иметь разрешение как на чтение (из нее будут читаться данные), так и на выполнениe (из нее будет загружена и запущена библиотека ASP01.dll). Альтернатива — разместить эти файлы в разных директориях, одна из которых имеет доступ Read, а вторая — Execute. Однако в любом случае выбранные директории должны быть доступны с помощью протокола HTTP. Поэтому в первую очередь необходимо обратиться к WWW-сервису Internet Information Server, посмотреть список доступных директорий и при необходимости создать новые с соответствующими правами доступа.
В нашем примере на компьютере, который имеет IP-адрес 10.10.10.65, была создана виртуальная директория /Test, имеющая права доступа Read и Execute и соответствующая физическому адресу на компьютере C:\ASPTest. В эту директорию были скопированы оба файла. Далее в Microsoft Internet Explorer в поле Address был введен следующий URL: HTTP://10.10.10.65/Test/Test.asp. Результат выполнения этого запроса показан на рис. 1. Видно, что скрипт (текст между ) был замещен результатом выполнения метода ScriptContent созданного нами ASP-объекта.
Теперь подробнее рассмотрим, каким образом выполняется скрипт на странице Test.asp. Internet Information Server, получающий запрос о показе этой страницы, считывает ее содержимое, находит скрипт и выполняет его. При этом запускается интерпретатор VBScript и вызывается команда CreateObject. В случае если ASP01.dll ранее не была загружена, происходит ее загрузка. Для данного запроса создается COM-объект — экземпляр класса TTest (он описан в модуле реализации, для данного примера — U1_01.pas). Ссылка на интерфейс IDispatch (он поддерживается в классе TTest) сохраняется в переменной DelphiASPObj.
При последующем написании кода после имени переменной, хранящей ссылку на интерфейс IDispatch, можно набирать практически любой текст. Интерпретатор VBScript использует текст, который содержится в скрипте и следует после имени переменной (в нашем примере: переменная — DelphiASPObj, метод — .ScriptContent), для того чтобы передать его ASP-серверу. Если ASP-сервер найдет метод с данным именем, он его выполнит. В противном случае генерируется исключение. Поэтому при написании скриптов для ASP-сервера следует быть внимательным в названиях методов и при наличии исключений в первую очередь проверить корректность имен методов. При вызове какого-либо метода ASP-серверу становятся доступными интерфейсы IRequest и IResponse.
А теперь рассмотрим пример создания более сложного ASP-сервера, где запрос клиента анализируется при помощи методов интерфейса IRequest. Задачу поставим следующим образом: дадим возможность клиенту найти запись по фрагменту поля ENAME таблицы EMP в базе данных ORCL, сгенерированной Oracle 8.0.4 при его установке. Для этого в каком-либо редакторе форм создадим форму, содержащую однострочный редактор текста и кнопку Submit. HTML-документ с такой формой выглядит следующим образом:
При реализации этой формы вместо IP-адреса 10.10.10.65 следует указать IP-адрес компьютера, на котором установлен ASP-сервер.
Поместим этот документ в директорию C:\ASPTest, которая имеет доступ на чтение и выполнение (см. выше) под именем Name.htm. Но прежде чем создавать модуль данных и обращаться к серверу баз данных, необходимо выяснить, каким образом анализируется запрос клиента в ASP-объекте.
Запрос клиента можно анализировать при помощи вызова методов интерфейса IRequest, ссылка на который находится в свойстве Request класса TASPObject — предка класса, где реализуется ASP-объект. Интерфейс IRequest предоставляет три свойства: QueryString, Form и Body, в которых находятся ссылки на интерфейс IRequestDictionary. QueryString содержит параметры запроса, Form — список элементов управления, предоставляемых клиенту, а Body — данные, которые клиент ввел в эти элементы управления. Нам потребуются данные, поэтому мы будем анализировать свойство Body, однако все сказанное ниже о методах IRequestDisctionary применимо и к любому другому свойству типа ICustomDictionary — QueryString и Form.
IRequestDictionary определен в модуле ASPTlb.pas следующим образом:
Документация о свойствах этого интерфейса в Delphi отсутствует, и остается только догадываться, каким образом из него можно извлечь параметры запроса, введенные пользователем. Привлекая документацию по компоненту TWebDispatcher, который используется при создании CGI-приложений и ISAPI DLL, где также можно анализировать параметры запроса пользователя, можно понять, что свойство Count содержит число элементов управления на форме — для формы, содержащейся в документе Name.htm, оно равно 2. Свойство Key содержит имена элементов управления; для формы, содержащейся в документе Name.htm, это имена T1 (текстовое поле) и B1 (кнопка). И наконец, свойство Item содержит введенные пользователем значения.
Свойство Count работает как положено, то есть возвращает двойку для примера, приведенного выше. При попытке же извлечь имена элементов управления обнаруживается неприятная особенность: в коллекции Key[] индексы начинаются с единицы, а не с нуля, как это принято в приложениях подобного типа. Но все же, обращаясь к коллекции Key с соответствующим индексом — 1 или 2 для нашего примера, можно получить названия элементов управления в виде строковых переменных.
Аналогичная попытка извлечь данные, введенные пользователем в элементы управления, ни к чему хорошему не приводит. Так, при присвоении строковой переменной значения из коллекции Item[] (которая объявлена аналогично коллекции Key[]) происходит исключение. При анализе значения, возвращаемого коллекцией Item[I], обнаруживается, что возвращается интерфейс — потомок IDispatch. Методы и свойства этого интерфейса также не описаны.
Отсутствие описания интерфейса, а также возврат ссылки на него в переменной типа OLEVariant (а не IDispatch) обычно характерно для продуктов, которые находятся в стадии разработки. В этом случае заголовки методов интерфейса, список параметров методов и их число постоянно меняются, а во избежание исключений в клиентских приложениях часто используется позднее связывание. Этот факт настораживает: не исключена возможность изменения методов интерфейса в будущем, что может привести к потере работоспособности созданных ранее ASP-серверов. Однако хочется надеяться, что данный интерфейс устоялся, а в Microsoft по какой-либо причине просто забыли внести изменения в интерфейсный модуль.
Интерфейс — потомок IDispatch имеет два свойства: Count, которое всегда возвращает 1, и Item[]-коллекцию, которая возвращает текст, введенный клиентом в элементах управления. Коллекция Item начинается с индекса 1.
Для понимания и тестирования запроса в ASP-объекте сделаем небольшое дополнение к проекту. Воспользовавшись редактором библиотек типов, создадим новый метод RequestProp, как это было описано ранее (см. рис. 2). Напишем следующий код для метода RequestProp:
Скомпилируем проект и в созданном ранее файле Test.asp изменим код на языке VBScript следующим образом: вместо строки DelphiASPObj.ScriptContent напишем строку DelphiASPObj. RequestProp. После этого в Internet Explorer необходимо обратиться к странице Name.htm командой http://10.10.10.65/Test/Name.htm, где вместо 10.10.10.65 следует набрать IP-адрес сервера.
В полученной форме (рис. 3) введем какое-либо значение в элемент управления для редактирования данных и нажмем кнопку Submit. В итоге имеем результат выполнения приведенного выше кода метода RequestProp:
Итак, для определения параметров, введенных клиентом в какой-либо элемент управления, необходимо просмотреть всю коллекцию Keys, найти индекс интересующего нас элемента управления (в нашем примере — это 1, что соответствует ключу T1) и извлечь значение, введенное клиентом, посредством вызова команды Request.Body.Item[Index].Item[1].
Теперь можно перейти к модификации имеющегося сервера — созданию нового метода для доступа к базам данных.
Модуль данных, в который можно помещать невизуальные компоненты, экспертом создания ASP-объекта не генерируется — его необходимо создавать отдельно. Поэтому выберем пункт File|New главного меню среды разработки и из репозитария объектов выберем пиктограмму Data Module. В результате к проекту будет добавлен модуль данных, который мы сохраним под именем U1_02.pas. В этот модуль данных будут помещены невизуальные компоненты доступа к данным (визуальные компоненты в ASP-объектах использовать нельзя).
Вообще говоря, в Web-приложениях (ASP, ISAPI/NSAPI, CGI) показ модальных форм с элементами управления (а диалоги — частный вид таких форм) ни к чему хорошему не приводит. При попытке показать диалог элементы управления на диалоге будут созданы, и приложение будет ожидать, когда диалог будет закрыт (нажатием кнопки OK или Cancel), чтобы продолжить свою работу. Особенность заключается в том, что диалог невидим. Поэтому его нельзя закрыть ни с помощью нажатия на кнопки (они не получают сообщения OnClick), ни с помощью клавиш-акселераторов (сигналы с клавиатуры не посылаются невидимым элементам управления). Визуально программист наблюдает следующее: приложение «висит», отклика от ASP-объекта клиент не получает, а для повторной компиляции проекта требуется перезапуск Internet Information Server либо перезагрузка операционной системы. Даже если команды показа диалогов в ASP-сервере отсутствуют, они могут быть показаны в процессе работы приложения — например, какая-либо из библиотек, используемых приложением, пришлет сообщение об ошибке. Данный факт надо принимать во внимание при написании кода, в котором следует тщательно проверять данные перед их использованием, чтобы внешние приложения или библиотеки не присылали сообщений об ошибках в виде диалогов.
Традиционно доступ к данным в Delphi оcуществляется с помощью механизма Borland Database Engine (BDE), при этом необходимо использовать компоненты TSession, TDatabase и TQuery. Однако при создании ASP-объектов для доступа к данным выяснилось, что в них нельзя использовать BDE для доступа к SQL-серверам — при попытке соединиться с базой данных после передачи параметров, содержащих имя пользователя и пароль, происходит исключение (данный факт был проверен для Oracle 8.0.4 и IB Database 5.5). Через BDE удалось получить доступ только к базе данных DBDEMOS, которая не требует аутентификации пользователя при обращении к данным. К сказанному следует добавить, что в Windows 2000 нельзя получить доступ к данным через BDE в традиционных Web-серверных приложениях ISAPI DLL и CGI, в отличие от предыдущих версий Windows (95/98/NT).
К счастью, в Delphi 5 имеется альтернативный способ доступа к данным — с помощью ADO (ActiveX Data Objects). Для работы с ADO прежде всего необходимо использовать компонент TADOConnection. Поместим его в модуль данных. В инспекторе объектов выберем свойство ConnectionString и вызовем диалог для создания строки. В предложенном диалоге выберем Microsoft OLE DB Provider for Oracle и нажмем кнопку Next (рис. 4).
На второй странице диалога необходимо указать имя сервера (в данном примере — beq-local) и параметры аутентификации: имя пользователя (SCOTT) и пароль (TIGER). Обязательно следует отметить опцию Allow Saving Password, иначе ASP-объект попытается показать диалог ввода имени пользователя и пароля. Протестировать соединение можно нажатием кнопки Test Connection — должно появиться сообщение об успешном соединении с сервером.
Далее в инспекторе объектов следует установить свойство LoginPromp равным False. При работе с другими примерами нужно также изменять свойство DefaultDatabase — имя базы данных, но для данного примера это не обязательно. Проверить правильность установок можно при помощи установления свойства Connected равным True, при этом не должны появляться ни диалог ввода имени пользователя и пароля, ни информация об исключении.
Поместим компонент TADOQuery в модуль данных и в его свойстве Connection сошлемся на определенный выше компонент ADOConnection1.
Следует учесть, что только что разработанный модуль данных автоматически при загрузке ASP-сервера или при обращении клиента к ASP-объекту создаваться не будет. Поэтому необходимо переписать конструктор и деструктор класса TTest, реализация которого находится в файле U1_01.pas. Сошлемся на модуль U1_02.pas в модуле U1_01.pas. В объявлении класса TTest в секции private определим переменную FData типа TDataModule1. В секции public объявим процедуры AfterConstruction и BeforeDesctruction c обязательной директивой override:
Реализуем процедуры AfterConstruction и BeforeDestruction в секции реализации:
Далее в библиотеке типов (см. рис. 2) следует создать новый метод, который назовем QueryResponse. Реализуем его следующим образом:
В данном методе динамически создается SQL-запрос, при этом используются параметры, введенные клиентом в форму на рис. 3. С этим запросом происходит обращение к серверу баз данных, и возвращаемые данные помещаются в HTML-документ. В созданном ранее файле Test.asp изменим код на VBScript следующим образом: вместо строки DelphiASPObj.ScriptContent напишем строку DelphiASPObj. QueryResponse. После этого запустим Microsoft Internet Explorer и снова обратимся к странице Name.htm. Результат выполнения запроса приведен на рис. 5.
При использовании ASP-объекта необходимые для его работы параметры можно поместить, в HTML-документ. Параметры могут редактироваться в документе, что позволит изменять опции для конкретного сайта. Это удобно при поставке ASP-сервера: купившая его компания может изменить начальные установки так, чтобы они соответствовали требованиям компании. Для этого достаточно отредактировать HTML-документ, что возможно даже силами специалистов низкой квалификации.
Пример проиллюстрируем следующим образом: определим в заголовке класса TTest (U1_01.pas) две переменные: FCompanyName:string и FCopyrightYear:string. Определим в библиотеке типов (см. рис. 2) два новых свойства: CompanyName:string и CopyrightYear:integer. В методах *Read и *Wirte для этих свойств определим чтение и возврат данных из описанных выше переменных. В библиотеку типов добавим новый метод ShowCopyright, который реализуем следующим образом:
В созданном ранее файле Test.asp изменим код на языке VBScript:
Результатом обращения к ASP-объекту при помощи команды http://10.10.10.65/Test/Test.asp (вместо 10.10.10.65 следует ввести IP-адрес вашего сервера) будет генерация следующей страницы (рис. 6).
Если в файле Test.asp изменить имя компании (а это можно сделать при помощи любого текстового редактора), соответствующие изменения отобразятся в HTML-документе.
При помощи ASP-объектов можно передавать не только текстовую информацию, но и двоичную, например графические изображения. Создадим обычную HTML-страницу следующего вида:
Файл Test1.asp выглядит следующим образом:
При создании этого файла следует обратить внимание на отсутствие в нем тэгов , и т.д. а также любых записей, которые могут быть интерпретированы как текст в формате HTML. В нем должно быть только обращение к методу ASP-объекта и больше ничего. В противном случае появится сообщение о недопустимости изменения типа отклика после записи заголовков в HTML-документ.
Метод GetPicture реализуем следующим образом:
Для передачи двоичных данных необходимо указать их тип в свойстве Response.ContentType и воспользоваться методом BinaryWrite объекта Response. В качестве параметра этого метода используется переменная типа OLEVariant. В этой переменной должны находиться двоичные данные в виде массива байтов. Этот массив передается как отклик. В результате обращения к исходному HTML-документу клиент получит отклик (рис. 7).
До сих пор мы рассматривали in-process-серверы, которые работают в адресном пространстве Internet Information Server и реализуются в динамически загружаемых библиотеках — DLL. В заключение следует сказать и о создании out-of-process ASP-серверов. Такие серверы реализуются в виде исполняемых файлов и работают в отдельном адресном пространстве.
Для создания out-of-process-сервера необходимо открыть готовый проект, компиляция которого приводит к созданию исполняемого файла, или создать новый проект с помощью пункта меню File|New Application. После этого следует выбрать пункт главного меню File|New среды разработки Delphi, а затем со страницы ActiveX репозитария объектов — пиктограмму Active Server Object. В результате будет сгенерирована библиотека типов, содержащая методы OnStartPage и OnEndPage. Все, что было сказано выше по поводу in-process-сервера, справедливо и в отношении out-of-process-сервера: разработка ASP-объекта заключается в создании новых методов, которые будут вызываться из VBScript-кода ASP-страницы.
Сложности возникают при попытке протестировать out-of-process-сервер. По умолчанию параметры настройки Internet Information Server таковы, что запуск им приложений запрещен, а разрешена только загрузка DLL. Более того, в администраторе Internet Information Server отсутствует опция, позволяющая разрешить или запретить использование приложений как ASP-серверов. Для разрешения запуска исполняемого файла как ASP-сервера необходимо выполнить следующий код:
Для выполнения данного скрипта необходимо, чтобы пользователь, инициирующий его выполнение, имел статус администратора. По этой причине данный скрипт бесполезно определять в HTML-документе и запускать его с использованием Internet Explorer, так как любой пользователь Internet имеет статус гостя (Guest), а не администратора. Скрипт необходимо поместить в обработчик какого-либо события в среде разработки Visual Basic или VBA и запустить его оттуда (либо использовать Windows Scripting Host. — Прим.ред.).
К сожалению, в Delphi отсутствует метод, аналогичный методу VBScript GetObject. Очевидно, что метод GetObject возвращает ссылку на интерфейс IDispatch Internet Information Server. Однако при этом в качестве параметра он использует строку, которая не является классовой (ее GUID отсутствует в системном реестре). В Delphi аналогичных методов нет, по крайней мере, в виде простых вспомогательных функций. Возможно, подобный метод станет доступным в следующих версиях Delphi.
Разработка элементов управления ASP.NET на примере навигационной панели (исходники)
Эта проблема стара, как само веб-программирование — даже на самом простом сайте нам нужна панель навигации (или меню). Ну да, та самая, где написано: «О компании», «Услуги», «Прайс-лист», «Сервис» и «Контакты».
Давным-давно я писал её на Perl и SSI, потом на PHP, потом на ASP, и конца этому не было, пока не вышла 2-ая версия ASP.NET.
Разработчики Microsoft предложили удобное расширяемое решение, которое позволило описывать иерархию страниц в несложном XML-файле, а при желании — увязывать между собой структуры из разных источников (например, из базы данных, из дерева каталогов на диске, из нескольких XML-файлов).
Кроме того, мы получили компоненты для отображения структуры сайта: TreeView , Menu и SiteMapPath . Казалось бы — вот оно, счастье!
Однако нет. Я обнаружил, что простым сайтам нужна лёгкая линейная структура, и встроенные компоненты оказываются для этого случая слишком «громоздкими». Вроде всё хорошо, но не совсем понятно, стоит ли для пары-тройки ссылок подгружать столько кода на JavaScript.
С другой стороны, дизайнеры тоже не дремлют — иногда с ними можно договориться о цвете ссылок, но если речь идёт о навигации, они непреклонны. Хорошо, если ребята не настаивают на ручной отрисовке каждого пункта меню, но вот подложку и roll-over им надо сделать обязательно. И объяснить, что TreeView или Menu для этого не предназначены, чертовски сложно.
Единственное, что нам остаётся — написать подходящий компонент самостоятельно. Этим мы и займёмся.
Для примера возьмём самую простую структуру сайта:
xml version =»1.0″ encoding =»utf-8″ ? >
siteMap xmlns =»http://schemas.microsoft.com/AspNet/SiteMap-File-1.0″ >
siteMapNode url =»
/Default.aspx» title =»Главная страница» >
siteMapNode url =»
/Services.aspx» title =»Услуги и цены» />
siteMapNode url =»
/Contacts.aspx» title =»Контакты» />
siteMapNode >
siteMap >
Нам бы хотелось получить тривиальную панель навигации, например, вот такого вида:
/ Главная страница / Услуги и цены / Контакты /
Текущая страница должна выводиться простым текстом, а все остальные — ссылками.
В коде страницы мы хотим задействовать шаблоны (такие же, как и в компоненте Repeater):
binateq:NavigationPanel ID =»NavigationPanel1″
runat =»server» DataSourceID =»SiteMapDataSource1″ >
HeaderTemplate > / HeaderTemplate >
ItemTemplate >
a href =` ` > #Container.Title %> a > /
ItemTemplate >
SelectedItemTemplate >
span > #Container.Title %> span > /
SelectedItemTemplate >
binateq:NavigationPanel >
asp:SiteMapDataSource ID =»SiteMapDataSource1″ runat =»server» />
Если свести все требования воедино, нужно, чтобы компонент:
- Умел подключаться к SiteMapDataSource и разворачивал структуру сайта в линейный список.
- Использовал шаблоны, разные для текущей страницы и для всех остальных.
Нам «всего лишь» осталось реализовать два перечисленных выше пункта, и затем сделать так, чтобы у нас получился компонент, который можно подвесить на панель инструментов (Toolbox).
Подключение к SiteMapDataSource
Для того, чтобы наш компонент мог подключиться кSiteMapDataSource, мы должны унаследовать его от класса System.Web.UI.WebControls.HierarchicalDataBoundControl и переопределить виртуальный метод PerformDataBinding
public class NavigationPanel: HierarchicalDataBoundControl
<
protected override void PerformDataBinding()
<
base .PerformDataBinding();
nodes.Clear();
if (!IsBoundUsingDataSource >null ))
return ;
HierarchicalDataSourceView view = GetData( string .Empty);
if (view != null )
<
IHierarchicalEnumerable enumerable = view.Select();
RecurseDataBind(enumerable, 1);
>
>
>
Для начала мы должны убедиться, что программист установил одно из свойств DataSource или DataSourceID . Если панель навигации не подключена к SiteMapDataSource (то есть ни одно из свойств не установлено), посетитель сайта увидит содержимое шаблона EmptyTemplate .
Обратите внимание, что DataSource мы проверяем сами, а вот для ревизии DataSourceID необходимо обратиться к свойству IsBoundUsingDataSourceID .
Непосредственный доступ к данным возможен через представление (класс HierarchicalDataSourceView ), то есть тем же способом, каким мы работаем с любым источником данных в .NET, будь то SqlDataSource или XmlDataSource .
Метод GetData позволяет получить любое поддерево структуры сайта, но поскольку нам нужна вся иерархия, в качестве пути мы указываем пустую строку.
Всю работу по извлечению данных выполняет наш метод RecurseDataBind , который рекурсивно вызывает сам себя:
private void RecurseDataBind(IHierarchicalEnumerable enumerable, int level)
<
foreach ( object item in enumerable)
<
IHierarchyData hierarchyData = enumerable.GetHierarchyData(item);
SiteMapNode siteMapNode = hierarchyData as SiteMapNode;
if (siteMapNode != null )
<
if ( HttpContext .Current == null //
siteMapNode.IsAccessibleToUser( HttpContext .Current))
<
bool isSelected =
(siteMapNode.Prov >null ) ?
false :
(siteMapNode.Prov >
nodes.Add(
new Node(
ToAbsolute(siteMapNode.Url),
siteMapNode.Title,
siteMapNode.Description,
level,
isSelected
)
);
>
if (hierarchyData.HasChildren)
<
IHierarchicalEnumerable recurseEnumerable =
hierarchyData.GetChildren();
if (recurseEnumerable != null )
RecurseDataBind(recurseEnumerable, level + 1);
>
>
>
>
Метод проверяет, что он работает с объектами класса SiteMapNode (программист может передать нашему компоненту любую иерархию, и эту ситуацию надо обрабатывать).
С помощью вызова IsAccessibleToUser мы прячем от неавторизованного посетителя недоступные страницы. Авторизация работает только во время исполнения, когда установлено свойство HttpContext.Current , поэтому при настройке компонента в Visual Studio (design mode) все страницы видимы.
Низкоуровневый доступ к данным осуществляет провайдер (наследник класса SiteMapProvider ) из которого мы получаем информацию о текущей странице. Провайдеры доступны только во время выполнения, поэтому в режиме редактирования ни одна страница текущей не является.
Как видим, метод RecurseDataBind сохраняет информацию об уровне вложенности (переменная level ), которой мы можем воспользоваться при подготовке шаблонов.
Функция ToAbsolute переводит виртуальные пути (
/Default.aspx ) в абсолютные ( /Default.aspx ). Фактически, она вызывает VirtualPathUtility.ToAbsolute , но кроме того, обрабатывает ситуации, когда в качестве пути заданы полные URI ( http://domain.tld/path/filename.ext ).
Результатом работы метода RecurseDataBind становится список объектов класса Node, который мы обсудим позднее.
Для того, чтобы наш класс понимал шаблоны, мы должны описать свойства класса ITemplate и установить для них несколько атрибутов:
private ITemplate headerTemplate = null ;
private ITemplate footerTemplate = null ;
private ITemplate itemTemplate = null ;
private ITemplate selectedItemTemplate = null ;
private ITemplate emptyTemplate = null ;
…
[Browsable( false )]
[PersistenceMode(PersistenceMode.InnerProperty)]
[DefaultValue( typeof (ITemplate), «» )]
[TemplateContainer( typeof (Node))]
public virtual ITemplate HeaderTemplate
<
get < return headerTemplate; >
set < headerTemplate = value ; >
>
В целях экономии места, я опустил описание свойств FooterTemplate , ItemTemplate , SelectedItemTemplate и EmptyTemplate , которые полностью идентичны описанию HeaderTemplate .
С помощью атрибутов мы указываем визуальному редактору (т.е. Visual Studio), как обрабатывать эти свойства:
- Browsable(false)
Свойства-шаблоны недоступны на панели Properties во время редактирования. Такое же поведение характерно для «родных» компонентов ASP.NET. - PersistentMode(PersistentMode.InnerProperty)
Шаблоны в коде странице представлены не в виде атрибутов, а в виде вложенных тегов. Пользуясь этой подсказкой, в Visual Studio работает IntelliSense. - DefaultValue(typeof(ITemplate), «»)
Значением по умолчанию является пустой шаблон (без текста и вложенных тегов). Этот атрибут позволяет перевести значение свойства в исходное состояние. - TemplateContainer(typeof(Node))
Одно из самых важных свойств, которое обеспечивает привязку к данным (data binding). О подробностях мы поговорим ниже.
Генерация кода страницы выполняется в методе CreateChildControls :
protected override void CreateChildControls()
<
Controls.Clear();
if (nodes.Count > 0)
<
InstantiateTemplate(headerTemplate);
foreach (Node node in nodes)
<
if (node.IsSelected)
InstantiateNodeTemplate(selectedItemTemplate, node);
else
InstantiateNodeTemplate(itemTemplate, node);
>
InstantiateTemplate(footerTemplate);
>
else
InstantiateTemplate(emptyTemplate);
>
Два вспомогательных метода InstantiateTemplate и InstantiateNodeTemplate я написал, чтобы упростить CreateChildControls :
private void InstantiateTemplate(ITemplate template)
<
if (template != null )
<
Control templateHolder = new Control();
template.InstantiateIn(templateHolder);
Controls.Add(templateHolder);
>
>
private void InstantiateNodeTemplate(ITemplate template, Node node)
<
if (template != null )
<
template.InstantiateIn(node);
Controls.Add(node);
node.DataBind();
>
>
Если свойство установлено, нужно добавить в код страницы элементы управления, описанные в шаблоне.
Эту работу выполняет метод InstantiateIn , которому требуется родительский объект Control , где и будут созданы дочерние элементы управления. Если программист определил шаблон в коде страницы, ASP.NET создаёт для нас объект, реализующий интерфейс ITemplate , в том числе и этот метод.
В методе InstantiateNodeTemplate мы пользуемся уже готовым объектом класса Node , который также является наследником Control . Для того чтобы в код шаблона попали значения выражений вида , мы вызываем метод DataBind .
Давайте подробнее остановимся на том, как происходит связывание с данными (data binding):
- Данные нужно сначала получить, а затем вставить в Control . Метод InstantiateIn устроен так, что получение данных и отображение выполняется через один и тот же объект, поэтому наш класс Node с одной стороны содержит свойства Url , Title , Description , а с другой — наследует классу Control и используется для отображения шаблона. Такой подход снижает зацепление , т.е. класс выполняет действия, которые никак друг с другом не связаны, и делать так не рекомендуется. Что ж, это тот самый случай, когда мы ничего не можем исправить.
- Если мы используем внутри шаблона элементы управления, у которых установлен атрибут ID , он будет дублироваться у повторяющихся шаблонов, что приведёт к ошибке. Речь идёт о таких конструкциях, как:
ItemTemplate >
asp:LinkButton ID =» LB » runat =» server » Text =» Container . Title %>» OnClick =» LB_Click » />
ItemTemplate >
Последнее, что мы должны сделать, это увязать между собой разворачивание структуры сайта в линейный список и его отображение:
public override void DataBind()
<
base .DataBind();
CreateChildControls();
ChildControlsCreated = true ;
>
Метод base.DataBind среди прочего вызывает PerformDataBinding , и сразу после этого мы создаём дочерние компоненты на базе шаблонов.
Доводим компонент до ума
Поддержка ViewState и сериализация
Для того чтобы на странице автоматически работали такие компоненты, как GridView и Repeater , ASP.NET вызывает метод DataBind при первой загрузке страницы. Метод вызывается рекурсивно для всех компонентов страницы, они получают данные и сохраняют их в ViewState. При повторных запросах POST данные не считываются до тех пор, пока мы сами этого не сделаем.
Мы должны обеспечить такое же поведение для класса Node , иначе навигационная панель будет «пропадать» со страницы при запросах POST.
Технически, это делается в два этапа:
- Мы переопределяем методы SaveViewState и LoadViewState у класса NavigationPanel .
- Мы делаем класс Node сериализуемым.
Код методов SaveViewState и LoadViewState достаточно прост, поэтому я не буду останавливаться на нём подробно:
protected override object SaveViewState()
<
object [] currentStates = new object [nodes.Count + 1];
currentStates[0] = base .SaveViewState();
for ( int i = 0; i return ( object )currentStates;
>
protected override void LoadViewState( object savedState)
<
if (savedState != null )
<
object [] currentStates = ( object [])savedState;
Оба метода предполагают, что объекты класса Node умеют себя сохранять (сериализовывать) и восстанавливать (десериализовывать). В простейших случаях, когда речь идёт о сохранении/восстановлении публичных свойств, достаточно пометить класс атрибутом Serializable , и .NET сам сможет выполнить необходимую работу.
Однако в нашем случае этот способ не подходит, поскольку сериализуемым должен быть не только класс Node , но и все его предки. Проблему в данном случае создаёт класс Control , которому мы должны наследовать.
Чтобы её решить, мы должны реализовать в классе Node интерфейс ISerializable , то есть один-единственный метод GetObjectData :
[SecurityPermission(SecurityAction.LinkDemand, Flags =
SecurityPermissionFlag.SerializationFormatter)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
<
info.AddValue( «Url» , url);
info.AddValue( «Title» , title);
info.AddValue( «Description» , description);
info.AddValue( «Level» , level);
info.AddValue( «IsSelected» , isSelected);
>
Кроме того, мы должны добавить в класс защищённый конструктор, который создаёт объект из сохранённых ранее значений:
protected Node(SerializationInfo info, StreamingContext context)
<
url = info.GetString( «Url» );
title = info.GetString( «Title» );
description = info.GetString( «Description» );
level = info.GetInt32( «Level» );
isSelected = info.GetBoolean( «IsSelected» );
>
Узлы дерева мы храним в закрытом поле nodes :
Родительский блок DIV
Компонент NavigationPanel является наследником WebControl , который требует, чтобы содержимое компонента размещалось внутри одного из HTML-тегов. По умолчанию в качестве родительского тега используется SPAN , но нам больше подошёл бы тег DIV . Чтобы этого добиться, достаточно переопределить защищённое свойство TagKey :
При описании компонента NavigationPanel мы должны установить несколько атрибутов, чтобы Visual Studio могла правильно работать с ним в коде страницы:
[assembly:TagPrefix( «Binateq.Web.Controls» , «binateq» )]
namespace Binateq.Web.Controls
<
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level =
AspNetHostingPermissionLevel.Minimal)]
[ParseChildren( true )]
[ToolboxData(
» »
)]
public class NavigationPanel: HierarchicalDataBoundControl
<
…
>
>
На слово binateq не обращайте внимания — я употребляю его при написании своего кода, вы же вполне можете задействовать ваше собственное название.
В свойстве assembly:TagPrefix задаётся префикс для компонентов, который будет использован Visual Studio. Обычно она сама генерирует их (uc1, uc2 и т.д.), но такие названия бессмысленны, поэтому я предпочитаю указать префикс при написании компонента.
Атрибут ParseChildren указывает Visual Studio на то, что теги внутри трактуются как значения свойств, то есть содержимое тега HeaderTemplate будет присвоено свойству NavigationPanel.HeaderTemplate .
Атрибут ToolBoxData подсказывает Visual Studio, что именно нужно вставить в код страницы при добавлении компонента. Вместо <0>будет вставлен префикс, в нашем случае binateq.
Помимо основного файла NavigationPanel.cs вы найдёте там вспомогательный — Utilities.cs , в котором собраны методы для корректного преобразования виртуальных, абсолютных и относительных путей (тот самый метод ToAbsolute , про который я не стал писать в статье).
Можно подключить компонент к панели инструментов Visual Studio. Для этого распахните Toolbox , щёлкните правой кнопкой мыши внутри закладки General и выберите пункт Choose Items . В открывшемся диалоге нажмите кнопку Browse и загрузите Binateq.Controls.dll .
После этого навигационную панель можно будет перетаскивать с панели инструментов в код страницы. Visual Studio автоматически вставит в начало страницы строку , и добавит в проект ссылку (reference) на Binateq.Controls.dll .
При выводе структуры сайта мы можем исключить корневую страницу, установив SiteMapDataSource.ShowStartingNode в false . Мы также можем выводить многоуровневую структуру, воспользовавшись свойством Container.Level :
ItemTemplate >
div class =»level » >
a href =` ` > #Container.Title %> a >
div >
ItemTemplate >
SelectedItemTemplate >
div class =»level » >
#Container.Title %>
div >
SelectedItemTemplate >
Определив в таблице стилей классы div.level1 , div.level2 , div.level3 , мы можем с помощью отступов отразить древовидную структуру сайта. Существующий компонент не умеет ограничивать количество уровней вложенности, и за этим придётся следить самостоятельно. Вы можете внести в код компонента необходимые изменения, добавив свойство, например, MaxLevel , и заменив в коде RecourseDataBind
if (hierarchyData.HasChildren && (maxLevel == 0 // maxLevel > level))
Если MaxLevel будет равен 0, то компонент будет показывать все уровни, а если, например, 5, то только первые 5.
Дипломная работа: Разработка информационной системы для предприятия по установке газового оборудования
Название: Разработка информационной системы для предприятия по установке газового оборудования Раздел: Рефераты по информатике Тип: дипломная работа Добавлен 03:57:03 11 декабря 2010 Похожие работы Просмотров: 3903 Комментариев: 7 Оценило: 4 человек Средний балл: 3.8 Оценка: неизвестно Скачать |