Большие данные - MapReduce без Hadoop с применением конвейера ASP.NET

Дуг Дьюрнер

Продукты и технологии:

IIS Web Server, ASP.NET Pipeline, HttpModule, Task Parallel Library, Dynamic Language Runtime, MapReduce

В статье рассматриваются:

  • использование конвейера ASP.NET в качестве конвейера MapReduce;
  • преимущества в масштабируемости;
  • применение MapReduce для эмуляции шаблона распределенных вычислений, например «scatter-gather»;
  • применение MapReduce для эмуляции механизма распределенных рабочих процессов;
  • сравнение проекта-примера с Hadoop.

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

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

Именно эти вопросы подтолкнули нас пуститься в авантюру с целью создать крайне простой в настройке и использовании RESTful-компонент MapReduce.

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

Если в двух словах, то мы создали очень простую инфраструктуру, способную использовать MapReduce либо для выполнения интенсивных вычислений на узлах «ячейки» («mesh»), либо (в качестве альтернативы) для сбора данных на этих узлах, причем результаты коррелируются и объединяются в один конечный результат, который возвращается клиенту.

История вопроса

IIS Web Server (с его конвейером ASP.NET) доказал, что он является высокомасштабируемым веб-сервером корпоративного класса. Но эти технологии не ограничены простым обслуживанием веб-страниц и хостингом веб-сайтов. На самом деле нет никаких технических препятствий к тому, чтобы нельзя было использовать их как универсальный конвейерный механизм, доступный через HTTP. Стадии конвейера ASP.NET выполняются последовательно (переход к следующей стадии не осуществляется, пока не закончится выполнение предыдущей стадии), но каждая стадия может выполняться асинхронно. IIS Web Server можно сконфигурировать на выполнение нескольких конвейеров ASP.NET (т. е. нескольких w3wp.exe), обслуживающих HTTP-запросы.

Использование конвейера ASP.NET как универсального (доступного через HTTP) вместо обслуживания веб-страниц и хостинга веб-сайтов может показаться весьма необычным, но конвейер ASP.NET (с асинхронными стадиями конвейера) на самом деле довольно похож на конвейеры команд в микропроцессорах (bit.ly/1DifFvO), а возможность работы с несколькими файлами w3wp.exe (с конвейером ASP.NET в каждом w3wp.exe) во многом аналогична суперскалярной архитектуре микропроцессоров (bit.ly/1zMr6KD). Эти сходства наряду с проверенной масштабируемостью как раз и делают применение IIS Web Server и конвейера ASP.NET для всего, где нужна функциональность конвейеризации, очень интересным предложением.

Существует множество продуктов, которые уже предоставляют RESTful-интерфейс MapReduce (Hadoop, Infinispan, Riak, CouchDB, MongoDB и др.), но, как показывают наши исследования, они довольно трудны в подготовке к работе или требуют специализированных знаний экспертного уровня.

Мы хотели просто использовать наши существующие серверы Windows IIS, которые уже настроены и работают, задействовать наши существующие API-методы для операций над данными, которые уже написаны, получать данные для UI-экранов по требованию и иметь возможность в считанные минуты настроить и запустить все распределенную систему MapReduce (причем при ограниченном знании архитектуры и дизайна распределенных систем или систем MapReduce). Благодаря этому вы могли бы легко и быстро преобразовывать имеющееся маломасштабное приложение в более крупную распределенную систему с минимальными усилиями и знаниями на своих серверах или в облаке. Или, если вам нужно добавить богатые средства анализа данных в приложение для смартфона, вы могли бы сделать и это с минимумом усилий.

Этот RESTful-компонент MapReduce является своего рода накладкой, которая не требует переписывать существующее приложение, и основной кандидат на использование, если ваша цель заключается в простом добавлении базовой распределенной функциональности в приложение, уже имеющее обширный и общедоступный API для операций над данными. Он позволяет легко и быстро эмулировать шаблоны распределенных вычислений, такие как «scatter-gather» (рис. 1).

Эмуляция шаблонов распределенных вычислений, таких как «scatter-gather»

Рис. 1. Эмуляция шаблонов распределенных вычислений, таких как «scatter-gather»

Node 1 Узел 1
Node 2 Узел 2
Node 3 Узел 3
Local Node Локальный узел
AJAX Request AJAX-запрос
IIS IIS
DB База данных

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

MapReduce

