Исследование высоконагруженных компьютерных систем
Понятие высоконагруженных компьютерных систем. Традиционные качества, интерактивность, распределенная система, большое количество пользователей. Распределение задач сервером. Балансировка нагрузки. Исследование высоконагруженных систем Google и Вконтакте.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | дипломная работа |
Язык | русский |
Дата добавления | 11.12.2015 |
Размер файла | 552,9 K |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Размещено на http://allbest.ru
Исследование высоконагруженных компьютерных систем
Обозначения и сокращения
Web 2.0 |
методика проектирования систем, путем учета сетевых взаимодействий |
|
NoSQL |
not only SQL - ряд подходов, направленных на реализацию хранилищ баз данных |
|
БД |
база данных |
|
HTML |
HyperText Markup Language |
|
Nginx |
веб-сервер и почтовый прокси-сервер, работающий на Unix-подобных операционных системах |
|
URL |
Uniform Resource Locator -- единообразный локатор (определитель местонахождения) ресурса |
|
API |
интерфейс создания приложений |
|
GFS |
Google File System - распределенная файловая система |
|
СУБД |
система управления базами данных |
Введение
С каждым годом неуклонно возрастает число пользователей услугами сети Интернет. Высоконагруженными ресурсами, в первую очередь, являются многопользовательские приложения, многие из которых являются распределенными системами, работающими более чем на одном сервере. Такая конфигурация необходима для обеспечения возможности обработки больших объемов данных, возникающих при пиковых нагрузках, а также их репликации. Второй по значимости задачей, описываемой в конфигурации приложения ресурсов, является обеспечение отказоустойчивости системы.
В данной выпускной квалификационной работе мы будем рассматривать высоконагруженные системы, их архитектуру и технологии, с помощью которых они реализуются.
Актуальность данного исследования продиктована возрастающей значимостью информационных технологий в современном мире.
Целью работы является исследование конкретно взятых высоконагруженных систем Google и Вконтакте; изучение их архитектуры, оборудования и платформ на которых они реализованы.
1. Что такое высоконагруженные компьютерные системы
В последнее время все чаще говорят о высоконагруженных приложениях. Нельзя не заметить что это теперь очень популярная, можно даже сказать модная, область знаний.
Итак, что же такое высоконагруженная система? Ответ на этот вопрос стоит начать с описания качественных свойств такого рода систем.
1.1 Традиционные качества высоконагруженной системы
Как правило, к таким качествам относят большое количество пользователей и данных. В целом это правда, но тут есть несколько нюансов:
- это не вся правда;
- приведенные факторы являются количественными, а не качественными.
Ниже на основании предпосылки “много пользователей, много данных” будет сформулирован список качественных факторов присущих высоконагруженным системам. Начнем с самого простого.
1.1.1 Многопользовательская система
Высоконагруженное приложение в первую очередь является многопользовательским. То есть в один момент времени с ним работает более чем один человек. Сейчас, в эру стремительного развития Интернета, это тысячи и сотни тысяч человек.
Устойчивая ассоциация высоконагруженных систем с большим количеством пользователей в нашей индустрии появилась очень давно. Ничего принципиально неверного в такой связи нет. Но если высокая нагрузка подразумевает большое количество пользователей, то большое количество пользователей совсем не обязательно подразумевает высоконагруженную систему.
Если посмотреть на статистику Московского метрополитена за 2010 год, то окажется, что средняя часовая нагрузка на систему максимальна в диапазоне от 8 до 9 часов утра. За этот час через турникеты проходят приблизительно 720 тысяч человек. Что порождает необходимость не менее 200 раз в секунду проверять статус предъявленных проездных и принимать решение о пропуске того или иного человека через турникет. В Интернете существует масса высоконагруженных ресурсов с подобными показателями пропускной способности. Например, статистика по StackOverflow за тот же 2010-й год показывает, что их средняя пропускная способность находится в диапазоне 100-150 хитов в секунду.
Определенно у метрополитена более высокие требования к пропускной способности. Но значит ли это что Московский метрополитен можно считать более “высоконагруженным” чем StackOverflow? Вряд ли, в частности потому, что эти две системы оперируют несравнимыми объемами информации.
В обоих случаях приведена оценка пропускной способности, так как она дает больше информации о нагрузке, чем количество пользователей системы. Две разные системы могут подталкивать пользователей к разным паттернам их использования. Это может приводить к абсолютно разным требованиям по пропускной способности для этих систем. Пропускная способность точнее описывает количество работы, которую должна уметь выполнять система в единицу времени.
1.1.2 Распределенная система
Высоконагруженные системы являются системами распределенными, то есть работают более чем на одном сервере. Зачастую это десятки и сотни серверов. Требование распределенности вытекает из следующих причин:
- необходимости обрабатывать возрастающие объемы данных;
- необходимости “живучести” системы в случаях отказа части серверов.
Большинство высоконагруженных приложений являются Интернет-приложениями. А отличительной особенностью современного Интернета основанного на концепции Web 2.0 является тот факт, что сами пользователи генерируют данные, которые они сами же в итоге и потребляют. Это приводит к тому, что чем больше пользователей, тем больше потенциальный объем хранимых данных.
Требование обработки больших объемов данных может существенно осложнить жизнь. Под “большим объемом” подразумевается такой объем информации, который не может эффективно обработать одна машина. В большинстве случаев, это объем превышающий объем доступной на сервере оперативной памяти. То есть, приходится тем или иным образом распределять данные между несколькими машинами, каждая из которых обрабатывает свой небольшой кусочек данных, но делает это эффективно, без ошибок страниц и прочих неприятностей. Необходимость эффективной обработки данных диктуется другим очень важным качеством высоконагруженных систем, - интерактивностью.
Но большие объемы данных - это не все. Система должна работать в режиме 24x7 без остановок и перерывов. Но, любое даже самое надежное оборудование иногда выходит из строя. Встает естественная задача обеспечения доступности системы в случаях отказа оборудования.
Тут вступает область знания распределенных систем, эксплуатация которых редко бывает безоблачной, даже при использовании готовых решений. Тем не менее, распределенные системы, не смотря на сложность их разработки и поддержки единственный подход позволяющий обеспечить вышеизложенные требования в полной мере.
1.1.3 Позитивная динамика по пользователям и данным
Если приложение представляет хоть какой-то интерес, то даже если ничего не делать, аудитория будет расти просто с ростом аудитории Интернета. Поэтому характерной чертой высоконагруженных систем является не просто большое количество пользователей, но и позитивная динамика количества пользователей.
В контексте реалий Web 2.0 растущее количество пользователей может привести к тому, что такая же позитивная динамика будет иметь место и по данным. Поэтому в контексте высоконагруженных систем корректней говорить не о большом, а о растущем количестве пользователей и данных.
1.1.4 Интерактивность
Интерактивность - одно из основополагающих качеств высоконагруженной системы. Интерактивность подразумевает, что пользователь после того как послал запрос сидит и ждет ответа, а люди ждать не любят. А это значит, что эти ответы должны занимать минимум времени. То есть отвечать за приемлемое время. Это контрастирует, например, с системами пакетной обработки данных, в которых время ответа системы вторично.
1.1.5 Управление ресурсами
Качество интерактивности очень важно для понимания сути высоконагруженных приложений, потому что по вышеизложенным причинам разрабатывая такую систему нужно быть уверенным в том, что каждый раз, когда приложение получает очередной запрос, у него должно быть достаточно свободных ресурсов (CPU, память, диск, сеть) для обработки запроса за приемлемое время.
Контроль над ресурсами является неотъемлемой частью работы с высоконагруженным проектом.
Необходимо также учитывать тот факт, что из-за позитивной динамики свободных ресурсов становится меньше с течением времени. В этом заключается “парадокс” высоконагруженных систем.
Высоконагруженная система - это интерактивная распределенная система, которая требует постоянного контроля над собственными ресурсами. В противном случае она перестает работать.
Причина в том, что со временем системе просто перестанет хватать ресурсов. А нехватка ресурсов провоцируется факторами, часть из которых уже были рассмотрены, а именно:
- рост аудитории;
- рост объема данных;
- изменения паттернов поведения аудитории, в том числе и сезонные.
В основном темы докладов на конференциях посвященных тематике разработки высоконагруженных систем можно свести к двум основополагающим направлениям:
- как решать существующие задачи, используя меньше ресурсов (практически все NoSQL БД, оптимизация);
- как имея больше ресурсов решать пропорционально больший объем задач (message queueing, распределенные вычисления, распределенные БД, параллелизм).
1.2 Высоконагруженный проект - это интернет приложение
Если исходить из того что неотъемлемой частью высоконагруженного проекта является постоянный рост данных и аудитории, то становится понятно почему высоконагруженные проекты - это поголовно Интернет приложения.
В реальной жизни всегда есть некий предел, который не позволяет использовать систему все возрастающему количеству людей. В том же метрополитене человеку требуется некоторое время, чтобы пройти через турникет. Исходя из этого времени, а также общего количества турникетов можно достаточно точно рассчитать верхний предел возможной нагрузки. Ведь это невозможно, чтобы за секунду через один турникет могло пройти 10 человек.
В сфере High Performance Computations приложения могут выполнять просто гигантское количество операций в единицу времени. Больше чем любое Интернет-приложение. Но это количество зависит только от объема входных данных, а также алгоритма обработки (например, от точности моделирования, если речь идет о моделировании динамических систем). Тяжело придумать причину, почему нагрузка на такую систему может вырасти сама по себе без вмешательства персонала ее сопровождающего.
Интернет-приложения это единственная сфера, в которой нагрузка есть переменная, не имеющая верхнего предела.
В контексте высокой нагрузки список первостепенных задач, которые стоят перед разработчиками:
- создание инфраструктуры предоставляющей качественную обратную связь по утилизации аппаратных ресурсов системы (мониторинг, self-аудит приложений);
- определение звеньев системы нуждающихся в масштабировании в ближайшем времени и выбор стратегии масштабирования этих звеньев;
- определение toolbox'а позволяющего решать типовые задачи эффективно по аппаратным ресурсам (базы данных, очереди сообщений, языки программирования, библиотеки и т.д.).
2. Три кита HightLoad
2.1 Отложенные вычисления
Отложенные вычисления или ленивые вычисления -- концепция, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.
Отложенные вычисления позволяют сократить общий объём вычислений за счёт тех вычислений, результаты которых не будут использованы. Программист может просто описывать зависимости функций друг от друга и не следить за тем, чтобы не осуществлялось «лишних вычислений».
В общем случае, отложенные вычисления осуществляются по принципу:
if (!res)
{
res = someCalculation();
}
Одной из разновидностей предварительных вычислений является кэширование.
2.1.1 Кэширование и memcached
Кэширование - организация вычислений с использованием промежуточного буфера с быстрым доступом, содержащим информацию, которая может быть запрошена с наибольшей вероятностью.
Кэширование сегодня является неотъемлемой частью любого Web-проекта, не обязательно высоконагруженного. Для каждого ресурса критичной для пользователя является такая характеристика, как время отклика сервера. Увеличение времени отклика сервера приводит к оттоку посетителей. Следовательно, необходимо минимизировать время отклика: для этого необходимо уменьшать время, требуемое на формирование ответа пользователю, а ответ пользователю требует получить данные из каких-то внешних ресурсов (backend). Этими ресурсами могут быть как базы данных, так и любые другие относительно медленные источники данных (например, удаленный файловый сервер, на котором мы уточняем количество свободного места). Для генерации одной страницы достаточно сложного ресурса нам может потребоваться совершить десятки подобных обращений. Многие из них будут быстрыми: 20 миллисекунд и меньше, однако всегда существует некоторое небольшое количество запросов, время вычисления которых может исчисляться секундами или минутами (даже в самой оптимизированной системе они могут быть, хотя их количество должно быть минимально). Если сложить всё время, которое мы затратим на ожидание результатов запросов (если же мы будем выполнять запросы параллельно, то возьмем время вычисления самого долгого запроса), мы получим неудовлетворительное время отклика.
Решением этой задачи является кэширование: мы помещаем результат вычислений в некоторое хранилище (например, memcached), которое обладает отличными характеристиками по времени доступа к информации. Теперь вместо обращений к медленным, сложным и тяжелым backend'ам нам достаточно выполнить запрос к быстрому кэшу.
2.1.2 Принцип локальности
Чем же определяется такой успех кэширования? Ответ лежит в принципе локальности: программе, устройству свойственно в определенный промежуток времени работать с некоторым подмножеством данных из общего набора. В случае оперативной памяти это означает, что если программа работает с данными, находящимися по адресу 100, то с большей степенью вероятности следующее обращение будет по адресу 101, 102 и т.п., а не по адресу 10000, например. То же самое с жестким диском: его буфер наполняется данными из областей, соседних по отношению к последним прочитанным секторам, если бы наши программы работали в один момент времени не с некоторым относительно небольшим набором файлов, а со всем содержимым жесткого диска, буферы были бы бессмысленны. Буфер автомагнитолы совершает упреждающее чтение с диска следующих минут музыки, потому что мы, скорее всего, будем слушать музыкальный файл последовательно, чем перескакивать по набору музыки и т.п.
В случае web-проектов успех кэширования определяется тем, что на сайте есть всегда наиболее популярные страницы, некоторые данные используются на всех или почти на всех страницах, то есть существуют некоторые выборки, которые оказываются затребованы гораздо чаще других. Мы заменяем несколько обращений к backend'у на одно обращения для построения кэша, а затем все последующие обращения будет делать через быстро работающий кэш.
Кэш всегда лучше, чем исходный источник данных: кэш ЦП (центральный процессор) на порядки быстрее оперативной памяти, однако мы не можем сделать оперативную память такой же быстрой, как кэш - это экономически неэффективно и технически сложно. Буфер жесткого диска удовлетворяет запросы за данными на порядки быстрее самого жесткого диска, однако буфер не обладает свойством запоминать данные при отключении питания - в этом смысле он хуже самого устройства. Аналогичная ситуация и с кэшированием в Web'е: кэш быстрее и эффективнее, чем backend, однако он обычно в случае перезапуска или падения сервера не может сохранить данные, а также не обладает логикой по вычислению каких-либо результатов: он умеет возвращать лишь то, что мы ранее в него положили.
2.1.3 Memcached
Memcached представляет собой огромную хэш-таблицу в оперативной памяти, доступную по сетевому протоколу. Он обеспечивает сервис по хранению значений, ассоциированных с ключами. Доступ к хэшу мы получаем через простой сетевой протокол, клиентом может выступать программа, написанная на произвольном языке программирования (существуют клиенты для C/C++, PHP, Perl, Java и т.п.).
Самые простые операции - получить значение указанного ключа (get), установить значение ключа (set) и удалить ключ (del). Для реализации цепочки атомарных операций (при условии конкурентного доступа к memcached со стороны параллельных процессов) используются дополнительные операции: инкремент/декремент значения ключа (incr/decr), дописать данные к значению ключа в начало или в конец (append/prepend), атомарная связка получения/установки значения (gets/cas) и другие.
Memcached был реализован Брэдом Фитцпатриком (Brad Fitzpatrick) в рамках работы над проектом ЖЖ (LiveJournal). Он использовался для разгрузки базы данных от запросов при отдаче контента страниц. Сегодня memcached нашел своё применение в ядре многих крупных проектов, например, Wikipedia, YouTube, Facebook и другие.
2.1.4 Общая схема кэширования
В общем случае схема кэширования выглядит следующим образом: frontend'у (той части проекта, которая формирует ответ пользователю) требуется получить данные какой-то выборки. Frontend обращается к быстрому как гепард серверу memcached за кэшом выборки (get-запрос) (рисунок 1). Если соответствующий ключ будет обнаружен, работа на этом заканчивается. В противном случае следует обращение к тяжелому, неповоротливому, но мощному (как слон) backend'у, в роли которого чаще всего выступает база данных. Полученный результат сразу же записывается в memcached в качестве кэша (set-запрос). При этом обычно для ключа задается максимальное время жизни (срок годности), который соответствует моменту сброса кэша.
Рисунок 1 - Общая схема кэширования
Такая стандартная схема кэширования реализуется всегда. Вместо memcached в некоторых проектах могут использоваться локальные файлы, иные способы хранения (другая БД, кэш PHP-акселератора и т.п.).
2.1.5 Архитектура memcached
Каким же образом устроен memcached? Как ему удаётся работать настолько быстро, что даже десятки запросов к memcached, необходимых для обработки одной страницы сайта, не приводят к существенной задержке. При этом memcached крайне нетребователен к вычислительным ресурсам: на нагруженной инсталляции процессорное время, использованное им, редко превышает 10%.
Во-первых, memcached спроектирован так, чтобы все его операции имели алгоритмическую сложность O(1), т.е. время выполнения любой операции не зависит от количества ключей, которые хранит memcached. Это означает, что некоторые операции (или возможности) будут отсутствовать в нём, если их реализация требует всего лишь линейного (O(n)) времени. Так, в memcached отсутствуют возможность объединения ключей «в папки», т.е. какой-либо группировки ключей, также мы не найдем групповых операций над ключами или их значениями.
Основными оптимизированными операциями является выделение/освобождение блоков памяти под хранение ключей, определение политики самых неиспользуемых ключей (LRU) для очистки кэша при нехватке памяти. Поиск ключей происходит через хэширование, поэтому имеет сложность O(1).
Используется асинхронный ввод-вывод, не используются нити, что обеспечивает дополнительный прирост производительности и меньшие требования к ресурсам. На самом деле memcached может использовать нити, но это необходимо лишь для использования всех доступных на сервере ядер или процессоров в случае слишком большой нагрузки - на каждое соединение нить не создается в любом случае.
По сути, можно сказать, что время отклика сервера memcached определяется только сетевыми издержками и практически равно времени передачи пакета от frontend'а до сервера memcached (RTT). Такие характеристики позволяют использовать memcached в высоконагруженных web-проектах для решения различных задач, в том числе и для кэширования данных.
2.1.6 Потеря ключей
Memcached не является надежным хранилищем - возможна ситуация, когда ключ будет удален из кэша раньше окончания его срока жизни. Архитектура проекта должна быть готова к такой ситуации и должна гибко реагировать на потерю ключей. Можно выделить три основных причины потери ключей:
- ключ был удален раньше окончания его срока годности в силу нехватки памяти под хранение значений других ключей. Memcached использует политику LRU, поэтому такая потеря означает, что данный ключ редко использовался и память кэша освобождается для хранения более популярных ключей;
- ключ был удален, так как истекло его время жизни. Такая ситуация строго говоря не является потерей, так как мы сами ограничили время жизни ключа, но для клиентского по отношению к memcached кода такая потеря неотличима от других случаев - при обращении к memcached мы получаем ответ «такого ключа нет».
- самой неприятной ситуацией является крах процесса memcached или сервера, на котором он расположен. В этой ситуации мы теряем все ключи, которые хранились в кэше. Несколько сгладить последствия позволяет кластерная организация: множество серверов memcached, по которым «размазаны» ключи проекта: так последствия краха одного кэша будут менее заметны.
Можно разделить данные, которые мы храним в memcached, по степени критичности их потери.
«Можно потерять».
К этой категории относятся кэши выборок из базы данных. Потеря таких ключей не так страшна, потому что мы можем легко восстановить их значения, обратившись заново к backend'у. Однако частые потери кэшей приводят к излишним обращениям к БД.
«Не хотелось бы потерять».
Здесь можно упомянуть счетчики посетителей сайта, просмотров ресурсов и т.п. Хоть и восстановить эти значения иногда напрямую невозможно, но значения этих ключей имеют ограниченный по времени смысл: через несколько минут их значение уже неактуально, и будет рассчитано новое значение.
«Совсем не должны терять».
Memcached удобен для хранения сессий пользователей - все сессии равнодоступны со всех серверов, входящих в кластер frontend'ов. Так вот содержимое сессий не хотелось бы терять никогда - иначе пользователей на сайте будет «разлогинивать». Как попытаться избежать? Можно дублировать ключи сессий на нескольких серверах memcached из кластера, так вероятность потери снижается.
Если нам нужна WEB-страница, которая формируется несколько сот миллисекунд или даже секунд, то можно ее сформировать один раз, запомнить в каком-то буфере, и потом отдавать по мере необходимости.
Существует много разных вариантов формирования WEB-страниц с использованием кэширования: кэширование HTML, кэширование результатов запросов из БД, кэширование HTML блоков, кэширование опкода, кэширование промежуточных результатов и т.д.
Кэширование осуществляется на разных уровнях, начиная от сохранения в кэше браузера HTML страниц, логотипов и картинок, сохранения HTML-страниц и статического контента (картинки) в прокси-серверах и кэше WEB-сервера.
Кэширование результатов трансляции опкода специальными кэшерами и акселераторами. Применительно к РНР: АРС, XCache, eAccelerator, ZendAccelerator и т.д.
Кэширование на уровне WEB-приложения осуществляется либо с использованием файловой системы, БД или специальными кэширующими демонами.
2.2 Предварительные вычисления
Если вычисления трудоемкие, такие как расчет статистики или подсчет топов/голосов/рейтингов, но нет необходимости ждать первого запроса, чтоб осуществлять вычисления, если есть возможность их подготовить заранее. Незачем тратить столь дорогое процессорное время нагруженного фронт-сервера. Такие, расчетные задачи выполняются по расписанию, cron, или через "сервера задач", на удаленных компьютерах.
Как правило, первое сочетается со вторым. Первоначально рассчитывается некоторая задача, например 10 лучших товаров/блогов/фотографий, формируется набор данных или уже готовый HTML и кладется в кэш.
Далее, фронтэнд, по мере необходимости выбирает нужную информацию из кэша, формирует HTML страницу и отдает запрашиваемому браузеру. В результате имеем 5-10 кратное повышение производительности.
2.2.1 А как же это всё использовать
Вся суть HighLoad проектов сводится к принципу "Быстро взял - быстро отдал". Как минимизировать скорость отдачи страницы. Логика отдачи такова:
- часть данных выносим в отдельную логику, подготавливать их отдельными скриптами или демонами. Отдавать их отдельными AJAX запросами, а HTML формировать на стороне клиента (браузера). Тем самым мы уменьшаем время на формирование и передачу выходных данных. Такими данными могут быть счетчики сообщений, просмотров, "Что нового у друзей" и так далее;
- минимизировать обращение к БД.
2.2.1.1 Кэшировать запросы к БД
Например, стоит задача постраничной навигации по списку некоторой выборки: определенной категории товаров или объектов. Классическое решение - это использование в операторе SELECT (на примере MySQL) ключевых слов LIMIT/OFFSET. Для первой страницы выдачи значение LIMIT равно кол-во записей выдачи, как правило, это 10, 20 или 50, а OFFSET равно 0. Для последующих страниц, OFFSET равно (Np -1)* Psize, где Np - номер страницы, Psize = значению LIMIT - размер выдачи на одной странице.
Однако если выбрать весь массив данных и сохранить его в кэше, а затем брать результаты выборки из кэша и делать операции выборки по страницам или сортировки по каким-то критериям уже на клиенте, то значительно сократиться нагрузка на БД.
Необходимо заметить, что если запрос выборки непомерно велик, то можно пользоваться усредненной стратегией: выбирать по средним наборам записей, где-то до 1000 записей.
2.2.1.2 Виды кэширования
Выделяют следующие виды кэширования:
- данных;
- HTML блоков;
- страниц целиком.
2.2.1.3 Кэширование данных
Обычно хранят сериализованный массив данных. Ключом может быть:
- элемент данных запроса в ключевом слове WHERE, например id, наименование категории и прочие. Обычно с префиксом: catid_12321;
- значение хеша (md5) от всего текста запроса.
Общий код будет выглядеть так:
$data = cache->get($key);
id (!$data)
{
$data = db->query($sql);
cache->set($key, $data)
}
2.2.1.4 Кэширование блоков
Многие современные фреймворки осуществляют кэширование блоками. Блоком является некий блок вывода, который рассчитывается отдельным бэкенд-контроллером.
Однако можно использовать для кэширования средства ssi (server side include -- включение на стороне сервера). В частности в nginx используется следующий подход:
- общий HTML собирается как ssi;
- каждый блок вызывает не тяжелый php скрипт, который формирует HTML;
- после отработки скрипт кладется c некоторым ключом в memcache;
- при обращении на внутренний url, который указан в шаблоне-сборщике (ssi шаблоне), идет обращение к модулю ngx_http_memcached. Если данных там нет, HTTP ERROR 404, то происходит переориентирование на внутренний url, который вызывает необходимый PHP скрипт.
2.2.1.5 Кэширование страниц
Можно осуществить кэширование страницы, по принципу кэширования данных, но уже кэшировать результирующий HTML, который формируется бэкендом. Однако, производительней если кэширование будет осуществлять WEB сервер. Обычно кэшируют домашнюю страницу (index.html) и статические наиболее часто запрашиваемые страницы.
2.3 Распараллеливание задач
Отдача контента в HighLoad проектах сводится к задаче "как можно быстрее отдать". Как этого достичь:
- быстро изъять или рассчитать необходимые данные;
- быстро сформировать HTML;
- как можно быстрее отдать, полученный HTML.
Как можно быстро собрать данные - это либо изъять уже готовые данные (кэширование). Либо раскидать поиск данных по нескольким машинам, если многочисленные данные у нас распределены по нескольким серверам.
2.3.1 Поиск MapReduce
Для поиска в распределенных хранилищах данных используется технология от Google для обработки больших массивов данных MapReduce.
Алгоритм следующий:
- каждый узел в системе считывает свою часть данных и образует из них пары «ключ-значение»;
- сформированные пары преобразуются по определенному алгоритму в новые пары «ключ-значение» в соответствии от месторасположения (стадия Map);
- эти новые пары группируются по ключу, и для каждого ключа вычисляется новое значение на основе группы промежуточных значений (стадия Reduce).
Результат стадии Reduce обычно и является выходной информацией.
MapReduce по сути лишь частный случай обработки потоков данных. В общем случае процесс обработки потоков данных может иметь любое количество этапов, преобразований и сортировок данных, необходимых для получения результата.
2.4 Фоновые вычисления
Отдача и формирование динамического контента - это задача, требующая относительно большого количества процессорного времени. Если в проекте есть задачи, например расчетные или по обработке графики, то их желательно отдать на другие сервера. Обычно это делается через специальные сервера задач или очереди. Например, задачу загрузки медиа контента можно поручить отдельному серверу и не задействовать фронтэнд, используя специальные модули nginx для загрузки файлов: ngx_http_upload_module, ngx_http_upload_progress_module.
2.4.1 Сервера задач
Наиболее известным продуктом по распределению задач является Gearman.
Был разработан компанией Danga Interactive, та же которая разработала и memcached. Название является анаграммой к слову manager, и именно роль менеджера и является кратким описанием функциональности данного приложения -- управление, контроль и распределение различных задач.
Первоначально gearman был реализован на Perl, но со временем для повышения производительности был переписан на C, с использованием библиотеки libevent, наличие которой является необходимой для функционирования основной части -- сервера задач.
Приложение, использующее Gearman в своей работе, работает с 3 основными компонентами:
- сервер задач -- является центральной частью Gearman, именно сюда будут приходить задачи и результаты от клиентов и исполнителей, и именно отсюда будут высылаться задания и результаты, исполнителям и клиентам соответственно;
- исполнитель -- является основной частью, где необходима реализация какой-либо функциональности. Исполнитель принимает и пытается выполнить задание от сервера;
- клиент -- также имплементируется отдельно, создаёт и отсылает задачу на сервер и, в некоторых случаях, получает результат.
Клиент и исполнитель используют Gearman API для вызова и реализации необходимых функций (рисунок 2).
Рисунок 2 - Схема, иллюстрирующая работу Gearman
Рассмотрим классический пример работы такой системы -- пользователь закачал фотографию на сайт, и хочет показать её всем своим друзьям. Необходимо быстро подготовить различные размеры данной фотографии, для использования по всему сайту. Рассмотрим, как эту задачу можно решить с использованием Gearman -- при получении фотографии, мы создаём задачу для gearman сервера, в которую входит необходимая информация о фотографии и отправляем её на выполнение. Сервер задач выбирает (round robin) свободного исполнителя (исполнителей может быть несколько) и отсылает задачу на выполнение. Исполнитель получает всю необходимую информацию, обрабатывает её и отправляет ответ об успешно выполненной задаче и, если задача была синхронная, результат работы. Задачи бывают двух типов -- синхронные, когда клиент ждёт выполнения отправленной задачи, и соответственно результат, и асинхронные, когда клиент выступает только в роли инициатора задачи. Теперь добавляем сюда факт, что протокол общения между частями gearman приложения -- TCP/IP, то есть подразумевается, что каждая часть (сервер, клиент, исполнитель) может находится на отдельной машине. Серверов и клиентов может быть несколько. Реализация клиента и исполнителя не зависят друг от друга (доступные языки в которых имплементирован API для работы с gearman включают в себя perl, php, python, java, C, MySQL UDF и др.).
Проект активно развивается -- новые версии с исправлением ошибок и улучшениями выходят каждый месяц. Спектр решаемых задач ограничен только фантазией и опытом разработчика. Для примера, несколько общих задач, которые успешно решались и решаются с использованием gearman:
- вышеописанная задача с обработкой фотографии, причём не стоит ограничиваться только генерацией различных размеров;
- асинхронная инвалидация/обновление данных в кэше, без использования крона и задержек со стороны клиента;
- архивирование больших объёмов данных;
- работа с данными в формате -- (ключ => значение), с использованием шифрования на стороне исполнителя.
Основным объектом работы в gearman является задача -- task, которая состоит из типа (синхронный, асинхронный), названия задачи (к примеру resize), и параметров -- workload. Каждая задача идентифицируется по паре название/параметры. То есть если на сервер поступит две одинаковые задачи с одинаковыми параметрами, выполнена будет только первая, вторую сервер откинет. Поэтому всегда стоит помнить -- для стопроцентного выполнения конкретной задачи необходимо добавлять к параметрам уникальное значение.
Сервер является процессом-демоном. Имеет достаточно много конфигурационных параметров наряду со стандартными для таких приложений настройками user-port-interface, имеется также несколько специфичных опций. Количество попыток выполнения задачи настраивается параметром -j, --job-retries=. Для использования нескольких потоков существует параметр -t, --threads=. Сервер имеет возможность сохранять очередь задач в какое-либо хранилище, чтобы при рестарте была возможность восстановить весь процесс без потери данных. Хранилищем может выступать MySQL/drizzle, memcached, PostgreSQL или sqllite. Более чем интересной является опция использования сторонних протоколов в процессе коммуникации клиент -> сервер задач.
Единственным таким, имплементированным на данный момент, протоколом является HTTP. При помощи этой опции, можно настроить gearman сервер на приём задач по определённому порту через HTTP запрос (requested url транслируется в task name, http body в workload, http headers соответственно в task type), то есть реализация клиента сводится к реализации простого HTTP клиента на вызывающей стороне. Никаких ограничений на тип/размер задачи это не накладывает. Используя HTTP протокол и Gearman построение эффективно сбалансированных REST сервисов может стать как никогда простой задачей.
Создавать задачи для gearman можно практически из любого языка программирования, так как обёртки на вызовы функций API написаны для большинства сред.
Исполняющая часть соответственно находится в так называемом исполнителе. Здесь выбор средств реализации также очень обширен, начиная от стандартных php, python, java и заканчивая пользовательскими функциями в mysql. Единственное условие -- наличие поддержки gearman библиотеки, что может потребовать дополнительной компиляции каких-либо модулей. Так называемый исполнитель (worker) является background процессом, после запуска регистрирует все задачи, которые он может выполнить и, находясь в памяти, ждёт поступления задач от сервера. Количество серверов, с которыми держит связь конкретный исполнитель -- не ограничено.
Так же есть несколько недостатков. Так как исполнитель является долгоживущим процессом, то любое изменение его функциональности подразумевает под собой процесс перезапуска самого исполнителя, это же касается и добавления/удаления задач, которые может выполнить данный исполнитель. То есть, если нужно добавить новую функцию, или изменить уже существующую, нужно перезапустить все процессы исполнителей, которые связанны с выполнением данной задачей. Этот процесс, в случае, когда исполнители разбросаны по разным серверам, в разном количестве, не является тривиальным. Да и не все языки предназначены для написания эффективных процессов-демонов.
Распределение задач сервером происходит только по одному единственному алгоритму -- round robin. Существуют задачи с двумя уровнями приоритета -- высокий и низкий. Но более точный контроль над выполнением задач недоступен. Более того, нельзя точно узнать, на каком из исполнителей будет выполнена задача, и также невозможно указать напрямую, которому узлу её послать, что сильно усложняет процесс отладки.
Из описанных выше недостатков следует основной -- отсутствие вменяемых средств управление реализованной системой. Всё приходится делать вручную, и всё время держать в голове структуру gearman приложения.
2.4.2 Сервера очередей
Сервера задач ориентированы на задачи, а сервера очередей в большей степени ориентированы на сообщения. Однако, использование серверов очередей, помогают разгрузить фронтэнд. На работу бэкенда мы можем подключать столько фоновых задач, сколько нам необходимо, чтобы разгрузить фронтэнд.
Какие задачи могут решать сервера очередей:
- обмен сообщений между клиентами;
- новостная лента, всякого рода подписки;
- асинхронная обработка фоновых "тяжелых" задач;
- обработка медиа контента: фото и видео;
- почтовая рассылка;
- взаимодействие между приложениями.
Протоколы:
- AMQP - Advanced Message Queue Protocol;
- STOMP - Simple (or Streaming) Text Oriented Message Protocol;
- XMPP - Extensible Messaging and Presence Protocol.
2.4.2.1 Сервера очередей software
Простой сервер очередей MemcachedQ, использует memcached протокол get/set, реализован как надстройка над memcached. Простой, эффективный и не требует дополнительных клиентов и библиотек.
ZMQ - (так же ШMQ, ZeroMQ, 0MQ или ZMQ) высокопроизводительный сервер, реализующий протокол AMQP 1.0.
RabbitMQ, ActiveMQ - сервера очередей, реализуют протокол AMQP.
RestMQ - REST надстройка над redis, реализация redis с версии 2.0 имеет возможность работать с подписками.
Очереди в redis реализовались, как работа со списками: писалось (клалось в очередь) в начало списка LPUSH, а читалось с конца спичка (бралось из очереди) RPOP.
2.5 Балансировка нагрузки
Балансировка (выравнивание) нагрузки - распределение процесса выполнения заданий между несколькими серверами сети с целью оптимизации использования ресурсов и сокращения времени вычисления.
Производительность Web узла в целом увеличиться, если увеличить количество фронт серверов, с размещением на узле "зеркальных" копий WEB серверов (горизонтальное масштабирование) (рисунок 3). Распределяя общую нагрузку по всем компонентам системы, в итоге сокращается общее время обработки информации, обрабатывая ее на одном из серверов. Кроме увеличения мощности, горизонтальное масштабирование добавляет надежности системе - при выходе из строя одного из серверов, нагрузка будет сбалансирована между работающими и приложение будет жить.
Рисунок 3 - Балансировка WEB-сервера
Типы балансировок:
- WEB-сервера;
- кластер серверов БД;
- кластер кэшей.
Все запросы проходят через балансировщик, который определяет кому из серверов отдать на обработку. О его настройке и пойдет речь.
При получении запроса от клиента, балансировщику нужно определить, какому из WEB-серверов переслать запрос. Алгоритм принятия решения называется методом или стратегией балансировки, наиболее распространенные стратегии:
- Round robin. Из доступных серверов строится очередь и балансировщик выбирает первый в очереди. После выполнения запроса сервер перемещается в конец очереди;
- меньшее количество соединений. Балансировщик ведет учет количества незакрытых соединений и выбирает тот сервер, у которого таких соединений меньше;
- использование "веса" серверов. Каждому серверу в зависимости от мощности присваивается вес, который используется для ранжирования.
Очевидно, что стратегия, не включающая проверку состояния серверов или хотя бы работоспособности, не пригодна для использования, так как не гарантирует обработку запроса. Поэтому алгоритм должен уметь проверять боеспособность сервера, его загруженность и выбирать наиболее способный.
При балансировке часто возникает проблема хранения сессий, ведь сессия доступна только на создавшем ее сервере, что нужно учитывать в алгоритме перенаправления запроса. Или же хранить сессии на отдельном сервере либо в БД (рисунок 4).
Рисунок 4 - Тривиальная схема балансировщика нагрузки двух WEB-серверов
Для реализации программного балансировщика есть много решений, например:
- Nginx;
- Apache + mod_proxy_balancer;
- Прокси сервер.
2.5.1 Балансировка серверов БД
Балансировка серверов БД, осуществляется специальными прокси серверами. Для MySQL - это MySQLProxy. Для PostgreSQL это сочетание PgProxy и PgBouncer. Один является пулом соединений, а второй балансировщиком нагрузки.
2.5.2 Балансировка КЭШей
Такой продукт, как memcached и redis имеют встроенные средства балансировки.
Если для балансировки WEB запросов и запросов к БД используется алгоритм round-robin.
То для балансировки кэшей используется алгоритм ketama, суть которого в том, чтобы закрепить ключи за серверами и при добавлении нового сервера в кластер кэшей не нарушить распределение ключей по серверам кластера. Например, если номер сервера будет выбираться хаотично (хоть и постоянно при одинаковом количестве серверов memcached), то при добавлении нового сервера ключи изменят свое положение на серверах и закэшированные данные будут потеряны.
Используя ketama можно безболезненно добавлять новые серверы memcached, старые ключи будут лежать на старых серверах. Доступ к данным будет сохранен.
3. Исследование конкретной высоконагруженной системы
3.1 Google
3.1.1 Статистика
Общее:
- ежедневная аудитория Google составляет около 1 миллиарда человек;
- по данным Alexa (Alexa Internet -- дочерняя компания Amazon.com, известная своим сайтом, где собирается статистика о посещаемости других сайтов) больше половины аудитории интернета каждый день пользуются Google;
- по данным IWS аудитория интернета составляет 2.1 миллиарда человек;
- используется более 900 тысяч серверов;
- планируется расширение до 10 миллионов серверов в обозримом будущем;
- 12 основных датацентров в США, присутствие в большом количестве точек по всему миру (более 38);
- около 32 тысяч сотрудников в 76 офисах по всему миру.
Поиск:
- за последние 14 лет среднее время обработки одного поискового запроса уменьшилось с 3 секунд до менее 100 миллисекунд, то есть в 30 раз;
- более 40 миллиардов страниц в индексе, если приравнять каждую к листу А4 они бы покрыли территорию США в 5 слоев;
- более 1 квинтиллиона уникальных URL (10 в 18 степени); если распечатать их в одну строку, её длина составит 51 миллион километров, треть расстояния от Земли до Солнца;
- в интернете встречается примерно 100 квинтиллионов слов, чтобы набрать их на клавиатуре одному человеку потребовалось бы примерно 5 миллионов лет;
- проиндексировано более 1.5 миллиардов изображений, чтобы их сохранить потребовалось бы 112 миллионов дискет, которые можно сложить в стопку высотой 391 километр.
Gmail:
- активных пользователей более 170 миллионов;
- второй по популярности почтовый сервис в США, третий в мире (по данным comScore);
- при текущем темпе роста аудитории GMail и конкурентов, он станет лидером рынка через 2-3 года.
Google+:
- более 110 миллионов пользователей на март 2015, при запуске в июне 2011;
- 25 миллионов пользователей за первый месяц;
- 70:30 примерное соотношение мужчин и женщин;
- себестоимость разработки больше полумиллиарда долларов.
YouTube:
- загружается более 13 миллионов часов видео в год;
- каждую минуту загружается 48 часов видео, что соответствует почти 8 годам контента или 52 тысячам полнометражных фильмов в день;
- более 700 миллиардов просмотров видео в год;
- месячная аудитория составляет 800 миллионов уникальных посетителей;
- несколько тысяч полнометражных фильмов в YouTube Movies;
- более 10% всех видео в формате HD;
- 13% просмотров (400 миллионов в день) происходит с мобильных устройств;
- до сих пор работает в убыток, лишь 14% просмотров видео приносят выручку с рекламы.
Финансы:
- выручка порядка 36 миллиардов долларов в год;
- прибыль после налогов порядка 10 миллиардов долларов в год;
- капитализация порядка 200 миллиардов долларов.
3.1.2 Архитектура
Google - огромная интернет-компания, неоспоримый лидер на рынке поиска в Интернет и владелец большого количества продуктов, многие из которых также добились определенного успеха в своей нише.
В отличие от большинства интернет-компаний, которые занимаются лишь одним продуктом (проектом), архитектура Google не может быть представлена как единое конкретное техническое решение. Сегодня мы скорее будем рассматривать общую стратегию технической реализации интернет-проектов в Google, возможно слегка затрагивая и другие аспекты ведения бизнеса в Интернет.
Все продукты Google основываются на постоянно развивающейся программной платформе, которая спроектирована с учетом работы на миллионах серверов, находящихся в разных датацентрах по всему миру.
3.1.3 Оборудование
Обеспечение работы миллиона серверов и расширение их парка - одна из ключевых статей расходов Google. Для минимизации этих издержек очень большое внимание уделяется эффективности используемого серверного, сетевого и инфраструктурного оборудования.
В традиционных датацентрах потребление электричества серверами примерно равно его потреблению остальной инфраструктурой, Google же удалось снизить процент использования дополнительной электроэнергии до 14% (рисунок 5).
Рисунок 5 - Использование электроэнергии Google
Таким образом, суммарное энергопотребление датацентром Google сравнимо с потреблением только серверов в типичном датацентре и вдвое меньше его общего энергопотребления. Основные концепции, которые используются для достижения этого результата:
- точное измерение потребления электроэнергии всеми компонентами позволяет определить возможности по его уменьшению;
- в датацентрах Google тепло, что позволяет экономить на охлаждении;
- при проектировании датацентра уделяется внимание даже незначительным деталям, позволяющим сэкономить даже немного - при таком масштабе это окупается;
- Google умеет охлаждать датацентры практически без кондиционеров, с использованием воды.
В Google активно пропагандируют максимальное использование возобновляемой энергии. Для этого заключаются долгосрочные соглашения с её поставщиками (на 20 и более лет), что позволяет отрасли активно развиваться и наращивать мощности. Проекты по генерации возобновляемой энергии, спонсируемые Google, имеют суммарную мощность более 1.7 гигаватт, что существенно больше, чем используется для работы Google. Этой мощности хватило бы для обеспечения электричеством 350 тысяч домов.
Если говорить о жизненном цикле оборудования, то используются следующие принципы:
- уменьшение транспортировки: там, где это возможно, тяжелые компоненты (вроде серверных стоек) закупаются у местных поставщиков, даже если в других местах аналогичный товар можно было бы купить дешевле;
- повторное использование: прежде, чем покупать новое оборудование и материалы, рассматриваются возможности по использованию уже имеющихся. Этот принцип помог избежать покупки более 90 тысяч новых серверов;
- утилизация: в тех случаях, когда повторное использование невозможно, оборудование полностью очищается от данных и продается на вторичном рынке. То, что не удается продать, разбирается на материалы (медь, сталь, алюминий, пластик и т.п.) для последующей правильной утилизации специализированными компаниями.
Google известны за свои эксперименты и необычные решения в области серверного оборудования и инфраструктуры. Некоторые запатентованы; какие-то прижились, какие-то - нет. Вкратце о некоторых:
- резервное питание, интегрированное в блок питания сервера, обеспеченное стандартными 12V батарейками;
- "Серверный сендвич", где материнские платы с двух сторон окружают водяную систему теплоотвода в центре стойки;
- датацентр из контейнеров.
В заключение этого раздела хотелось бы взглянуть правде в глаза: идеального оборудования не бывает. У любого современного устройства, будь то сервер, коммутатор или маршрутизатор, есть шанс прийти в негодность из-за производственного брака, случайного стечения обстоятельств или других внешних факторов. Если умножить этот, казалось бы, небольшой шанс на количество оборудования, которое используется в Google, то окажется, что чуть ли не каждую минуту из строя выходит одно, или несколько, устройств в системе. На оборудование полагаться нельзя, поэтому вопрос отказоустойчивости переносится на плечи программной платформы, которую мы сейчас и рассмотрим.
3.1.4 Платформа
В Google очень рано столкнулись с проблемами ненадежности оборудования и работы с огромными массивами данных. Программная платформа, спроектированная для работы на многих недорогих серверах, позволила им абстрагироваться от сбоев и ограничений одного сервера.
Основными задачами в ранние годы была минимизация точек отказа и обработка больших объемов слабоструктурированных данных. Решением этих задач стали три основных слоя платформы Google, работающие один поверх другого:
- Google File System: распределенная файловая система, состоящая из сервера с метаданными и теоретически неограниченного количества серверов, хранящих произвольные данные в блоках фиксированного размера;
- BigTable: распределенная база данных, использующая для доступа к данным две произвольных байтовых строки-ключа (обозначающие строку и столбец) и дату/время (обеспечивающие версионность);
- MapReduce: механизм распределенной обработки больших объемов данных, оперирующий парами ключ-значение для получения требуемой информации. Взаимодействие всех этих слоев изображено ниже (рисунок 6).
Рисунок 6 - Схема работы трех основных слоев платформы Google
Google File System (GFS) - распределенная файловая система, созданная компанией Google в 2000 году для своих внутренних потребностей. Используемая реализация является коммерческой тайной компании Google, однако общие принципы построения системы были опубликованы в 2003 году. Несовместима с POSIX, тесно интегрирована с MapReduce. Обновленная GFS второй версии (2009 год) имеет кодовое название Colossus.
GFS - кластерная система, оптимизированная для центрального хранилища данных Google и нужд поискового механизма, обладающая повышенной защитой от сбоев. Система предназначена для взаимодействия между вычислительными системами, а не между пользователем и вычислительной системой.
Вся информация копируется и хранится в трёх (или более) местах одновременно, при этом система способна очень быстро находить реплицированные копии, если какая-то машина вышла из строя. Задачи автоматического восстановления после сбоя решаются с помощью программ, созданных по модели MapReduce.
BigTable - проприетарная высокопроизводительная база данных, построенная на основе Google File System (GFS), Chubby Lock Service и некоторых других продуктах Google. В настоящий момент не распространяется и не используется за пределами Google
Такая комбинация, дополненная другими технологиями, довольно долгое время позволяла справляться с индексацией Интернета, пока скорость появления информации в Интернете не начала расти огромными темпами из-за "бума социальных сетей". Информация, добавленная в индекс даже через полчаса, уже зачастую становилась устаревшей. В дополнение к этому в рамках самого Google стало появляться все больше продуктов, предназначенных для работы в реальном времени.
Подобные документы
Анализ современного состояния проблем тестирования высоконагруженных информационных систем. Построение математической модели определения высоконагруженных операций. Разработка программного обеспечения системы генерации сценариев нагрузочного тестирования.
дипломная работа [4,4 M], добавлен 24.08.2017Классификации архитектур вычислительных систем. Организация компьютерных систем. Устройство центрального процессора. Принципы разработки современных компьютеров. Эволюция микропроцессорных систем. Увеличение числа и состава функциональных устройств.
дипломная работа [1,4 M], добавлен 29.01.2009Формирование мирового рынка интегрированных систем. Классификация компьютерных систем предприятия. Компания и корпоративная система. История компьютерных систем, их классификация. Компьютерные системы "Галактика", "1С: предприятие 8.0", "SAP R/3".
реферат [37,8 K], добавлен 05.01.2010Понятие и внутренняя структура операционных систем, их классификация и разновидности, предъявляемые требования, этапы становления и развития, функциональные особенности. Описание и назначение базовых компьютерных систем: DOS, Windows, Linux, Mac.
курсовая работа [44,9 K], добавлен 14.12.2013Описание нетрадиционных и мультипроцессорных архитектур вычислительных систем. Принципы параллельной и конвейерной обработки данных. Теория массового обслуживания и управления ресурсами компьютерных систем. Базовые топологии локальных и глобальной сетей.
книга [4,2 M], добавлен 11.11.2010Обеспечение высокой релевантности поиска с помощью поисковой системы Google. Быстрота и надежность работы, большее количество ссылок в русскоязычном секторе Интернета. Службы, отсутствующие у других поисковых систем. Google как законодатель моды.
презентация [1,5 M], добавлен 10.03.2015Рассмотрение истории развития компьютерных систем. Изучение способов организации внутренней программно-аппаратной и логической структуры компьютерных систем и сетей. Структура системы; возможности и ограничения, взаимодействие и взаимосвязь элементов.
презентация [6,6 M], добавлен 06.04.2015Общие принципы охлаждения и работы различных видов и типов охлаждения компьютерных систем. Технико-экономическое обоснование и анализ различных систем охлаждения. Проектирование и расчеты отопления, вентиляции, природного и искусственного освещения.
дипломная работа [3,4 M], добавлен 10.07.2010Компьютерная база и программное обеспечение предприятия. Применяемые на предприятии информационные технологии. Техническое обслуживание и ремонт компьютерных систем и комплексов. Ремонт печатающей головки на МФУ EPSON. Реестр компьютерной техники.
отчет по практике [44,4 K], добавлен 26.04.2014Характеристики интерфейсов информационного взаимодействия компьютерных иерархических систем. Принцип "обратной связи". Свойства, простота и правила создания программно-аппаратных интерфейсов. Новые направления в проектировании компьютерных систем.
курсовая работа [112,7 K], добавлен 05.01.2017