Говоря простым языком, MapReduce — это способ агрегации больших хранилищ данных. Стадия Map выполняется на множестве серверных узлов распределенной обработки. Она обычно исполняет некую задачу на каждом распределенном серверном узле для выборки данных из узлов данных и может дополнительно преобразовывать или предварительно обрабатывать данные, когда они находятся на распределенной серверном узле. Стадия Reduce выполняется на одном или более серверных узлов конечной обработки и консолидирует все результаты от стадий Map в один конечный набор результатов, используя иножество различных алгоритмов объединения.

Говоря простым языком, MapReduce — это способ агрегации больших хранилищ данных.

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

Одно из основных преимуществ MapReduce — он обеспечивает горизонтальное масштабирование (scale out) вместо вертикального (scale up). Иначе говоря, для масштабирования вы просто добавляете обычные серверные узлы, а не приобретаете более эффективное оборудование для одного основного серверного узла. Горизонтальное масштабирование, в целом, является более дешевым и гибким выбором, поскольку используется недорогое стандартное аппаратное обеспечение, тогда как вертикальное масштабирование обычно гораздо дороже, потому что стоимость аппаратного обеспечения растет по экспоненте по мере увеличения его сложности.

В качестве отступления замечу, что MapReduce по-настоящему блистает, когда дело доходит до чрезвычайно огромных объемов данных (масштаба Интернета), а сами данные частично структурированы или вообще не структурированы, например файлы журналов и большие двоичные объекты. Реляционные базы данных SQL, напротив, хороши, когда речь идет о нормализованных структурированных данных со схемами, по крайней мере до определенного предела, когда издержки реляционной базы данных уже не позволяют справляться с огромным объемом данных.

На рис. 2 показана высокоуровневая схема процесса MapReduce и сравнивается простой SQL-запрос к реляционной базе данных с соответствующим запросом в процессе MapReduce для больших данных.

{Рисунок, без перевода}

Простой SQL-запрос к реляционной базе данных в сравнении с таким же запросом MapReduce
Рис. 2. Простой SQL-запрос к реляционной базе данных в сравнении с таким же запросом MapReduce

REST

Representational State Transfer (REST) определяет открытый API поверх HTTP, который использует парадигму CRUD (create, read, update, delete), основанный соответственно на HTTP-командах Post, Get, Put и Delete, и возвращает представление объекта от сервера клиенту, выдавшему запрос. REST ориентирован на предоставление открытого доступа к самому объекту как к сущности, а не только к функциональным операциям с этим объектом. Это ни спецификация, ни документ RFC, а просто рекомендация по проектированию. Вы можете близко придерживаться чистого проекта REST и требовать, чтобы URL был отформатирован для обработки объекта как сущности:

http://server/MapReducePortal/BookSales/Book A

Или выбрать проект, больше похожий на стиль RPC, и требовать, чтобы URL форматировался с указанием имен класса и выполняемого метода:

http://server/MapReducePortal/BookSales/
  GetTotalBookSalesByBookName/Book A
http://server/MapReducePortal/BookSales/
  GetTotalBookSalesByBookName?bookName=Book A

RESTful MapReduce

RESTful MapReduce означает выполнение операций MapReduce поверх HTTP для API и для транспортного механизма между распределенными серверными узлами.

REST поверх HTTP для API и транспорта имеет несколько преимуществ:

  • протокол HTTP через порт 80 является дружественным к брандмауэрам;
  • клиентские приложения почти с любой платформы могут легко использовать ресурсы без необходимости в специфичных для платформы зависимостях;
  • HTTP-команды (Get, Post, Put, Delete) — простая и элегантная парадигма для запрашиваемых ресурсов;
  • сжатие по алгоритму Gzip помогает уменьшать размеры полезных данных;
  • сам протокол HTTP имеет дополнительные преимущества, такие как встроенное кеширование.

В настоящее время проект-пример использует REST поверх HTTP для API и транспорта и поддерживает только Get и Post без операций записи и взаимодействие исключительно в формате JSON. Это похоже на некоторые из общеизвестных методологий для доступа к Hadoop Distributed File System (HDFS) извне (например, Hadoop YARN и Hadoop WebHDFS), но поддерживает лишь абсолютно необходимый минимум для функционирования системы. Мы не пытаемся заменить Hadoop или тягаться со всей его обширной функциональностью. Мы лишь стремимся предоставить крайне рудиментарную, простую в использовании альтернативу ценой сокращения функциональности.

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

В случае проекта-примера просто скопируйте MapReduceModule.dll в каталог \bin виртуального каталога на каждом серверном узле IIS, который вы хотите задействовать как распределенный серверный узел в своей системе MapReduce, а затем поместите запись в раздел modules файла web.config:

<modules>
  <add name="MapReduceModule"
    type="MapReduce.MapReduceModule" />
</modules>

Все готово. Вот так легко.

Один из привлекательных факторов проекта-примера — масштабируемость, достигаемая использованием конвейера ASP.NET в качестве конвейера MapReduce для выполнения процесса MapReduce.

Если на серверном узле IIS нет виртуального каталога, создайте новый виртуальный каталог с каталогом \bin, сделайте его Application и убедитесь, что он использует Microsoft .NET Framework 4 Application Pool. Увеличьте счетчик рабочих процессов w3wp.exe в Application Pool, который обслуживает виртуальный каталог MapReducePortal, чтобы предоставить больше обрабатывающих конвейеров для запросов MapReduce. Прочие дополнительные параметры конфигурации, применяемые для настройки IIS Server, обычно уже заданы IT-отделом, который управляет сервером; обсуждение этого вопроса выходит за рамки данной статьи, но, если эти параметры не заданы, см. их описания на веб-сайте Microsoft.

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

В проекте-примере просто добавьте атрибут PathInfoAttribute ко всем существующим API-методам бизнес-объекта и укажите строку PathInfo, которая будет использоваться для сопоставления URL с методом и его аргументами. Вот и все.

Одна из интересных особенностей кода примера в том, что, какие бы типы данных не возвращались существующими API-методами, они могут оставаться теми же, и изменять их не требуется. Инфраструктура способна автоматически обрабатывать почти все типы, так как использует .NET DynamicObject, чтобы динамически представлять возвращаемые типы данных. Например, если существующий метод возвращает набор объектов Customer, то DynamicObject представляет тип данных Customer.

Строка PathInfoAttribute PathInfo использует тот же .NET-класс UriTemplate, который применяется в Windows Communication Foundation (WCF), и позволяет делать те же фокусы, что и в проекте WCF Web HTTP REST или ASP.NET Web API 2, например подстановку имени переменной-аргумента, использование символов-шаблонов и т. д. Вы выбираете сопоставление URL с теми или иными методами. При этом вы имеете полный контроль и можете реализовать свой REST API как угодно. Можно придерживаться чистого REST API и делать так, чтобы сегменты URL представляли ваши объекты как полноправные сущности:

http://server/MapReducePortal/BookSales/Book A

[PathInfoAttribute(PathInfo="/BookSales/{bookName}",
  ReturnItemType="Book")]
public BookSales GetTotalBookSalesByBookName(string bookName)
{
}

Или следовать REST не столь строго и делать так, чтобы сегменты URL указывали имена класса и метода, который вы хотите выполнить:

http://server/MapReducePortal/BookSales/
  GetTotalBookSalesByBookName/Book A

[PathInfoAttribute(PathInfo="/BookSales/
  GetTotalBookSalesByBookName/{bookName}",
  ReturnItemType="Book")]
public BookSales GetTotalBookSalesByBookName(string bookName)
{
}

Выбор за вами.

Привлекательные факторы

Один из привлекательных факторов проекта-примера — масштабируемость, достигаемая использованием конвейера ASP.NET в качестве конвейера MapReduce для выполнения процесса MapReduce. Поскольку конвейер ASP.NET функционирует последовательно, он подходит для выполнения как стадии Map, так и стадии Reduce. А самое замечательное в том, что, хотя конвейер является последовательным и не будет переходить к следующей стадии, пока не завершится предыдущая, каждую стадию все равно можно выполнять асинхронно. Это позволяет конвейеру продолжать прием и обработку новых запросов MapReduce даже несмотря на то, что конвейер блокируется в ожидании возврата вызовов Map от других распределенных серверных узлов.

Как показано на рис. 3, каждый w3wp.exe несет один конвейер ASP.NET, действующий как конвейер MapReduce. Файл w3wp.exe (рабочий процесс IIS) управляется пулом приложений, закрепленным за виртуальным каталогом MapReducePortal. По умолчанию в пуле приложений один w3wp.exe обрабатывает новые запросы, поступающие виртуальному каталогу, но этот пул можно легко сконфигурировать на поддержку любого нужного вам количества экземпляров w3wp.exe. Это позволяет размещать несколько конвейеров MapReduce на одном автономном серверном узле, которые совместно обрабатывают запросы MapReduce, входящие в виртуальный каталог MapReducePortal. Асинхронная природа индивидуального конвейера ASP.NET обеспечивает параллельную обработку множества запросов. Возможность использования нескольких экземпляров w3wp.exe выводит вас на новый уровень.

Увеличиваем счетчик рабочих процессов IIS для пула приложений, чтобы больше конвейеров MapReduce обслуживали запросы MapReduce, посылаемые виртуальному каталогу MapReducePortal на этом IIS Server
Рис. 3. Увеличиваем счетчик рабочих процессов IIS для пула приложений, чтобы больше конвейеров MapReduce обслуживали запросы MapReduce, посылаемые виртуальному каталогу MapReducePortal на этом IIS Server

IIS IIS
MapReducePortal (Virtual Directory) MapReducePortal (Виртуальный каталог)
App Pool Пул приложений
bin bin
(IIS Worker Process) w3wp.exe (рабочий процесс IIS)
Requests Запросы
MAP Request Запрос MAP
ASP.NET Pipeline Конвейер ASP.NET
1. Begin Request Processing 1. Начало обработки запроса
2. Authentication 2. Аутентификация
3. Authorization 3. Авторизация
4. Cache Resolution 4. Разрешение кеша
5. Handler mapping 5. Сопоставление обработчика
6. Handler Pre-execution 6. Предварительное выполнение обработчика
7. Handler execution 7. Выполнение обработчика
8. Reliase State 8. Освобождение состояния
9. Update Cache 9. Обновление кеша
10. Update Log 10. Обновление журнала
11. End Request Processing 11. Конец обработки запроса
Reduce Reduce
Responses Ответы

Архитектура проекта-примера также позволяет добавлять столько IIS-серверов, сколько вам нужно для формирования все более крупной «ячейки» («mesh») серверных узлов, как показано на рис. 4. Чем крупнее становится ячейка, тем потенциально более крупная задача может быть обработана ее разбиением на все меньшие части и тем выше уровень параллелизма, который потенциально может быть достигнут. Асинхронный конвейер ASP.NET в сочетании с несколькими конвейерами на каждый сервер обеспечивает параллелизм на процессорных ядрах одного сервера. Ячейка серверов дает другой уровень параллелизма — между несколькими серверными машинами. В добавлении IIS-серверов в ячейку нет ничего сложного: вам надо лишь скопировать MapReduceModule.dll в папку \bin в виртуальном каталоге и добавить запись в файл web.config. Поскольку все IIS-серверы являются автономными, никакого дополнительного конфигурирования не требуется. Продукты вроде Hadoop, напротив, обычно требуют гораздо больше усилий, экспертных знаний и планирования, так как обычно серверы должны конфигурироваться как реальный серверный кластер.

Любой серверный узел может инициировать запрос MapReduce, и любое количество других распределенных серверных узлов, перечисленных в AJAX URL, может выполнять части стадии Map для этого запроса параллельно
Рис. 4. Любой серверный узел может инициировать запрос MapReduce, и любое количество других распределенных серверных узлов, перечисленных в AJAX URL, может выполнять части стадии Map для этого запроса параллельно

AJAX Request AJAX-запрос
IIS IIS
IIS servers could all be in the same building on same LAN, or could be distributed across the country over the Internet. IIS-серверы могут находиться все в одном здании в одной LAN или распределяться по всей стране и взаимодействовать через Интернет
Each IIS server has an identical MapReducePortal virtual directory and an identical MapReduceModule.dll. Каждый IIS-сервер имеет идентичный виртуальный каталог MapReducePortal и идентичную MapReduceModule.dll

Вам даже не нужно специально создавать IIS-серверы. Вы можете просто задействовать любые доступные IIS-серверы, скопировав MapReduceModule.dll в любой виртуальный каталог, уже имеющийся на сервере. Больше ничего не требуется. Теперь следующий AJAX-вызов может включать новый IIS-сервер в список distributednodes в URL QueryString.

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

В этой архитектуре серверных ячеек нет узла Master. Любой серверный узел может инициировать запрос MapReduce, и данные располагаются на том узле, который собирает их. Как видно на рис. 4, любой серверный узел может быть одновременно как инициатором запроса данных, так и провайдером данных. Сервер может запрашивать данные от любых других узлов в серверной ячейке, которые выполняют функцию Map, и может принимать результаты, объединяя их в один конечный набор результатов на стадии Reduce. Одновременно этот же серверный узел может действовать и как пограничный серверный узел (edge server node), обрабатывая стадию Map, возвращая свои частичные результаты для запроса MapReduce, который исходил от другого серверного узла.

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

Поскольку в такой ячеистой архитектуре используется ряд проверенных временем продуктов Microsoft (Windows Server, IIS Web Server и базы данных SQL Server), вы получаете средства поддержки избыточности и отказоустойчивости (например, Network Load Balancing [NLB] в Windows Server для IIS, AlwaysOn Availability Groups with Automatic Page Repair или Mirroring with Automatic Page Repair для SQL Server), которые уже встроены в эти коммерческие продукты. Подробное описание этих средств см. на веб-сайтах Microsoft.

Архитектура проекта-примера также позволяет объединять в цепочку множество запросов MapReduce, образуя рабочий процесс, где начальный ввод одного запроса MapReduce является результатом предыдущего запроса MapReduce. Это осуществляется изменением запроса MapReduce на Post вместо Get и включением результатов предыдущего запроса MapReduce в тело запроса Post. На рис. 5 показан пример конечного вывода на тестовой странице.

Отображение вывода от цепочки запросов на тестовой странице
Рис. 5. Отображение вывода от цепочки запросов на тестовой странице

Обзор проекта-примера

Фактически MapReduceModule.dll преобразует конвейер ASP.NET в конвейер MapReduce. Эта библиотека использует HttpModule для реализации функциональности Map и Reduce. Интересно, что некоторые операции объединения (вроде union), выполняемые на стадии Reduce, опираются на IEqualityComparer<T>, где T — это DynamicObject, который в каком-то смысле позволяет вам осуществлять в период выполнения сравнения на равенство на основе имени свойства как строкового значения даже несмотря на то, что IEqualityComparer<T> требует определения на этапе компиляции конкретного типа. Потрясающе!

На рис. 6 показана высокоуровневая схема устройства MapReduceModule.dll, иллюстрирующая поток обработки по мере его прохождения через MapReduceModule.dll. MapReduceModule — единственная обязательная DLL, и она должна находиться на каждом серверном узле, который участвует в инфраструктуре MapReduce. Добавить MapReduceModule.dll на сервер очень легко: достаточно скопировать MapReduceModule.dll в папку \bin в виртуальном каталоге и добавить запись в файл web.config.

Высокоуровневая схема потока обработки в MapReduceModule.dll
Рис. 6. Высокоуровневая схема потока обработки в MapReduceModule.dll

Browser Браузер
AJAX GET (RESTful API call with MapReduce) AJAX GET (вызов RESTful API с MapReduce)
Gets BookSales from <host>, Node1, Node2, Node3 (MAP), and merges/sums results based on BookName (REDUCE) Получает BookSales от <host>, Node1, Node2, Node3 (MAP) и объединяет/суммирует результаты на основе BookName (REDUCE)
request запрос
w3wp.exe (IIS Worker Process) w3wp.exe (рабочий процесс IIS)
ASP.NET Pipeline Конвейер ASP.NET
Begin Request Processing Начало обработки запроса
Authentication Аутентификация
Authorization Авторизация
Cache Resolution Разрешение кеша
Handler mapping Сопоставление обработчика
Handler Pre-execution Предварительное выполнение обработчика
Handler execution Выполнение обработчика
Reliase State Освобождение состояния
Update Cache Обновление кеша
Update Log Обновление журнала
End Request Processing Конец обработки запроса
response ответ
IHttpModule (MapReduce Module) IHttpModule (модуль MapReduce)
MAP MAP
waits ожидание
REDUCE REDUCE
Spawns a new thread for each node in QueryString using Parallel.ForEach to iterate distributenodes list and waits until all threads are finished to continue to Reduce step. It queries BookSales table for local node and makes HttpWebRequest call to MapReducePortal to query BookSales table on each distributed edge node, and stores results in HttpContext.Items as HashSet serialized to JSON to be used later in Reduce step. Порождает новый поток для каждого узла в QueryString, используя Parallel.ForEach для прохода по списку distributenodes, и ожидает завершения всех потоков, чтобы продолжить стадию Reduce. Запрашивает таблицу BookSales для локального узла и выдает HttpWebRequest-вызов к MapReducePortal для запроса таблицы BookSales в каждом распределенном пограничном узле. Результаты сохраняются в HttpContext.Items как HashSet, сериализованный в JSON для последующего использования на стадии Reduce
HashSets are reduced (merged/summed) into one final set using HashSet.UnionWith based on union=BookName and sum=Sales in QueryString and the final set is serialized to JSON and returned to client. HashSet сокращаются (объединяются/суммируются) в один конечный набор, используя HashSet.UnionWith на основе union=BookName и sum=Sales в QueryString. Конечный набор сериализуется в JSON и возвращается клиенту

IHttpModule (рис. 6) использует первую стадию в конвейере ASP.NET для функциональности MAP, подписываясь на событие AddOnBeginRequestProcessingAsync, генерируемое на стадии Begin Request Processing (Начало обработки запроса) в конвейере ASP.NET. IHttpModule использует последнюю стадию в конвейере ASP.NET для функциональности REDUCE, подписываясь на событие AddOnEndRequestProcessingAsync, генерируемое на стадии End Request Processing (Конец обработки запроса) в конвейере ASP.NET.

Если в двух словах, то вы подписываетесь только на события Begin Request Processing и End Request Processing в конвейере ASP.NET. Они выполняются последовательно и не переходят на следующую стадию, пока не будет завершена предыдущая стадия.

IHttpModule на стадии Begin Request Processing инициирует все MAP-запросы, запрашивая локальный узел и посылая веб-запрос HTTP каждому из распределенных серверных узлов, присутствующих в списке distributednodes в URL QueryString. Этот веб-запрос HTTP использует тот же URL, но без параметра distributednodes в URL.

Фактически MapReduceModule.dll преобразует конвейер ASP.NET в конвейер MapReduce.

На распределенных серверных узлах, которые принимают MAP-запрос, те же две стадии конвейера ASP.NET выполняются последовательно, но, поскольку в URL нет параметра distributednodes, стадии Begin Request Processing и End Request Processing фактически запрашивают только этот узел. Метод выборки данных MAP, указанный в PathInfoAttribute, выполняется на пограничном распределенном серверном узле, чтобы получить локальные данные от этого узла. Данные, возвращаемые в потоке ответов от каждого пограничного распределенного серверного узла, который инициировал исходный запрос, затем сохраняются в HttpContext, используя URL как ключ, благодаря чему впоследствии их можно извлечь на конечной стадии REDUCE.

На локальном серверном узле, который инициировал исходный запрос, метод выборки данных MAP, указанный в PathInfoAttribute, выполняется для получения данных, находящихся на этом локальном серверном узле. Данные с локального серверного узла потом сохраняются в HttpContext, используя URL как ключ, чтобы впоследствии их можно было извлечь на конечной стадии REDUCE.

IHttpModule на стадии End Request Processing выполняет стадию REDUCE, находя в HttpContext все данные и параметры REDUCE, которые были переданы в URL QueryString (строка может состоять из предопределенных параметров вроде sum=, union=, sort= или пользовательских параметров-функций наподобие reduce=CustomReduceFunction). Затем он объединяет/сокращает все наборы данных ото всех узлов в один конечный набор результатов, используя указанный параметр REDUCE. Наконец, он сериализует полученный набор результатов в JSON и возвращает этот набор в потоке ответов тому клиенту, который инициировал исходный AJAX-запрос MapReduce. Если никакие параметры REDUCE не заданы, возвращаются все данные ото всех узлов без обработки. На рис. 7 показан пример полученного вывода на тестовой странице.

Полученный вывод на тестовой странице
Рис. 7. Полученный вывод на тестовой странице

Сравнение проекта-примера с Hadoop

В табл. 1 вы найдете сравнение базовой функциональности MapReduce в Hadoop и в проекте-примере.

Табл. 1. Сравнение базовой функциональности MapReduce

Hadoop Проект-пример
Java-функция задания MAP, которая подсчитывает слова Любой метод, дополненный PathInfoAttribute, подобен функции задания MAP
Java-функция задания REDUCE, которая суммирует количество слов Параметры Reduce в URL QueryString (например, sum=) подобны функции задания REDUCE, которая выполняет операцию суммирования
Интерфейс Writable (сериализация) Атрибут [Serializable()] (сериализация)
Интерфейс WritableComparable (сортировка)

Интерфейс IComparer<T> (сортировка)

Интерфейс IEqualityComparer<T> (sum,union)

Ввод в задании MAP и вывод в задании REDUCE — соответствующие наборы пар <ключ,значение> Аргументы для методов, дополненных PathInfoAttribute, подобны вводу для задания MAP, а параметры Reduce в URL QueryString выполняют операцию сокращения и сериализуют результаты в JSON подобно тому, что выводит задание REDUCE

Один из распространенных сценариев, в которых MapReduce блистает, — подсчет того, сколько раз конкретное слово встречается в миллионах документов. На рис. 8 показано сравнение некоторого базового псевдокода, который реализует эквивалент знаменитой программы-примера «Hello World», но только в области больших данных — «Word Count Sample». Здесь представлены код на Hadoop Java и соответствующий код на C#, который можно было бы использовать для создания эквивалента в проекте-примере. Учтите, что этот код является просто псевдокодом и ни в коей мере не может быть синтаксически правильным или полным. Он лишь демонстрирует возможные способы реализации сходной функциональности в двух архитектурах. Полученный вывод на тестовой странице представлен на рис. 9.

Рис. 8. Сравнение псевдокода Word Count Sample

Hadoop MAP

public void map(LongWritable key, Text value, OutputCollector
  <Text, IntWritable> output, Reporter reporter)
  throws IOException {
  String line = value.toString();
  StringTokenizer tokenizer = new StringTokenizer(line);
  while (tokenizer.hasMoreTokens()) {
    word.set(tokenizer.nextToken());
    output.collect(word, one);
  }
}

Hadoop REDUCE

public void reduce(Text key, Iterator<IntWritable> values,
  OutputCollector<Text, IntWritable>
  output, Reporter reporter) throws IOException {
  int sum = 0;
  while(values.hasNext()) {
    sum += values.next().get();
  }
  output.collect(key, new IntWritable(sum));
}

Проект-пример MAP

http://server/.../WordCount/Test.txt?distributednodes=
  Node1,Node2,Node3&union=Word&sum=Count

[PathInfoAttribute(PathInfo="/WordCount/{fileName}",
  ReturnItemType="Row")]
public HashSet<Row> GetWordCount(string fileName)
{
  HashSet<Row> rows = new HashSet<Row>();
  byte[] bytes = File.ReadAllBytes(fileName);
  string text = Encoding.ASCII.GetString(bytes);
  string[] words = text.Split(new char[ ]{ ' ', '\r', '\n' });
  foreach(string word in words)
  {
    dynamic row = new Row();
    row["Word"] = word;
    row["Count"] = 1;
  }
  return rows;
}

Проект-пример REDUCE

http://server/.../WordCount/Test.txt?distributednodes=
  Node1,Node2,Node3&union=Word&sum=Count

Полученный вывод на тестовой странице
Рис. 9. Полученный вывод на тестовой странице

На рис. 10 показано, как реализовать базовую функциональность MapReduce в проекте-примере. Заметьте, что объектная сущность в URL сопоставляется с эквивалентом функции стадии MAP с помощью PathInfoAttribute и что параметры REDUCE в URL QueryString вроде sum= и reduce= тождественны эквиваленту функциональности стадии REDUCE в Hadoop.

{Для верстки: следующий листинг нужно дать с наездом на соседнюю колонку или даже в разворот}

Рис. 10. Базовая функциональность MapReduce в проекте-примере

          (Как Hadoop MAP)                    (Как Hadoop REDUCE)

http://server/.../BookSales?distributednodes=Node1,Node2,Node3&union=BookName&sum=Sales

[PathInfoAttribute(PathInfo="/BookSales", ReturnItemType="Book")]
public BookSales GetTotalBookSales()
{
}

          (Как Hadoop MAP)                    (Как Hadoop REDUCE)

http://server/.../Alarms?distributednodes=Node1,Node2,Node3&reduce=UnionIfNotDeleted

[PathInfoAttribute(PathInfo="/Alarms", ReturnItemType="Alarm")]
public Alarms GetAlarms()
{
}

private static HashSet<Alarm> UnionIfNotDeleted(HashSet<Alarm> originalData,
  HashSet<Alarm> newData)
{
}

Дополнительные примеры

На рис. 11 представлены дополнительные способы реализации функциональности наподобие MapReduce и показано, как RESTful URL сопоставляется с методами. Реализация метода опущена для краткости. Этот код можно реализовать по-разному, используя, в частности, алгоритм, который подсчитывает слова, данные таблицы BookSales в базе данных в каждом книжном магазине из цепочки, API-метод данных бизнес-объекта, который возвращает набор классов бизнес-объектов, данные датчиков, распределенные в точках по всей стране. Все зависит от вашего воображения — желаем приятно провести время!

Один из распространенных сценариев, в которых MapReduce блистает, — подсчет того, сколько раз конкретное слово встречается в миллионах документов.

Рис. 11. Различные способы реализации MapReduce в проекте-примере

Пример

http://server/.../BookSales/Book A?distributednodes=
  Node1,Node2,Node3&union=BookName&sum=Sales

[PathInfoAttribute(PathInfo="/BookSales/{bookName}",
  ReturnItemType="Book")]
public BookSales GetTotalBookSales(string bookName)
{
}

Пример

http://server/.../Alarms?distributednodes=
  Node1,Node2,Node3&union=AlarmID

[PathInfoAttribute(PathInfo="/Alarms", ReturnItemType="Alarm")]
public Alarms GetAlarms()
{
}

Пример

http://server/.../Alarms?distributednodes=
  Node1,Node2,Node3&reduce=UnionIfNotDeleted

[PathInfoAttribute(PathInfo="/Alarms", ReturnItemType="Alarm")]
public Alarms GetAlarms()
{
}

private static HashSet<Alarm> UnionIfNotDeleted(HashSet<Alarm>
  originalData, HashSet<Alarm> newData)
{
}

Пример

http://server/.../SensorMeasurements/2?distributednodes=
  Node1,Node2,Node3&union=SensorID

[PathInfoAttribute(PathInfo="/SensorMeasurements/{sensorID}",
  ReturnItemType="SensorMeasurement")]
public SensorMeasurements GetSensorMeasurements(int sensorID)
{
}

Пример

http://server/.../MP3Songs?distributednodes=
  Node1,Node2,Node3&union=SongTitle

[PathInfoAttribute(PathInfo="/MP3Songs",
  ReturnItemType=" MP3Song")]
public MP3Songs GetMP3Songs()
{
}

Заключение

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

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

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

Проект-пример позволяет задействовать существующие API-методы для данных бизнес-объектов на стадии Map простым применением к методу атрибута, сопоставляющего URL-путь с этим методом. Он также обеспечивает управление стадией Reduce добавлением простых команд к URL QueryString, например union для выполнения операции объединения данных по основному ключу.

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

В случае малых устройств вся тяжелая работа выполняется на IIS-серверах в ячейке, а не на самих устройствах. Благодаря этому, например, приложения для смартфона может использовать парадигму MapReduce с помощью всего одного HTTP-вызова, задействовав минимальное количество ресурсов смартфона.


Исходный код можно скачать по ссылке

Дуг Дьюрнер (Doug Duerner) — старший инженер ПО с более чем 15-летним опытом проектирования и реализации крупномасштабных систем на основе технологий Microsoft. Работал в нескольких банковских учреждениях из списка «Fortune 500» и на компанию, которая проектировала и создавала систему управления крупномасштабными распределенными сетями, использовавшимися Агентством оборонных информационных систем Defense Information Systems Agency (DISA) Министерства обороны для своей «глобальной информационной сети» и Государственным департаментом США. В глубине души помешан на компьютерах, но получает удовольствие от наиболее комплексных и трудных технических задач, особенно тех, которые всеми считаются невыполнимыми. С ним можно связаться по адресу coding.innovation@gmail.com.

Юн-Чанг Ванг (Yeon-Chang Wang) — старший инженер ПО с более чем 15-летним опытом проектирования и реализации крупномасштабных систем на основе технологий Microsoft. Тоже работал в нескольких банковских учреждениях из списка «Fortune 500» и на компанию, которая проектировала и создавала систему управления крупномасштабными распределенными сетями, использовавшимися агентством оборонных информационных систем Defense Information Systems Agency (DISA) Министерства обороны для своей «глобальной информационной сети» и Государственным департаментом США. Кроме того, занимался проектированием и реализацией крупномасштабной системы сертификации драйверов для одного из крупнейшего в мире производителя чипов. Имеет степень магистра в области компьютерных наук. Сложнейшие проблемы щелкает как орешки. С ним можно связаться по адресу yeon_wang@yahoo.com.

Выражаем благодарность за рецензирование статьи экспертам Microsoft Микелю Ситруку (Mikael Sitruk) и Марку Стейвли (Mark Staveley).