Автор: Талия Д. Трунфио П. Мароццо Ф. Белькастро Л. Кантини Р.
Теги: данные система управления базами данных (субд) анализ данных информационные технологии обработка данных обработка информации языки программирования
ISBN: 978-5-93700-358-4
Год: 2025
Доминико Талия, Паоло Трунфио, Фабрицио Мароццо,
Лорис Белькастро, Риккардо Кантини и Алессио Орсино
Большие данные
Domenico Talia, Paolo Trunfio,
Fabrizio Marozzo, Loris Belcastro,
Riccardo Cantini & Alessio Orsino
University of Calabria, Italy
Programming
Big Data Applications
Scalable Tools and Frameworks
for Your Needs
NEW JERSEY • LONDON • SINGAPORE • BEIJING • SHANGHAI • HONG KONG • TAIPEI • CHENNAI • TOKYO
Доминико Талия, Паоло Трунфио,
Фабрицио Мароццо, Лорис Белькастро,
Риккардо Кантини и Алессио Орсино
Университет Калабрии, Италия
Большие
данные
Современные фреймворки
и разработка приложений
Москва, 2025
УДК 004.6:004.655
ББК 16.35
П78
П78
Талия Д., Трунфио П., Мароццо Ф., Белькастро Л.,
Кантини Р. и Орсино А.
Большие данные. Современные фреймворки и разработка приложений /
пер. с англ. А. В. Логунова. – М.: ДМК Пресс, 2025. – 272 с.: ил.
ISBN 978-5-93700-358-4
В книге рассматриваются модели, системы и фреймворки, специально разработанные для обработки и анализа больших наборов данных. Вы познакомитесь с основными парадигмами и механизмами, применяемыми в анализе
больших данных, включая MapReduce, рабочие потоки, массовый синхронный
параллелизм, передачу сообщений и SQL-подобные модели. В главах книги
описаны примеры использования фреймворков Hadoop, Spark, Storm и MPI
и рассмотрены вопросы выбора среды, наиболее подходящей для достижения
целевых задач приложения.
Книга предназначена разработчикам приложений для работы с большими
данными, исследователям и профессионалам бизнеса, основанного на данных.
Читатель должен хорошо владеть такими языками, как Java, Python или Scala,
и знать основные концепции параллельного и распределенного программи
рования.
УДК 004.6:004.655
ББК 16.35
All rights reserved. This book, or parts thereof, may not be reproduced in any form or by any
means, electronic or mechanical, including photocopying, recording or any information storage
and retrieval system now known or to be invented, without written permission from the Publisher.
Russian translation arranged with World Scientific Publishing Co Pte Ltd, Singapore.
Все права защищены. Любая часть этой книги не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения
владельцев авторских прав.
ISBN 978-1-80061-504-5 (англ.) Copyright © 2024 by World
Scientific Publishing Co Pte Ltd
ISBN 978-5-93700-358-4 (рус.)
© Перевод, оформление, издание,
ДМК Пресс, 2025
Посвящается нашим студентам
Содержание
От издательства.........................................................................................................9
Предисловие.............................................................................................................10
Об авторах.................................................................................................................11
Признательности.....................................................................................................13
Глава 1. Введение....................................................................................................14
1.1.
1.2.
1.3.
1.4.
Мотивация и цели...............................................................................................14
Главные темы.......................................................................................................16
Аудитория и организация..................................................................................17
Онлайновые ресурсы..........................................................................................18
Глава 2. Концепции больших данных..............................................................19
2.1. Принципы и характеристики больших данных.............................................19
2.2. Концепции науки о данных...............................................................................22
2.2.1. Процессы науки о данных..........................................................................24
2.2.2. Навыки, специфичные для науки о данных............................................26
2.3. Хранение больших данных. ..............................................................................28
2.3.1. MongoDB. ......................................................................................................30
2.3.2. Google Bigtable..............................................................................................32
2.3.3. HBase..............................................................................................................33
2.3.4. Redis...............................................................................................................34
2.3.5. DynamoDB.....................................................................................................35
2.3.6. Apache Cassandra..........................................................................................36
2.3.7. Графовая система управления базами данных Neo4j. ...........................37
2.3.8. Сводные соображения о хранилищах NoSQL..........................................38
2.4. Масштабируемый анализ данных....................................................................41
2.5. Параллельные вычисления. ..............................................................................45
2.5.1. Базовые понятия и определения. .............................................................45
2.5.2. Параллельные архитектуры.......................................................................46
2.5.3. Аппаратные платформы.............................................................................48
2.5.4. Метрики производительности. .................................................................50
2.6. Облачные вычисления.......................................................................................51
2.6.1. Модели распределения и развертывания облачных услуг....................52
2.6.2. Облачные услуги для больших данных....................................................54
2.7. На пути к экзафлопсным вычислениям. .........................................................59
Содержание 7
2.7.1. Главные трудности систем экзафлопсного масштаба............................61
2.8. Параллельное и распределенное машинное обучение.................................63
2.8.1. Стратегии параллельного обучения.........................................................64
2.8.2. Стратегии распределенного обучения.....................................................67
Глава 3. Модели программирования для больших данных....................72
3.1. Параллельное программирование для приложений по обработке
больших данных..........................................................................................................72
3.1.1. Необходимость в моделях параллельного программирования...........73
3.1.2. Характеристики моделей программирования........................................73
3.2. Модель на основе MapReduce............................................................................74
3.2.1. Ключевые идеи, положенные в основу MapReduce................................75
3.2.2. Модель программирования.......................................................................75
3.2.3. Программы MapReduce...............................................................................76
3.2.4. Применения и соображения о производительности.............................79
3.3. Модель на основе рабочего потока..................................................................81
3.3.1. Шаблоны рабочих потоков.........................................................................82
3.3.2. Ориентированные ациклические графы.................................................84
3.4. Модель на основе передачи сообщений..........................................................85
3.4.1. От коллективной памяти к передаче сообщений...................................86
3.4.2. Примитивы передачи сообщений. ...........................................................87
3.4.3. Групповая коммуникация..........................................................................89
3.5. Модель на основе массового синхронного параллелизма. ..........................90
3.5.1. Супершаг.......................................................................................................91
3.5.2. Стоимость алгоритма массового синхронного параллелизма.............93
3.5.3. Модель на основе массового синхронного параллелизма
с коллективной памятью.......................................................................................94
3.6. SQL-подобная модель.........................................................................................96
3.6.1. От модели NoSQL к SQL-подобной модели.............................................96
3.6.2. Зачем использовать язык SQL на больших данных?..............................97
3.6.3. Разбиение данных на разделы. .................................................................99
3.7. Модель на основе разделенного глобального адресного пространства.....99
3.7.1. Параллелизм в модели на основе разделенного глобального
адресного пространства......................................................................................101
3.7.2. Память и функция стоимости..................................................................101
3.7.3. Распределение данных по местам. .........................................................102
3.8. Модели для систем экзафлопсного масштаба..............................................102
3.8.1. Роль моделей программирования в системах экзафлопсного
масштаба................................................................................................................103
3.8.2. Требования к моделям экзафлопсного масштаба................................104
3.8.3. Ограничения существующих моделей программирования...............104
3.8.4. Модели программирования для систем экзафлопсного масштаба.....106
Глава 4. Инструменты обработки больших данных.................................109
4.1. Главные характеристики..................................................................................109
4.2. Инструменты программирования на основе модели MapReduce.............110
8 Содержание
4.2.1. Apache Hadoop............................................................................................111
4.3. Инструменты программирования на основе рабочих потоков.................120
4.3.1. Apache Spark. ..............................................................................................121
4.3.2. Apache Storm...............................................................................................134
4.3.3. Apache Airflow.............................................................................................145
4.4. Инструменты программирования на основе передачи сообщений.........154
4.4.1. Интерфейс передачи сообщений............................................................154
4.5. Инструменты программирования на основе массового
синхронного параллелизма. ...................................................................................161
4.5.1. Spark GraphX...............................................................................................161
4.6. Инструменты SQL-подобного программирования. ....................................169
4.6.1. Apache Hive.................................................................................................170
4.6.2. Apache Pig. ..................................................................................................176
4.7. Инструменты программирования на основе разделенного
глобального адресногопространства....................................................................182
4.7.1. UPC++...........................................................................................................183
Глава 5. Сравнение инструментов программирования...........................190
5.1. Перед проведением анализа инструментов.................................................190
5.2. Сравнительный анализ характеристик систем............................................191
5.2.1. Характеристики систем............................................................................191
5.2.2. Распространенность систем. ...................................................................195
5.2.3. Преимущества и недостатки....................................................................197
5.3. Сравнительный анализ на примерах приложений. ....................................199
5.3.1. Пакетное приложение: Apache Spark в сопоставлении
с Apache Hadoop....................................................................................................199
5.3.2. Потоковое приложение: Apache Storm в сопоставлении
с Apache Spark Streaming. ....................................................................................212
5.3.3. Приложение на основе SQL: Apache Hive в сопоставлении
с Apache Spark SQL................................................................................................221
5.3.4. Графовое приложение: MPI в сопоставлении с Apache Spark
GraphX....................................................................................................................229
Глава 6. Выбор правильного фреймворка для приручения
больших данных...................................................................................................243
6.1.
6.2.
6.3.
6.4.
Входные данные................................................................................................244
Класс приложения.............................................................................................246
Инфраструктура................................................................................................248
Другие факторы.................................................................................................251
Сопутствующие материалы...............................................................................253
Библиография........................................................................................................254
Предметный указатель. ......................................................................................262
От издательства
Отзывы и пожелания
Мы всегда рады отзывам наших читателей. Расскажите нам, что вы думаете
об этой книге – что понравилось или, может быть, не понравилось. Отзывы
важны для нас, чтобы выпускать книги, которые будут для вас максимально
полезны.
Вы можете написать отзыв на нашем сайте www.dmkpress.com, зайдя на
страницу книги и оставив комментарий в разделе «Отзывы и рецензии».
Также можно послать письмо главному редактору по адресу dmkpress@gmail.
com; при этом укажите название книги в теме письма.
Если вы являетесь экспертом в какой-либо области и заинтересованы в написании новой книги, заполните форму на нашем сайте по адресу http://
dmkpress.com/authors/publish_book/ или напишите в издательство по адресу
dmkpress@gmail.com.
Список опечаток
Хотя мы приняли все возможные меры для того, чтобы обеспечить высокое качество наших текстов, ошибки все равно случаются. Если вы найдете
ошибку в одной из наших книг, мы будем очень благодарны, если вы сообщите о ней главному редактору по адресу dmkpress@gmail.com. Сделав это,
вы избавите других читателей от недопонимания и поможете нам улучшить
последующие издания этой книги.
Нарушение авторских прав
Пиратство в интернете по-прежнему остается насущной проблемой. Издательство «ДМК Пресс» очень серьезно относится к вопросам защиты авторских прав
и лицензирования. Если вы столкнетесь в интернете с незаконной публикацией
какой-либо из наших книг, пожалуйста, пришлите нам ссылку на интернет-ресурс, чтобы мы могли применить санкции.
Ссылку на подозрительные материалы можно прислать по адресу элект
ронной почты dmkpress@gmail.com.
Мы высоко ценим любую помощь по защите наших авторов, благодаря
которой мы можем предоставлять вам качественные материалы.
Предисловие
В настоящей книге рассматриваются и обсуждаются модели, системы
и фреймворки программирования, специально сконструированные для обработки и анализа крупных наборов данных. В частности, в ней дается по
дробное описание свойств и механизмов главных парадигм программирования, используемых в анализе больших данных, таких как модели на основе
MapReduce, рабочих потоков, массового синхронного параллелизма, передачи сообщений и SQL-подобные. Более того, в главах книги на примерах программирования описаны наиболее часто используемые фреймворки, такие
как Hadoop, Spark, Storm и MPI, специально сконструированные для анализа
крупных коллекций данных.
Мировая путина, интернет вещей и платформы социальных сетей обес
печивают условия для порождения и сбора огромных объемов цифровых
данных, поступающих из самых разных источников, включая блоги, датчики,
мобильные устройства, носимые трекеры, спутники и камеры наблюдения.
Эти данные, общепринято именуемые «большими данными», бросают вызов
существующим системам и способностями по хранению, обработке и анализу. По этой причине в настоящее время изучаются, конструируются, разрабатываются и внедряются новые модели, языки, инструменты, системы
и алгоритмы, способные эффективно собирать, хранить и анализировать
большие данные, а также усваивать из них полезную информацию.
В этой книге описывается и приводится обзор параллельных и распределенных парадигм, языков и систем, используемых сегодня для анализа
больших данных и усвоения из них действенной информации на масштабируемых компьютерах. В частности, в ней дается подробное описание свойств
и механизмов главных парадигм параллельного программирования, а на
примерах программирования иллюстрируются наиболее широко используемые фреймворки, предназначенные для анализа больших данных. Более
того, в книге обсуждаются и сравниваются разные фреймворки, выделяются
главные характеристики каждого из них, их распространенность (сообщест
во разработчиков и пользователей), а также главные преимущества и недостатки их использования в реализации приложений по анализу больших данных. Конечная цель данного тома – помочь конструкторам и разработчикам
приложений по обработке больших данных, определив и выбрав наилучший
или наиболее подходящий инструмент(ы) программирования в зависимости
от их навыков, наличия оборудования, областей применения и целей, а также
учитывая поддержку, предоставляемую сообществом разработчиков. По каждому языку программирования/фреймворку представлены реально-практические примеры программирования, демонстрирующие способы разработки
и реализации приложений по обработке больших данных.
Об авторах
Доменико Талия – профессор кафедры Компьютерной инженерии в Университете Калабрии, Италия, и почетный профессор Университета Амити,
Индия. Является старшим ассоциированным редактором журнала ACM Computing Surveys, ассоциированным редактором журнала Computer, а также
членом Редакционного совета журналов Future Generation Computer Systems, IEEE Transactions on Parallel and Distributed Systems, International Journal of Web and Grid Services, Journal of Cloud Computing, Big Data and Cognitive
Computing и International Journal of Next-Generation Computing. В сферу его
научных интересов входят высокопроизводительные вычисления, большие данные, машинное обучение, параллельный и распределенный анализ
данных, облачные вычисления, анализ социальных сетей, распределенное
обнаружение знаний, одноранговые системы и конкурентные модели программирования. Является автором нескольких книг и более 400 научных
статей.
Паоло Трунфио – доцент кафедры Компьютерной инженерии в Университете Калабрии, Италия. В 2007 году работал приглашенным исследователем
в шведском Институте вычислительных наук (SICS) в Стокгольме, Швеция.
В настоящее время является ассоциированным редактором журналов Journal
of Big Data, IEEE Transactions on Cloud Computing и ACM Computing Surveys
и членом редколлегий нескольких научных журналов, включая Future Generation Computer Systems, Big Data and Cognitive Computing, International
Journal of Web Information Systems и International Journal of Parallel, Emergent
and Distributed Systems. В сферу его научных интересов входят облачные вычисления, большие данные, анализ социальных сетей, параллельное и распределенное обнаружение знаний и одноранговые системы.
Фабрицио Мароззо – доцент кафедры Компьютерной инженерии в Университете Калабрии, Италия. Получил степень доктора философии в области
системной и компьютерной инженерии в Университете Калабрии. В 2011–
2012 годах проходил стажировку в Барселонском суперкомпьютерном центре
в Исследовательской группе по решеточным вычислениям на факультете Вычислительных наук. Входит в состав редакционных советов нескольких журналов, включая IEEE Access, IEEE Transactions on Big Data, Journal of Big Data,
Big Data and Cognitive Computing, Algorithms, Frontiers in Big Data, Heliyon
и SN Computer Science. В сферу его научных интересов входят анализ больших
данных, анализ социальных сетей, высокопроизводительные вычисления,
облачные и периферийные вычисления, а также машинное обучение.
12 Об авторах
Лорис Белькастро – исследователь в области компьютерной инженерии
в Университете Калабрии, Италия. Получил степень доктора философии
в области информационно-коммуникационной инженерии в Университете
Калабрии. В 2012 году стал стипендиатом в Институте высокопроизводительных вычислений и сетей Национального исследовательского совета Италии
(ICAR-CNR). Выступал в качестве приглашенного редактора в многочисленных журналах, включая Future Generation Computer Systems, Journal of Big
Data, Sensors, Algorithms, Applied Sciences и Frontiers in Big Data. В сферу его
научных интересов входят облачные и периферийные вычисления, большие
данные, анализ социальных сетей, параллельный и распределенный анализ
данных.
Риккардо Кантини – исследователь в области компьютерной инженерии
в Университете Калабрии, Италия. В том же университете получил степень
доктора философии в области информационных и коммуникационных технологий. В 2021–2022 годах состоял приглашенным исследователем в Барселонском суперкомпьютерном центре, работая вместе с Группой по рабочим
потокам и распределенным вычислениям на факультете Вычислительных
наук. В сферу его научных интересов входят анализ социальных сетей и больших данных, машинное и глубокое обучение, обработка естественного языка,
анализ мнений, выявление тем, периферийные вычисления и высокопроизводительная аналитика данных.
Алессио Орсино – в настоящее время Алессио Орсино получает степень
доктора философии в области информационных и коммуникационных
технологий в Университете Калабрии, Италия. В 2023 году работал приглашенным исследователем на факультете Вычислительных наук и технологий
Кембриджского университета, сотрудничая с Лабораторией исследования
мобильных систем. В сферу его научных интересов входят анализ больших
данных, параллельные и распределенные вычисления, высокопроизводительная аналитика данных, облачные и периферийные вычисления, а также
машинное обучение.
Признательности
Хотели бы поблагодарить Рози Уильямсон и Логеша Арумугама за их полезную поддержку и комментарии к ранним черновикам этой книги и во время
всех редакционных процессов. Выражаем свою признательность за частичную финансовую поддержку проекта eFlows4HPC (Европейская комиссия по
программе исследований и инноваций Horizon 2020 и EuroHPC JU по контракту 955558) и проекта «PNRR MUR PE0000013-FAIR» – CUP H23C22000860006.
Глава
1
Введение
Архитектуры параллельных вычислений и языки и инструменты масштабируемого программирования играют ключевую роль в конструировании
и реализации сложных программных приложений, ориентированных на
управление крупными наборами данных, хранящихся в файловых системах,
базах данных, архивах и озерах данных, и их анализ. Эта книга написана
для тех, кто интересуется программированием приложений по обработке
больших данных на мультиядерных компьютерах, на облачных платформах, в системах распределенных вычислений и на массивно-параллельных
машинах. Студенты, разработчики и ученые, желающие узнать о наиболее
эффективных фреймворках программирования приложений, интенсивных
по привлечению данных, найдут в этой книге руководство, которое знакомит
с моделями, языками и инструментами эффективного управления большими
данными и их анализа, а также обсуждение вопросов выбора среды, наиболее
подходящей для достижения целевых задач приложения. Эта глава знакомит
с целями книги, иллюстрирует темы и описывает ее организацию.
1.1. Мотивация и цели
Современный мир генерирует беспрецедентное количество данных, и способность добывать из них ценные сведения имеет решающее значение для
успеха во многих областях, включая предпринимательство, науку и управление, обеспечивая инновации и принятие обоснованных решений. Самое
лучшее, что можно сделать, чтобы задействовать ценность огромного количества имеющихся данных, состоит в реализации масштабируемых приложений по управлению данными и проведению их анализа, которые эффективно
добывают из них полезные закономерности, модели и тренды. Задача программирования приложений по обработке больших данных сложна и многогранна и требует технических знаний и глубокого понимания самых разных
понятий и концепций, включая аналитику данных, распределенные вычис-
Мотивация и цели 15
ления, параллельную обработку и машинное обучение. Несмотря на значительные трудности, сфера больших данных постоянно растет, а вместе с ней
растет и спрос на квалифицированных специалистов, способных конструировать и строить эффективные и масштабируемые приложения по обработке
крупных объемов данных. Способность работать с большими данными стала
важнейшим навыком на современном рынке труда, и овладение им может
открывать массу карьерных возможностей.
Эта книга призвана стать незаменимым руководством для разработчиков,
стремящихся создавать надежные и масштабируемые приложения по обработке больших данных. Благодаря всеобъемлющему охвату главных инструментов и фреймворков она предлагает глубокое понимание принципов
и практик, необходимых для реализации эффективных приложений по проведению анализа больших данных, охватывая широкий спектр тем, включая
системы распределенного хранения и вычисления, масштабируемую обработку данных, управление данными и машинное обучение, с использованием популярных инструментов и фреймворков, таких как Hadoop, Spark, Hive,
MPI и Storm. Книга предлагает практический подход к конструированию
приложений по обработке больших данных, тем самым превращая ее в руководство, которое подойдет для разработчиков с разным уровнем опыта.
Одним из ключевых преимуществ этой книги является ее акцент на масштабируемости. По сути дела, обсуждаемые здесь инструменты и фреймворки специально сконструированы для работы с крупными наборами данных
и выполнения сложных заданий по обработке за счет привлечения параллелизма. Их освоение позволяет разработчикам создавать приложения, способные обрабатывать огромные объемы данных.
Еще одним существенным преимуществом книги является ее практический подход. В книге приводятся реально-практические примеры, которые
показывают читателям, как применять полученные знания, и помогают приобретать опыт работы с различными практическими вариантами использования. В дополнение к этому в книгу включено сравнение инструментов
обработки больших данных в реально-практических приложениях, которое
показывает порядок использования больших данных в различных сценариях и областях, обеспечивая читателям глубокое понимание потенциальных
приложений по обработке больших данных и предоставляя руководство по
выбору правильных инструментов по каждому конкретному варианту использования.
Мы также освещаем некоторые последние тренды, такие как вычисления
экзафлопсного масштаба, параллельное и распределенное машинное обуче
ние, и обсуждаем порядок их возможного привлечения для анализа и обработки крупных наборов данных.
К концу этой книги читатель получит глубокое понимание принципов
и методов, используемых для разработки масштабируемых и надежных приложений по обработке больших данных, а также практический опыт работы
с некоторыми наиболее широко используемыми инструментами в этой области.
16 Введение
1.2. Главные темы
Эта книга представляет собой всеобъемлющее руководство, в котором обследуются главные парадигмы и фреймворки, используемые для обработки
и анализа больших данных, и помогает программистам и разработчикам
в выборе самых лучших инструментов программирования в зависимости
от их навыков, наличия оборудования, областей применения и целей. Книга охватывает широкий спектр тем, связанных с обработкой, управлением
и анализом больших данных, включая:
главные системы распределенного хранения данных, являющиеся важнейшим элементом противостояния текущему экспоненциальному
росту требований к хранению данных, обеспечивающие масштабируемость, эффективность, отказоустойчивость, доступность и состыкуемость;
главные принципы, положенные в основу процессов анализа данных
и науки о данных, а также их развитие на масштабируемых вычислительных системах;
преимущества таких технологий, как высокопроизводительные, облачные и распределенные вычисления, которые способствуют обработке
крупных объемов данных в реально-практических контекстах;
главные модели программирования для больших данных, такие как
MapReduce, рабочие потоки и передача сообщений, являющиеся ключевыми парадигмами, помогающими пользователям выражать параллельные алгоритмы и приложения, предоставляя абстракции для
архитектуры параллельных вычислений;
новейшие предложения в области вычислений экзафлопсного масштаба, ориентированные на масштабируемые технологические решения
и инструменты в широком спектре научных областей, включая физику,
биологию и симуляцию природных явлений;
наиболее часто используемые инструменты программирования для
обработки больших данных, предлагающие как универсальные, так
и специализированные решения по работе с разными видами данных,
от структурированных данных до графов и потоков, и областями, включая пакетные, потоковые, графовые и запросные приложения;
ключевые характеристики, плюсы и минусы разных фреймворков относительно конкретных классов приложений, с целью оказания помощи программистам в выборе наиболее подходящего фреймворка,
а также другие важные факторы, которые могут влиять на этот выбор,
такие как тип данных, масштаб инфраструктуры, навыки разработчиков и размер сообщества.
Аудитория и организация 17
1.3. Аудитория и организация
Эта книга предназначена для студентов и инженеров-изыскателей, изучающих обработку и аналитику больших данных, разработчиков программного
обеспечения и профессионалов бизнеса, заинтересованных в использовании
больших данных в своих организациях. Читатели, как правило, должны хорошо понимать языки программирования, такие как Java, Python или Scala,
и иметь базовые знания о главных понятиях и концепциях параллельного
и распределенного программирования. С целью удовлетворения потребностей такого широкого круга читателей в книге содержится исчерпывающий
обзор фреймворков программирования масштабируемых распределенных
и параллельных приложений, интенсивных по привлечению данных. Она
представляет собой ценный ресурс для студентов, стремящихся глубже понять эти концепции и методы, а также для профессионалов, работающих на
фабриках по производству программного обеспечения и в компаниях, занимающихся наукой о данных, которые могут извлечь пользу из практических
идей и реальных приложений, представленных в главах книги.
Читатели могут свободно приспосабливать чтение книги под свои потребности, основываясь на собственных навыках и знакомстве с темой. Книгу
можно читать целиком либо сосредотачиваться на отдельных интересующих
разделах, не чувствуя себя обязанными внимательно прочитывать каждое
слово, перед тем как продолжать дальше.
Книга состоит из пяти глав, которые вкратце описаны ниже.
Глава 2 «Концепции больших данных» знакомит с областью больших данных
путем введения главных принципов и особенностей управления большими
данными и их анализа. В частности, обсуждаются техники анализа данных
и подходы на основе науки о данных, а также исследуется их развитие на
масштабируемых вычислительных системах. Также рассматриваются такие
технологии, как высокопроизводительные, облачные и распределенные вычисления, объясняя их полезность в обработке больших данных.
Глава 3 «Модели программирования для больших данных» посвящена главным моделям программирования, разработанным и используемым для реа
лизации крупномасштабных приложений по обработке больших данных,
включая модели на основе MapReduce, рабочих потоков, массового синхронного параллелизма, передачи сообщений, разделенного глобального адресного пространства и SQL-подобные модели. В книге также представлены
последние предложения в области вычислений экзафлопсного масштаба.
В данной главе рассматриваются ключевые характеристики и механизмы
каждой модели программирования, которые можно использовать для обработки и анализа больших данных.
Глава 4 «Инструменты для приложений по обработке больших данных» описывает языки программирования, библиотеки и инструменты, используемые
для конструирования масштабируемых приложений по обработке больших
данных. Фреймворки, включая Hadoop, Spark, Storm и MPI, представлены
18 Введение
с описанием их характеристик и механизмов программирования. По каждому инструменту программирования приводится несколько реально-практических примеров приложений по обработке больших данных.
Глава 5 «Сравнение инструментов программирования» посвящена сравнению инструментов программирования, представленных в предыдущей главе, путем выделения главных характеристик, преимуществ и недостатков их
использования в разных типах приложений, таких как пакетные, потоковые,
графовые и запросные приложения. В ней также обсуждается сообщество
разработчиков, проводится сравнение этих фреймворков по их распространенности и популярности с точки зрения конечных пользователей и разработчиков.
Глава 6 «Выбор правильного фреймворка для приручения больших данных»
завершает книгу обсуждением главных факторов, которые могут влиять на
выбор фреймворка, наиболее подходящего для обработки и анализа больших
данных. Основное внимание уделяется характеристикам входных данных,
классу приложений и масштабу инфраструктуры, а также приводятся многие
другие факторы, которые в той или иной степени могут влиять на решения
разработчиков, включая квалификацию конструктора или разработчика,
размер сообщества, конфиденциальность данных, требования к обеспечению безопасности, доступный бюджет, интегрируемость и совместимость.
1.4. Онлайновые ресурсы
Онлайновый репозиторий, включающий все исходные коды и наборы данных, использованные в примерах, приводимых в главах книги, доступен
читателю по адресу https://bigdataprogramming.github.io. Репозиторий предоставляет Docker-контейнеры для бесшовного исполнения предложенных
примеров. Также имеется краткое руководство по установке, компиляции
и запуску программ.
Копия слайдов, основанных на содержимом книги, предназначенных для
использования в образовательных целях, находится на веб-сайте издательства. Инструкции по доступу к слайдам приведены на стр. 253.
Глава
2
Концепции
больших данных
Эта глава знакомит с областью больших данных путем введения главных
принципов и описания особенностей управления большими данными и их
анализа. В частности, обсуждаются техники анализа данных и подходы на
основе науки о данных, а также исследуется их развитие на масштабируемых
вычислительных системах. Помимо этого, рассматриваются такие технологии, как высокопроизводительные, облачные и распределенные вычисления,
объясняя их полезность в обработке больших данных.
2.1. Принципы и характеристики
больших данных
За последние несколько лет способность генерирования и сбора данных выросла в геометрической прогрессии. В эпоху интернета вещей (IoT) из нескольких источников, таких как датчики, мобильные устройства, веб-при
ложения и услуги, генерируются и собираются огромные объемы цифровых
данных. Более того, с широким принятием мобильных устройств миллионы
людей ежедневно пользуются социальными сетями и производят огромные
объемы цифровых данных, которые можно эффективно задействовать для
добычи ценной информации о динамике и поведении людей. Такие данные,
которые принято называть «большими данными», содержат ценную информацию о действиях, интересах и поведении пользователей, что делает их
идеально пригодными для очень широкого круга приложений.
В настоящее время термин «большие данные» часто используется неправильно, но он очень важен в вычислительной науке для понимания предпринимательской и человеческой деятельности. В технической литературе
20 Концепции больших данных
было предложено несколько определений данного термина, однако достичь
глобального консенсуса относительно его содержимого было нелегко. Несмотря на то что указанный термин в явном виде не упоминается, первое его
определение было предложено Дугом Лэйни (Doug Laney, аналитиком META
Group, ныне компания Gartner) в отчете 2001 года (Laney и соавт., 2001), который предположил, что тремя аспектами трудностей в управлении данными
являются объем, разнообразие и скорость. Впоследствии компания Gartner
предложила более формальное определение (Gartner, Inc., дата публикации
отсутствует): «Большие данные – это крупные объемы, высокая быстрота и/или
большое разнообразие информационных активов, которые нуждаются в экономически эффективных, инновационных формах обработки, позволяющих улучшать понимание, принятие решений и автоматизацию процессов».
В таком определении для описания больших данных применена трехмерная модель, также именуемая моделью «3V» (то есть volume, velocity и variety –
объем, быстрота и разнообразие). В частности, объем относится к количеству
генерируемых данных, быстрота – к скорости, с которой эти данные генерируются, а разнообразие – к разнородности структуры и формата данных,
поступающих из разных источников. Давайте обсудим эти три свойства больших данных более подробно.
Объем – это, пожалуй, самое первое свойство, которое приходит на
ум при мысли о больших данных. Поскольку каждый день создаются экзабайты данных, то уже не редкость, когда на устройствах хранения в крупных компаниях размещаются даже петабайты данных.
Такой объем данных нередко бросает серьезный вызов способности
управлять, так как требует нестандартных решений в области хранения
и управления.
Быстрота, по сути, служит мерой скорости поступления данных. Некоторые данные поступают в режиме реального времени, а другие –
с задержкой, спорадически, отправляемые партиями или пакетами.
Таким образом, может случиться так, что сбор данных, происходящих
из разных источников, поступающих в одном и том же темпе, может
поставить систему сбора в затруднительное положение, так как традиционные вычислительные системы не смогут работать на данных,
поступающих быстрее, чем они способны их осмысливать. В качестве
примера достаточно взять систему, которая собирает данные из сети
датчиков, состоящей из тысяч устройств, посылающих данные с интервалом порядка секунд.
Разнообразие означает, что данные могут собираться из разных источников и предъявляться в самых разных форматах, таких как видео,
текст, аудио, CSV и PDF. Слияние или перевод этих данных в общий
формат может нуждаться в больших усилиях и продвинутых аналитических навыках, чтобы понимать эти входные данные и делать их
управляемыми и пригодными для анализа.
Согласно определению, данному компанией Gartner, большие данные характеризуются не только крупным размером наборов данных, но и их разно-
Принципы и характеристики больших данных 21
образием (то есть данными из нескольких хранилищ, областей определения
или типов), а также быстротой их сбора и обработки. По сути дела, можно
собирать так много цифровых данных из нескольких источников и с таким
высоким темпом, что их объем может легко превысить возможности по их
использованию. Такую ситуацию принято называть «лавиной данных».
Модель «3V» была принята многими представителями индустрии сферы
ИТ и сферы научных исследований (например, IBM (Zikopoulos и соавт., 2011)
и инженерами-изыскателями Microsoft (Meijer, 2011)). Вместе с тем был предложен целый ряд других определений, расширяющих модель «3V» за счет
введения других характеристик, таких как ценность (Gantz и Reinsel, 2011;
Dijcks, 2013), достоверность (Schroeck и соавт., 2012) и сложность (Agrawal
и соавт., 2012)1.
В связи с этим в отчете Международной корпорации данных (IDC, полное
название International Data Corporation) за 2011 год (Gantz и Reinsel, 2011)
было дано определение больших данных «4V»: «Технологии больших данных
описывают новое поколение технологий и архитектур, сконструированных для
экономичной добычи ценности из очень крупных объемов разнообразных данных путем обеспечения высокоскоростного сбора, обнаружения и/или анализа».
Это определение очерчивает новую характеристику больших данных – ценность, которая означает возможность создания больших преимуществ для
организаций, обществ и потребителей за счет анализа данных. В противоположность этому Бейер и Лэйни (Beyer и Laney, 2012) расширили модель
«3V», введя в качестве четвертого «V» достоверность (veracity). Достоверность включает в себя вопросы о качестве, надежности и неопределенности
зарегистрированных данных и результата их анализа.
Наконец, в определении больших данных, внесенном Национальным институтом стандартов и технологий (NIST, от англ. National Institute of Standards and Technology) (Chang и Grady, 2015), вводится новая характеристика
больших данных – «вариабельность» (variability): «Большие данные состоят из
обширных наборов данных – в первую очередь с такими характеристиками, как
объем, разнообразие, быстрота и/или вариабельность, – которые нуждаются
в масштабируемой архитектуре, чтобы иметь возможность эффективно их
хранить и манипулировать ими». В других характеристиках вариабельность
определяется как изменчивость, которая порождает разброс смысла данных
в лексиконе, что потенциально может оказывать огромное влияние на обес
печение однородности данных.
Из приведенного выше обсуждения становится ясно, что найти общее
определение термина больших данных очень сложно. В связи с этим в работе
Де Мауро и соавт. (De Mauro, 2015) был рассмотрен краткий обзор определе1
Соответственно, value, veracity и complexity. Под ценностью данных понимаются
выгоды и преимущества, которые организации могут извлекать из своих данных. Под достоверностью (правдивостью) данных понимаются качество, точность,
целостность и надежность данных. Под сложностью данных понимается многогранная природа данных, включая их объем, разнообразие и быстроту, что может
затруднять их эффективную обработку и анализ. – Прим. перев.
22 Концепции больших данных
ний термина больших данных, целью которого является выработка консенсусного определения данного термина. Несмотря на множество определений,
предложенных с течением времени, определение термина больших данных
было расширено и адаптировано под различные контексты применения.
С этой целью были произведены новые модели, вводящие и комбинирующие
новые «V»:
виральность (Virality) означает способность данных нести в себе сообщение, которое может охватывать большое количество людей (например, путем размещения в социальных сетях);
визуализация (Visualization) означает возможность представлять данные графически, чтобы аналитик мог с первого взгляда понимать их
смысл и быстро делать вывод;
вязкость (Viscosity) означает способность добытой из данных информации вызвать у людей интерес и побуждать их к действию (призывать
к действию);
местонахождение (Venue) описывает происхождение данных, которые
могут собираться из многочисленных, распределенных и весьма разнородных источников.
2.2. Концепции науки о данных
Французский математик Ж. Анри Пуанкаре (J. Henri Poincare) говорил: «Наука
строится из фактов, как дом из камней. Но набор фактов – это не более наука,
чем груда камней – дом». Факты должны основываться на данных, а данные
приходится не только собирать, но и организовывать и структурировать,
а также находить и выстраивать взаимосвязи между ними, чтобы делать их
полезными в научном процессе. В этих операциях компьютеры могут играть
очень важную и новую роль. Интегрированное использование методов и технологий вычислительной науки и процессов научного открытия привело
науку к новой эре, когда научные методы существенно изменились благодаря использованию вычислительных методов, новых стратегий управления
данными и их анализа, что привело к созданию так называемой электронной
науки (e-science) (Bell и соавт., 2009).
Тысячу лет назад наука была эмпирической, и ее главной целью было
описание естественных явлений. Несколько сотен лет назад зародилась ее
теоретическая ветвь, которая начала использовать модели и делать обобщения. Благодаря компьютерам в последние несколько десятилетий возникла вычислительная ветвь науки, основанная на симулировании сложных
явлений. Сегодня электронная наука объединила теорию, эксперименты
и симуляции, реализовав процесс научного открытия, интенсивный по
привлечению данных.
Крупные наборы данных генерируются цифровыми устройствами и приборами или симуляторами. Затем они обрабатываются путем анализа дан-
Концепции науки о данных 23
ных, а результирующая информация/знания хранятся в компьютерных архивах и в интернете. В рамках электронной науки специалисты анализируют
данные, используя методы науки о данных. В книге «Четвертая парадигма» (Hey и соавт., 2009) авторы утверждают, что эта новая парадигма представляет собой не только сдвиг в методах научных исследований, но и сдвиг
в мышлении людей. Они заявили, что единственным способом справиться
с вызовами новой парадигмы является разработка нового поколения вычислительных систем управления, анализа и визуализации данных. Этот
новый класс вычислительных систем представлен архитектурами высокопроизводительных вычислений (HPC, от англ. high-performance computing),
системами облачных вычислений и крупномасштабными распределенными
платформами. В книге «Четвертая парадигма» приводится недавний доклад
Джима Грея (Jim Gray), и в последнем абзаце доклада четко резюмирована
мысль о том, как вычисления изменили науку: «Хотел бы отметить, что
из-за влияния информационных технологий в науке меняется почти все. Экспериментальная, теоретическая и вычислительная наука – все они затронуты
лавиной данных, и возникает четвертая, “интенсивная по привлечению данных” парадигма науки. Цель состоит в том, чтобы создать мир, в котором вся
научная литература будет находиться онлайн, все научные данные размещены
онлайн, и они будут взаимодействовать друг с другом. Для того чтобы это
произошло, необходимо очень много новых инструментов».
На самом деле ключевой дисциплиной для этой новой парадигмы, которая
поддерживает научное открытие за счет интенсивного привлечения компьютеров, алгоритмов и данных, является наука о данных. Наука о данных
сочетает в себе вычислительную науку, прикладную математику и техники
анализа данных, чтобы предоставлять полезную информацию, основываясь
на крупных объемах данных. Эта дисциплина позволяет улучшать качество
работы за счет принятия решений на основе действенной информации, добываемой из крупных наборов данных благодаря использованию алгоритмов сбора, очистки, преобразования и анализа (больших) данных. Хотя нау
ка о данных – и молодая дисциплина, ее история началась в 1960-х годах.
В 1966 году Питер Наур (Peter Naur) ввел термин «даталогия» (datology) как
«наука о природе данных и их использовании», а в 1974 году он применил
термин «наука о данных» как «наука о работе с данными, после того как они
определены», тогда как связь данных с тем, что они представляют, делегируется другим областям и наукам». Однако первой научной конференцией,
упомянувшей науку о данных в своем названии, стала конференция IFCS
(International Federation of Classification Societies) по науке о данных, классификации и смежным методам, состоявшаяся в 1996 году. А в 2003 году был
основан журнал Journal of Data Science с целью развития и продвижения методов, вычислений и приложений науки о данных во всех научных областях.
Методы науки о данных имеют практическую пользу, когда имеется крупный объем данных и когда человеку слишком сложно обнаруживать и добывать закономерности вручную. Можно сказать, что в науке о данных алгоритмы играют роль уравнений. В ней сбор и накопление данных являются
24 Концепции больших данных
первыми шагами; однако эти шаги не ограничиваются традиционным сбором данных, который проводится в статистике. В науке о данных не только
используются традиционные методики сбора данных, но и применяются
уже имеющиеся данные, то есть данные, произведенные для других целей,
которые тщательно отобраны и предварительно обработаны и могут быть
использованы для анализа научных и деловых процессов. В целом наличие
большего количества данных всегда идет на пользу. Однако наличие правильных данных – это самое важное требование. По этой причине бóльшая
часть времени в процессах науки о данных (около 60–70 %) уходит на сбор
и подготовку данных.
В своем исследовании, посвященном науке о данных (Cao, 2017), Лонгбинг Цао резюмировал наиболее часто используемые определения исследователя данных (то есть ученого в области науки о данных, от англ. data
scientist). Национальный научный совет США (US National Science Board) дает
определение исследователей данных как ученых в области информационных
и вычислительных наук, инженеров баз данных и программного обеспечения
и программистов, дисциплинарных экспертов, кураторов и экспертов-аннотаторов, библиотекарей, архивариусов и других специалистов, которые имеют
решающее значение для успешного управления коллекцией цифровых данных.
В отчете Комитета по науке Национального совета по науке и технологиям
США (US Committee on Science of the National Science and Technology Council)
исследователи данных определяются как ученые, которые пришли из областей
информационных или вычислительных наук, но изучают предметную область
и могут становиться кураторами научных данных в дисциплинах и продвигают
искусство науки о данных. При этом в центре внимания находятся все части
жизненного цикла данных. Наконец, Объединенный комитет по информационным системам (Joint Information Systems Committee) дал определение
исследователей данных как людей, которые работают там, где проводятся
исследования, или, в случае персонала центров обработки данных, в тесном
сотрудничестве с создателями данных и могут участвовать в творческом
изыскании и анализе, предоставляя другим сотрудникам возможность работать с цифровыми данными, а также в разработке в области технологий
управления базами данных.
2.2.1. Процессы науки о данных
Процессы науки о данных призваны преобразовывать задачу в решение путем привлечения данных, вычислительных техник и инфраструктур, а также
техник анализа. Процесс науки о данных включает в себя следующие ниже
шаги:
1)
2)
3)
4)
постановка задачи;
сбор необходимых данных;
обработка данных с целью проведения анализа;
разведывательный анализ данных;
Концепции науки о данных 25
5) проведение углубленного анализа;
6) сообщение результатов анализа.
Постановка задачи: первым шагом в рабочем процессе науки о данных
является понимание и определение задачи. Поставив перед собой четко
определенную задачу, исследователи данных смогут разработать эффективную модель, надлежаще и успешно ее выполняющую. Они должны уметь
переводить вопросы, связанные с данными, в нечто действенное. На этом
шаге выявляются слишком амбициозные или двусмысленные входные данные, поступающие от людей, ставящих задачи, и ведется работа со скудными
входящими данными, чтобы генерировать действенные результаты. В конце
этого шага важно получать всю информацию и контекст, необходимые для
урегулирования и решения задачи.
Сбор необходимых данных: сбор данных представляет собой работу по
накоплению и измерению информации об интересующих целевых переменных решаемой задачи. Данные позволяют исследователю отвечать на
релевантные вопросы и предлагать будущие результаты. Правильные данные необходимы для того, чтобы предоставлять сведения, требуемые для
превращения задачи в решение. На этом шаге нужно разбираться в том,
какие именно данные нужны, и отыскивать способы их сбора из одного или
нескольких источников, таких как файловые системы, системы управления
базами данных (СУБД), системы управления взаимоотношениями с клиентами (CRM, от англ. customer relationship management) и/или веб-страницы.
Обработка данных с целью проведения анализа: цель предобработки
и очистки сырых данных состоит в обеспечении того, чтобы на шаге проведения анализа данные находились в правильном формате. На этом шаге должны выявляться и соответствующим образом обрабатываться несостыковки
и ошибки. Иногда данные бывают беспорядочными, содержат пропуски или
ошибки, которые могут испортить анализ. После того как данные очищены
и правильно предобработаны, они готовы к разведывательному анализу.
Разведывательный анализ данных: получив в распоряжение крупный
объем хорошо структурированных данных, должен быть проведен их разведывательный анализ (EDA, от англ. exploratory data analysis), чтобы выявить
ценные сведения, которые пригодятся на следующем шаге рабочего процесса
науки о данных. Разведывательный анализ используется для просмотра, визуальной экспертизы наборов данных и резюмирования их главных характеристик. На этом шаге, как правило, помогают методы визуализации данных.
Они определяют способы обработки и наглядного представления данных,
облегчая исследователям данных работу по обнаружению закономерностей,
выявлению аномалий или проверке гипотез.
Проведение углубленного анализа: на этом шаге создаются модели данных, для чего исследователи данных используют техники глубокой переработки данных (data mining)1, машинного обучения, статистические методы
1
То есть процесс, применяемый для добычи полезных сведений из крупного набора
любых сырых данных. Ср. с переработкой сырья в промышленности. – Прим. перев.
26 Концепции больших данных
и математические алгоритмы, чтобы добывать полезные сведения и предсказания из данных. На этом шаге используются аналитические инструменты науки о данных, чтобы перерабатывать данные и отыскивать в них
всевозможные сведения. Здесь также генерируются предсказательные либо
описательные модели данных.
Сообщение результатов анализа: этот заключительный шаг рабочего
процесса науки о данных должен реализовываться для изложения результатов всего процесса. Безусловно, очень важно, чтобы пользователи понимали
причину, по которой обнаруженные сведения играют серьезную роль и имеют практическую пользу. Правильная коммуникация в рабочих процессах
науки о данных придает завершенный вид обнаруженным моделям и воплощает в жизнь решения поставленной задачи. При этом исследователям
данных, возможно, придется задействовать инструменты визуализации.
Наглядная демонстрация полученных результатов будет подчеркивать их
ценность.
2.2.2. Навыки, специфичные для науки о данных
Как представляется, наука о данных наиболее тесно связана с такими областями, как системы управления базами данных (СУБД) и вычислительная
наука. Здесь требуется несколько видов навыков, в том числе нематематических. Наиболее важные из них таковы:
усвоение прикладной области знания;
общение с владельцами/пользователями данных;
внимание к качеству данных;
знание способов внутреннего представления данных;
управление преобразованием и анализом данных;
знание методов визуализации и наглядного представления данных;
учет этических вопросов.
Совсем недавно получил распространение термин «X-информатика». Им
обозначаются новые области исследований, в которых широко используются
вычислительные методы и инструменты. Одной из наиболее заметных из
них является биоинформатика. Эти области имеют общие элементы: они
ориентированы на приобретение новых подходов и моделей путем применения информатикоориентированных подходов к существующим научным
областям знаний. Они также имеют общую методологию науки о данных,
которая включает в себя генерацию крупных объемов данных, их масштабируемый анализ и обнаружение знаний из этих источников данных. Другими примерами X-информатики являются астроинформатика, информатика
мозга, информатика здоровья, информатика поведения, медицинская информатика, социальная информатика и городская информатика.
Наука о данных предусматривает управление, обработку и анализ крупных
объемов данных с целью обнаружения в них моделей, трендов и законо-
Концепции науки о данных 27
мерностей. Как мы обсудим в этой книге, инфраструктура вычислительных
облаков и высокопроизводительных вычислений обеспечивает мощности,
которые также подкрепляют эту науку за счет привлечения параллельных
инструментов и языков программирования. Междисциплинарные навыки
и знания в области высокопроизводительных вычислений и науки о данных
помогают раскрывать знания, содержащиеся во все более крупных, сложных
и трудных для обработки наборах данных, которые сегодня генерируются
во многих областях науки и бизнеса. Для достижения поставленных целей –
решения новых задач и разработки новых научных дисциплин, сосредоточенных вокруг данных, – необходима интеграция используемых исследователями данных масштабируемых вычислительных ресурсов, программных
инструментов, сетей, данных, управления информацией и алгоритмов машинного обучения и анализа данных.
Мир неуклонно движется к тому, чтобы стать обществом под управлением данными, в котором данные являются самым ценным активом. Быстрое
и неуклонное распространение больших данных и больших вычислений способствовало внедрению машинного обучения и науки о данных в нескольких
прикладных областях. Эффективные и оптимизированные распределенные
и параллельные платформы резидентного и дискового исполнения сложных
вычислительных заявок по анализу данных имеют решающее значение для
противостояния этому вызову. Более того, для того чтобы иметь возможность
предлагать сложные абстрактные структуры и операции, которые должны
быть близки к сформулированным задачам, формату и организации данных,
необходимы языки программирования, специфичные для науки о данных
и ориентированные на данные.
Серьезная трудность, с которой приходится сталкиваться при выполнении заданий по разведывательному анализу, обработке и анализу данных,
используемых в приложениях науки о данных, заключается во времязатратных процессах выявления и тренировки адекватной модели. Следовательно,
наука о данных представляет собой высокоитеративный, разведывательный процесс, в котором большинство исследователей упорно работают над
отысканием наилучшей модели или алгоритма, удовлетворяющего требованиям стоящей перед ними задачи обработки данных. На практике не сущест
вует единой модели, которая подходила бы ко всем решениям. И действительно, нет ни одной модели или алгоритма, способного справиться со всеми
разновидностями наборов данных и изменениями в них, которые могут происходить с течением времени. Для того чтобы достигать равновесия между
точностью и обобщаемостью, все алгоритмы анализа данных и машинного
обучения нуждаются в пользовательских параметрах. Эта работа, именуемая
тонкой настройкой параметров, представляет собой длительный и сложный
процесс, требующий тщательного подхода. Привлечение машинного интеллекта к анализу данных играет большую роль в решении сложных и трудноразрешимых задач. И наука о данных создает новую эру для открытий в этом
направлении.
28 Концепции больших данных
2.3. Хранение больших данных
Вместе с экспоненциальным ростом требований к хранению данных возникла необходимость в использовании систем распределенного хранения,
состоящих из нескольких взаимосвязанных серверов. В сценариях с использованием распределенных сетей реляционные системы управления
базами данных (реляционные СУБД) имеют ограничения по масштабируемости, что значительно снижает эффективность запросов и анализа
(Abramova и соавт., 2014). Масштабируемость применительно к системам
хранения данных измеряется способностью системы повышать производительность при увеличении числа узлов хранения, при этом обеспечивая некоторые другие важные свойства, такие как эффективность, отказо
устойчивость, доступность и состыкованность. В частности, как показано
на рис. 2.1, принято использовать два подхода к масштабированию (Singh
и Reddy, 2015):
вертикальное масштабирование: оно заключается в увеличении ресурсов (процессоров, оперативной памяти, диска и сетевого ввода-вывода) одного сервера, что делает его быстрее и мощнее;
горизонтальное масштабирование: оно заключается в добавлении в систему большего количества узлов хранения/вычислений, чтобы распределять между ними рабочую нагрузку.
Вертикальное масштабирование
(масштабирование вверх)
Горизонтальное масштабирование
(масштабирование вширь)
Рис. 2.1 Вертикальное масштабирование
по сравнению с горизонтальным масштабированием
В контексте систем распределенного хранения большинство реляционных СУБД не имеют возможности горизонтального масштабирования на
многочисленные серверы, что затрудняет хранение и управление огромными объемами данных, ежедневно производимыми многочисленными приложениями, и приводит к потребности в альтернативных технологических
решениях по хранению, управлению и опрашиванию огромных объемов данных. В этом сценарии в последние несколько лет стали популярны системы
управления базами данных «не только SQL» (NoSQL), которые изначально
назывались нереляционными СУБД, как альтернатива или дополнение к реляционным СУБД с целью обеспечения горизонтальной масштабируемости
Хранение больших данных 29
простых операций баз данных по чтению/записи, распределенных по многочисленным серверам (Cattell, 2011).
По сравнению с реляционными СУБД, системы управления базами данных
NoSQL обычно более гибки и масштабируемы, так как они способны прозрачно задействовать преимущества новых узлов, не требуя ручного распределения информации или дополнительного управления со стороны СУБД
(Stonebraker, 2010). Они также сконструированы так, чтобы обеспечивать
автоматическое распределение данных и отказоустойчивость (Gajendran,
2012). Базы данных NoSQL допускают хранение скалярных значений (например, чисел, строковых литералов), двоичных объектов (например, изображений, видео) или более сложных структур (например, графов).
В соответствии с моделью данных системы управления базами данных
NoSQL можно разделить на четыре главные категории: хранилища ключейзначений, документные хранилища, столбцовые хранилища и графовые хранилища.
СУБД ключей-значений предоставляют механизмы для хранения данных
в виде пар «ключ-значение» на многочисленных серверах. В таких СУБД может использоваться распределенная хеш-таблица (DHT, от англ. distributed
hash table), позволяющая реализовывать масштабируемую структуру индексирования, в которой значение элемента данных отыскивается по ключу.
Документные СУБД предназначены для управления данными, хранящимися в документах разных форматов (например, в формате JSON). В таких
хранилищах каждому документу назначается уникальный ключ, используемый для его идентификации и извлечения. Следовательно, документные
хранилища расширяют хранилища ключей-значений, поскольку позволяют
хранить, извлекать и управлять не отдельными значениями, а полуструктурированной информацией. В отличие от хранилищ ключей-значений, документные хранилища поддерживают вторичные индексы и несколько типов документов в каждой базе данных, а также предоставляют механизмы
выполнения запросов к коллекциям на основе нескольких ограничений на
значения атрибутов (Cattell, 2011).
Столбцовые СУБД (также именуемые хранилищами расширяемых записей) предоставляют механизмы хранения расширяемых записей, которые
могут разбиваться по нескольким серверам. В базах данных этого типа записи считаются расширяемыми, поскольку новые атрибуты могут добавляться
на основе каждой записи. Расширяемые хранилища записей обеспечивают
как горизонтальную разбивку (хранение записей на разных узлах), так и вертикальную разбивку (хранение частей одной записи на разных серверах).
В некоторых системах столбцы таблицы могут распределяться по нескольким
серверам за счет использования групп столбцов, где заранее определенные
группы указывают на то, какие столбцы лучше всего хранить вместе.
Графовые СУБД широко распространены в качестве систем хранения и выполнения запросов к данным, которые могут быть представлены не в виде
таблиц или документов, а в виде графов. В частности, граф представляется
как множество вершин, ребер и свойств, которые сложно хранить в реляци-
30 Концепции больших данных
онной СУБД. Графовые хранилища данных позволяют эффективно выполнять большой набор запросов к графам без необходимости использовать дорогостоящие операции соединения таблиц. Благодаря этому можно ускорять
исполнение графовых алгоритмов, например используемых для отыскания
сообществ, степеней, центральности, расстояний, путей и других видов взаи
мосвязей между узлами.
Краткое сравнение систем управления базами данных NoSQL приведено
в табл. 2.1. Более подробное сравнение также см. в работах Хешема (Hashem
и соавт., 2015), Лоуренко (Lourenco и соавт., 2015) и Монирузамана (Moniruzzaman и Hossain, 2013). Последующие подразделы посвящены рассморению
наиболее важных систем управления базами данных NoSQL.
2.3.1. MongoDB
MongoDB1 – это современная СУБД, разработанная для поддержки интернет- и веб-приложений. Она работает как документная система управления базами данных NoSQL, предлагая высокоэффективную модель данных
и стратегии обеспечения долговечности, в которых приоритет отдается широким возможностям по чтению и записи. В дополнение к этому MongoDB
отличается бесшовной масштабируемостью, обеспечивая легкодостижимое
расширение с помощью механизмов автоматического восстановления пос
ле отказов. Одной из отличительных характеристик MongoDB является ее
документная модель данных, которая упрощает разработку, обеспечивая
встроенную поддержку неструктурированных данных. В отличие от традиционных СУБД, MongoDB устраняет необходимость в дорогостоящих и времязатратных миграциях при эволюционировании требований к приложениям.
СУБД MongoDB представляет документы в JSON-подобном формате, именуемом BSON, то есть легковесном, быстром и пересекаемом формате, хорошо сочетающемся с современными методологиями объектно ориентированного программирования. BSON служит форматом сетевой передачи для
документов MongoDB. Хотя поначалу BSON может напоминать двоичный
большой объект (BLOB, от англ. binary large object), он обладает важнейшим отличием: MongoDB понимает внутреннюю структуру объектов BSON.
Следовательно, MongoDB может углубляться в объекты BSON, даже если
они вложены друг в друга, с помощью точечной нотации. Эта способность
позволяет MongoDB конструировать индексы и сопоставлять объекты с запросными выражениями, охватывая как верхнеуровневые, так и вложенные
ключи BSON.
Помимо своих фундаментальных способностей, MongoDB может похвас
таться всесторонней поддержкой обогащенных запросов и полных индексов.
Это отличает ее от других документных систем управления базами данных,
1
См. https://www.mongodb.com.
Cassandra
Столб
HDFS
CFS
Да
Да
Да
Высокая
Высокая
Да
Java
Apache2
Redis
КЗ
MEM
FS
Да
Нет
Да
Да
Да
Да
Высокая Высокая
Высокая Высокая
Да
Да
Java
Ansi-C
Apache2 BSD
Hbase
Столб
HDFS
MongoDB
Док
MEM
FS
Да
Да
Да
Да
Да
Да
Высокая
Высокая
Высокая
Высокая
Да
Да
Java Python Go Ruby C++
Проприетарная
AGPL3
Bigtable
Столб
GFS
Neo4j
Граф
MEM
FS
Нет
Да
Да
Высокая
Высокая, переменная
Да
Java
GPL3
Примечание. FS: файловая система; MEM: резидентно; КЗ: ключ-значение; Док: документная; Столб: столбцовая; Граф: графовая.
Тип
Хранение данных
DynamoDB
КЗ
MEM
FS
MapReduce
Да
Долговечность
Да
Репликация
Да
Масштабируемость
Высокая
Производительность Высокая
Высокая доступность Да
Язык
Java
Лицензия
Проприетарная
Таблица 2.1. Сравнение некоторых систем управления базами данных NoSQL
Хранение больших данных 31
32 Концепции больших данных
которые в обработке сложных запросов опираются на отдельный серверный
слой. В дополнение к этому в MongoDB реализованы такие важные функциональные средства, как автоматическая сегментация (шардирование),
репликация и усовершенствованное управление хранением. Поскольку популярность MongoDB продолжает неуклонно расти, а в ее базах данных хранится масса конфиденциальной пользовательской информации, возникают
проблемы, связанные с конфиденциальностью данных, приватностью и безопасностью системы. Стоит отметить, что изначально, когда MongoDB была
только-только разработана, тема безопасности не получила первостепенного
внимания у ее создателей.
2.3.2. Google Bigtable
Google Bigtable1 – это популярное табличное хранилище. Оно построено
поверх файловой системы Google (GFS, от англ. Google File System) и способно хранить до петабайта данных, поддерживая таблицы с миллиардами
строк и тысячами столбцов. Благодаря высокой пропускной способности
чтения и записи с низкой задержкой Bigtable является идеальным источником данных для пакетных операций MapReduce (Chang и соавт., 2008)
и других приложений, ориентированных на обработку и анализ крупных
объемов данных.
Данные в Bigtable хранятся в разреженных, распределенных, долговечных
и многомерных таблицах, состоящих из строк и столбцов. Каждая строка
индексируется по одному строчному ключу, а связанные столбцы обычно
группируются в наборы, именуемые семействами столбцов. Генерический
столбец идентифицируется семейством столбцов и квалификатором столбцов, который представляет собой уникальное имя в пределах семейства
столбцов. Каждое значение в таблице индексируется кортежем <строчный
ключ, столбцовый ключ, временная метка>. Для улучшения масштабируемости и балансировки нагрузки на запросы данные упорядочиваются по
строчному ключу, а диапазон строк таблицы динамически разбивается на
смежные блоки, именуемые табличками (tablet). Эти таблички распределяются между разными узлами кластера Bigtable (то есть серверами-табличками). Для улучшения балансировки нагрузки сервер-мастер Bigtable может
разбивать большие таблички и объединять меньшие, перераспределяя их
между узлами по мере необходимости. Для обеспечения живучести данных
Bigtable хранит их в файловой системе Google и защищает от аварийных ситуаций с помощью репликации и резервного копирования данных. Bigtable
может использоваться в приложениях посредством многочисленных клиентов, в том числе адаптированной версии стандартного клиента Cloud Bigtable
HBase для нереляционной, распределенной СУБД Apache HBase, принятой
в качестве отраслевого стандарта.
1
См. https://cloud.google.com/bigtable/.
Хранение больших данных 33
2.3.3. HBase
С появлением интернета стали генерироваться огромные объемы структурированных и полуструктурированных данных в самых разных формах,
таких как сообщения электронной почты, JSON-, XML- и CSV-файлы. Подобное распространение полуструктурированных данных происходит по всему
миру и представляет собой серьезный вызов с точки зрения их хранения
и обработки.
Нереляционная, распределенная СУБД Apache HBase1 с открытым исходным кодом стала технологическим решением, быстро принятым на вооружение с целью преодоления этих вызовов. В основе HBase лежит представленная в разделе 2.3.2 распределенная система хранения Google Bigtable.
Подобно системе Bigtable, использующей распределенное хранение данных,
обеспечиваемое файловой системой Google, СУБД Apache HBase использует фреймворк Hadoop и HDFS, то есть распределенную файловую систему
Hadoop (от англ. Hadoop Distributed File System), чтобы обеспечивать аналогичные способности. Выдающиеся компании, такие как Facebook, Netflix,
Yahoo!, Adobe и Twitter, используют HBase в качестве стержневого компонента своих систем. Первостепенная задача HBase состоит в размещении
у себя крупномасштабных таблиц, содержащих миллиарды строк и миллионы столбцов, путем задействования кластеров товарного аппаратного
обеспечения.
Хотя HBASE является столбцовым хранилищем, использующим модель
данных, во многом сконструированную по образу и подобию Google Bigtable, в своей основе оно работает как хранилище ключей-значений, в котором данные хранятся и извлекаются на основе уникальных ключей. Модель
ключ-значение позволяет быстро обращаться к конкретным точкам данных.
Каждый ключ состоит из несовпадающих компонентов, включая строчный
ключ, семейство столбцов, квалификатор столбцов и временную метку. Такая многомерная структура позволяет хранить несколько версий данных
(с помощью временной метки), обеспечивая историческую перспективу изменения информации во временной динамике. В HBase строчные ключи
отсортированы лексикографически, что позволяет быстро исполнять диапазонные запросы, чего не хватает реляционным СУБД без гарантии соблюдения порядка сортировки. Более того, в HBase принят разреженный подход
к хранению данных, то есть HBase не хранит пустые или нулевые значения,
тем самым оптимизируя пространство для хранения.
В HBase таблицы не имеют схемы, а семейства столбцов определяются при
создании таблицы, а не отдельных столбцов. Конструкция системы HBase
рассчитана на линейное масштабирование и состоит из стандартных таблиц,
строки и столбцы которых аналогичны тем, которые встречаются у традиционных СУБД. В каждой таблице должен быть определен первичный ключ, который должен использоваться при всех попытках доступа к таблицам HBase.
1
См. https://hbase.apache.org/.
34 Концепции больших данных
HBase опирается на централизованную сторожевую службу поддержания
конфигурационной информации ZooKeeper1 с целью высокопроизводительной координации и хорошо интегрируется с СУБД Hive, которая работает как
механизм запросов для пакетной обработки больших данных, что позволяет
создавать отказоустойчивые приложения по обработке больших данных. Более того, HBase можно конфигурировать под использование языково-независимой системы сериализации данных Apache Avro2, которая обеспечивает
компактный формат двоичных данных. Avro разработана специально для
высокоэффективного хранения и передачи данных. Привлечение системы
Avro позволяет эффективно хранить и извлекать данные в таблицах HBase,
обеспечивая совместимость с разными языками программирования и упрощая интеграцию с другими системами в конвейере обработки данных.
2.3.4. Redis
Redis3 – это популярное резидентное хранилище данных с открытым исходным кодом, используемое в качестве базы данных, кеша, брокера сообщений
и механизма потоковой обработки. Оно позволяет выполнять разные типы
атомарных операций, такие как добавление строкового литерала, приращение значения в хеше, вставка элемента в список, вычисление пересечения, объединения и/или разности множеств, а также извлечение элемента
с максимальным баллом из упорядоченного множества. С целью повышения
производительности хранилище Redis работает с резидентными наборами
данных. Между тем в зависимости от варианта использования оно может
обеспечивать долговечность данных, периодически скачивая их на диск.
Redis также включает в себя другие интересные функциональности, такие как транзакционные операции, службы публикации/подписки, ключи
с ограниченным временем жизни и автоматическое преодоление отказа.
Резидентное хранилище Redis написано на языке ANSI C и работает на большинстве POSIX-систем, таких как Linux, BSD и MacOS, без внешних зависимостей. Его рекомендуется использовать в двух операционных системах – Linux
и MacOS, так как оно разработано и тестировалось в основном на них. Redis
может работать в системах на базе Solaris, хотя ее поддержка там ограничена.
Также нет официальной поддержки Microsoft Windows. Однако в целях разработки Redis можно установить и в Windows с помощью подсистемы WSL2
(Windows Subsystem for Linux), которая позволяет запускать исполняемые
файлы Linux нативно прямо в Windows. Для разработчиков Redis доступно
несколько клиентов с открытым исходным кодом, поддерживающих большой набор языков программирования, включая Java, Python, PHP, C, C++, C#,
JavaScript, Node.js, Ruby, R и Go.
1
2
3
См. https://zookeeper.apache.org/.
См. https://avro.apache.org/.
См. https://redis.io/.
Хранение больших данных 35
Поскольку резидентное хранилище Redis поддерживает многочисленные
типы значений и структуры данных, оно используется во многих вариантах
использования, например для передачи сообщений с помощью технологий
публикации/подписки, хранения произвольных данных, требующих быст
рого доступа с нескольких серверов, и управления очередями сообщений/
заданий. Наконец, Redis обычно используется для хранения веб-сеансов,
чтобы реализовывать так называемые «липкие сеансы», которые позволяют
поддерживать логин пользователя даже на разных серверах, где размещен
один и тот же веб-сайт. Например, при доступе к Facebook балансировщик
нагрузки может перенаправлять пользователей на сервер, отличный от того,
на котором они вошли на платформу. При использовании Redis пользователи
не будут терять свои существующие сеансы, так как архив сеансов используется коллективно между различными экземплярами веб-сайта.
2.3.5. DynamoDB
Amazon DynamoDB – это полностью управляемая услуга по управлению базами данных NoSQL, предоставляемая публичным облаком Amazon Web Services (AWS), которая разработана для обеспечения быстрой и предсказуемой
производительности с бесшовной масштабируемостью. DynamoDB хранит
данные в формате ключ-значение и идеально подходит для приложений,
которым требуется доступ к данным с низкой задержкой, таких как игровые
приложения, мобильные приложения и платформы электронной коммерции.
Одно из ключевых преимуществ DynamoDB перед конкурентами, такими
как Redis, заключается в возможности автоматического масштабирования
и балансировки нагрузки, что устраняет необходимость ручного вмешательства в управление ресурсами базы данных. Хотя Redis также обеспечивает
высокую производительность и низкую задержку, управляемая служба DynamoDB упрощает эксплуатационные сложности, позволяя разработчикам
сосредоточиваться не на управлении инфраструктурой, а на разработке приложений.
DynamoDB предлагает встроенные функциональности по обеспечению
безопасности, такие как шифрование в состоянии покоя и при передаче, мелкозернистый контроль доступа и интеграция с веб-сервисом AWS Identity and
Access Management (IAM). Эти меры обеспечения безопасности крайне важны для приложений, работающих с конфиденциальными данными, и обес
печивают предприятиям и разработчикам нужный уровень уверенности.
В отличие от других систем управления базами данных, которые предлагают
функциональности по обеспечению безопасности, интеграция DynamoDB
внутрь экосистемы AWS повышает ее доступность и простоту управления.
DynamoDB предлагает полностью управляемое резервное копирование,
возможности восстановления, многорегиональную синхронизацию и синхронизацию с несколькими серверами-мастерами, обеспечивая высокую
доступность и живучесть данных. Более того, данная услуга обеспечивает
36 Концепции больших данных
высокоуровневую интеграцию с другими услугами AWS, такими как AWS
Lambda, Amazon S3 и Amazon Kinesis, что позволяет разработчикам создавать мощные бессерверные архитектуры. Такой уровень интеграции совершенствует процесс разработки и позволяет создавать быстро реагирующие
и эффективные по стоимости приложения.
В целом, несмотря на то что у каждой системы управления базами данных
NoSQL есть свои сильные стороны, сочетание бесшовной масштабируемости,
надежных средств защиты, управляемых услуг и тесной интеграции в экосистему AWS делает DynamoDB привлекательным выбором для разработчиков
и предприятий, опирающихся в своих приложениях на инфраструктуру AWS.
2.3.6. Apache Cassandra
Apache Cassandra1 – это распределенная СУБД, которая обеспечивает высокую доступность без единой точки отказа. Apache Cassandra была разработана в Facebook под влиянием идей, положенных в основу Amazon Dynamo
и Google BigTable, и предназначена для управления крупными объемами
данных в нескольких центрах обработки данных и зонах доступности облака.
В Cassandra используется кольцевая архитектура без сервера-мастера,
в которой все узлы играют идентичную роль, что позволяет любому авторизованному пользователю подключаться к любому узлу в любом центре
обработки данных. Это очень простая и гибкая архитектура, которая позволяет добавлять узлы без перебоев в работе услуги. Процесс распределения данных между узлами очень прост, и от разработчиков не требуется
никаких программных операций. Поскольку все узлы взаимодействуют друг
с другом одинаково, у Cassandra нет единой точки отказа, что обеспечивает постоянную доступность данных и бесперебойную работу услуги. Более
того, Cassandra предоставляет адаптируемую услугу репликации данных,
которая позволяет реплицировать данные между узлами, организованными
в кольцо. Благодаря этому в случае отказа узла одна или несколько копий
необходимых данных будут доступны на других узлах. Репликация может
быть сконфигурирована под работу в одном центре обработки данных, во
многих центрах обработки данных и в нескольких зонах доступности облака. В Cassandra особое внимание уделяется производительности и масштабируемости, поэтому она достигает почти линейного ускорения, то есть
производительность операций в секунду (OPS, от англ. operations per second)
можно увеличивать путем добавления новых узлов (например, если два узла
способны обрабатывать 10 000 операций в секунду, то четыре узла будут поддерживать почти 20 000 операций в секунду и т. д.).
Многие компании уже успешно развернули Apache Cassandra и извлекают
из нее пользу, в том числе Apple (75 000 узлов, хранящих более 10 Пб данных),
китайская поисковая система Easou (270 узлов, 300 Тб и более 800 млн запро1
См. http://cassandra.apache.org/.
Хранение больших данных 37
сов в день), eBay (более 100 узлов и 250 Тб), Netflix (2500 узлов, 420 Тб и более
1 трлн запросов в день), Instagram, Spotify и Rackspace.
2.3.7. Графовая система управления
базами данных Neo4j
Во многих случаях требуется хранить взаимосвязи данных, которые относятся к ассоциациям между различными сущностями или узлами. Взаимо
связь данных описывает, в каком отношении два узла находятся с друг с другом, и может иметь свойства, характеризующие эту взаимосвязь. Например,
в графе социальной сети узлы могут представлять пользователей, а взаимо
связи – такие отношения, как «друг», «подписчик» или «замужем за».
Если требуется учитывать реально-временные взаимосвязи между данными (например, создавать запросы с использованием взаимосвязей между
данными), то системы управления базами данных NoSQL – не лучший выбор. СУБД на основе взаимосвязей или графов были созданы, по сути дела,
для естественной поддержки операций на данных, в которых используются
взаимосвязи данных. Графовые СУБД представляют собой новую и мощную
технику моделирования данных, в рамках которой данные хранятся не в таб
лицах, а в графовых моделях (Rodriguez и Neubauer, 2010), что придает ряд
преимуществ при хранении и извлечении данных, соединенных сложными
взаимосвязями.
Среди нескольких графовых моделей данных, таких как OrientDB, Virtuoso,
Allegro, Stardog и InfiniteGraph, мы остановились именно на Neo4j. Neo4j – это
графовая система управления базами данных NoSQL с открытым исходным
кодом, реализованная на Java и Scala, которая считается самой популярной
из используемых сегодня графовых СУБД. Исходный код Neo4j и отслеживание вопросов по ней доступны на GitHub, где существует большое сообщество
поддержки. В настоящее время ее используют многочисленные организации, работающие в различных отраслях, включая аналитику программного
обеспечения, научные исследования, управление проектами, рекомендации
и социальные сети.
В графовой модели Neo4j каждый узел содержит список записей-взаимосвязей, которые ссылаются на другие узлы и дополнительные атрибуты
(например, временные метки, метаданные и пары ключ-значение). Каждая запись-взаимосвязь должна иметь имя, направление, начальный узел
и конечный узел, а также может содержать дополнительные свойства. Как
узлам, так и взаимосвязям может назначаться одна или несколько меток.
В частности, такие метки могут использоваться для представления ролей,
которые узел играет в графе (например, пользователь, адрес, компания), или
для привязки индексов и ограничений к группам узлов.
Более того, кластеры Neo4j рассчитаны на высокую доступность и горизонтальное масштабирование чтения с помощью репликации мастер-слуга.
38 Концепции больших данных
Что касается производительности, то Neo4j в тысячи раз быстрее SQL при
выполнении операций обхода. Операция обхода состоит из посещения набора узлов графа путем перемещения по взаимосвязям (например, отыскание
потенциальных друзей в социальной сети на основе дружбы пользователей).
С помощью этой операции графовые модели позволяют учитывать только
необходимые данные, не выполняя дорогостоящих операций группировки,
как это делается в реляционных СУБД при операциях соединения (Vukotic
и соавт., 2015). Запросы в Neo4j пишутся посредством декларативного языка
на основе SQL под названием Cypher, с помощью которого принято описывать шаблоны в графах. Cypher является относительно простым, но очень
мощным языком, который позволяет легко выполнять запросы к сложным
графовым базам данных.
2.3.8. Сводные соображения о хранилищах NoSQL
Выбор оптимальной СУБД для создания приложения по обработке больших
данных требует учета нескольких моментов. Для того чтобы решить, какой
тип СУБД выбрать, вероятно, первым делом необходимо рассмотреть классы
запросов, которые будут делаться. Так, графовые СУБД, вероятно, являются
наилучшим решением по представлению высокосвязных данных и выполнению запросов к ним (например, данных, собранных в социальных сетях) или
тех, которые имеют сложные взаимосвязи и/или динамическую схему. В случаях, когда анализируются неграфовые данные, использование графовых
СУБД может приводить к действительно низкой производительности. В связи
с этим в табл. 2.5 представлены краткие соображения по графовым СУБД.
Еще одним моментом, который следует учитывать при выборе оптимального технологического решения для СУБД, являются способности поддерживать состыкованность, доступность и терпимость к разделениям сети (CAP,
от англ. consistency, availability, partition tolerance), так как распределенные
системы управления базами данных NoSQL не могут полностью соответствовать правилам CAP. Теорема CAP, также именуемая теоремой Брюера (Gilbert
и Lynch, 2002), по сути дела, гласит, что распределенная система не может
одновременно гарантировать все три следующих ниже свойства:
1) состыкованность (C), которая означает, что все узлы видят одни и те
же данные в одно и то же время;
2) доступность (A), которая означает, что каждый запрос будет получать
ответ в течение разумного времени;
3) терпимость к разделениям (P), которая означает, что система продолжает функционировать даже при произвольных разделениях сети из-за
потери или задержки связи между узлами.
Таким образом, если распределенная СУБД гарантирует состыкованность
и терпимость к разделениям, то она никогда не сможет обеспечить доступ-
Хранение больших данных 39
ность. И точно так же, если требуется полная доступность и терпимость к разделениям сети, то невозможно обеспечить состыкованность, по меньшей
мере не сразу. На деле распространение изменений данных одного узла на
других узлах в распределенной среде нуждается в определенном времени.
В течение этого времени копии будут взаимно несостыкованными, что может приводить к возможности чтения необновленных данных. В попытке
преодолеть это ограничение обычно предусматривается свойство окончательной состыкованности. Им гарантируется, что элемент системы рано или
поздно станет состыкованным. Это свойство является слабым, поэтому если
принятая на вооружение СУБД обеспечивает только окончательную состыкованность, разработчик должен знать, что существует возможность чтения
несостыкованных данных. Как следствие вместо традиционных свойств атомарности, состыкованности, изоляции и живучести (ACID, от англ. atomicity, consistency, isolation, duration), ассоциируемых с реляционными СУБД,
для систем управления базами данных NoSQL были определены свойства
BASE (Ganesh Chandra, 2015). В отличие от реляционных СУБД, в которых для
обеспечения целостности данных подчеркиваются атрибуты ACID, системы
управления базами данных NoSQL обычно подчиняются принципам BASE,
чтобы увеличивать масштабируемость и производительность. В частности,
принципы BASE таковы:
доступность в принципе (Basically available): означает, что система гарантирует доступность в соответствии с теоремой CAP. Благодаря ему
система всегда выглядит работающей, и это гарантируется распределением данных по нескольким системам хранения с высокой степенью
репликации;
мягкое состояние (Soft state): ввиду свойства окончательной состыкованности копии данных могут быть несостыкованными, и состояние
системы может меняться со временем даже без каких-либо входных
данных. При задействовании свойства мягкого состояния ресурсы, получаемые во время первоначальной загрузки, могут привлекаться для
последующих запросов;
окончательная состыкованность (Eventually consistent): если в приложении нет обновлений в течение заданного времени, то обновления
распространяются повсеместно.
В отличие от парадигмы ACID, модель BASE делает акцент на доступности. На самом деле состыкованность и изоляция часто приносятся в жертву,
поскольку трудно построить базу данных с ACID-свойствами, когда данные
распределены и синхронизация невозможна. Одна из ключевых концепций
BASE заключается в том, что забота о состыкованности данных входит в обязанности разработчиков, и она не должна возлагаться на СУБД. В заключение в табл. 2.2–2.5 приводится несколько сводных соображений по каждому
семейству СУБД.
40 Концепции больших данных
Таблица 2.2. Сводные соображения о СУБД ключей-значений
СУБД ключей-значений
Горизонтальное
Очень высокий масштаб обеспечивается за счет сегментации
масштабирование (шардирования)
Когда применять Простая схема данных или сценарий экстремальной скорости
(например, в режиме реального времени)
Компромиссы CAP Большинство решений отдают предпочтение состыкованности
перед доступностью
Плюсы
Простая модель данных; очень высокая масштабируемость;
к данным можно обращаться посредством языка запросов,
например SQL
Минусы
Некоторые запросы могут быть неэффективными или
ограниченными из-за сегментации (например, операции
соединения между сегментами); отсутствие стандартизации
API; сложность технического сопровождения; непригодность
для сложных данных
Таблица 2.3. Сводные соображения о столбцовых СУБД
Столбцовые СУБД
Горизонтальное
Очень высокие способности по масштабированию
масштабирование
Когда применять Когда необходимы состыкуемость и высокая
масштабируемость, без использования фронтенда
с индексированным кешированием
Компромиссы CAP Большинство решений отдают предпочтение состыкуемости
перед доступностью
Плюсы
Более высокая пропускная способность и более высокая
конкурентность при возможности разделения данных;
многоатрибутные запросы; данные естественным
образом индексируются по столбцам; поддержка
полуструктурированных данных
Минусы
Более высокая сложность; не подходит для взаимосвязанных
данных
Таблица 2.4. Сводные соображения о документных СУБД
Документные СУБД
Горизонтальное
Масштаб обеспечивается за счет репликации и сегментации
масштабирование (шардирования)
Когда применять Когда структура записи относительно невелика и можно
хранить все связанные с ней свойства в одном документе
Компромиссы CAP В большинстве случаев предпочтение отдается
состыкованности перед доступностью
Плюсы
Высокая масштабируемость и простая модель данных; обычно
поддерживаются вторичные индексы, несколько типов
документов в одной базе данных; вложенные документы;
поддержка механизмом MapReduce спонтанных запросов
Минусы
Окончательная состыкованность с ограниченной атомарностью
и изоляцией; запросы ограничены ключами и индексами;
не подходит для взаимосвязанных данных
Масштабируемый анализ данных 41
Таблица 2.5. Сводные соображения о графовых СУБД
Графовые СУБД
Горизонтальное
Плохое горизонтальное масштабирование
масштабирование
Когда применять Для хранения и опрашивания сущностей, связанных между
собой отношениями; примеры использования – социальные
сети и рекомендательные системы
Компромиссы CAP Обычно предпочтение отдается доступности перед
состыкованностью
Плюсы
Мощное моделирование данных и представление
взаимосвязей; локально индексированные связные данные;
простота и эффективность запросов
Минусы
Не подходит для неграфовых данных
2.4. Масштабируемый анализ данных
Обилие данных, хранящееся в цифровом виде, нуждается в разработке эффективных методов их анализа и добычи из них ценной информации. В частности, необходимо учитывать два главных тренда – технологический и методологический.
Технологический: огромные объемы данных собираются и складируются
в многочисленных хранилищах, разбросанных по всему миру. Данные
могут собираться и храниться на высоких скоростях в локальных базах
данных, из дистанционных источников или даже из нашей галактики.
В качестве примера можно привести наборы данных из областей медицинской визуализации, биоинформатики, дистанционного зондирования и нескольких цифровых обзоров неба. Как мы уже говорили,
из этого вытекает необходимость в надежных технологиях, стандартах
и протоколах хранения данных, сетевого взаимодействия и баз данных.
Методологический: огромные наборы данных трудно поддаются пониманию, и, в частности, невозможно напрямую улавливать присутствующие в них тренды и закономерности. Это прямое следствие растущей
сложности информации, главным образом ее многомерности. Например, вычислительная симуляция может генерировать терабайты данных в течение всего нескольких часов, тогда как анализ этих наборов
данных человеком может занимать несколько недель. По этой причине
бóльшая часть данных никогда не будет прочитана человеком, а будет
обрабатываться и анализироваться компьютером.
Вышесказанное можно резюмировать следующим образом: если несколько десятилетий назад главной проблемой был недостаток информации, то
сейчас, похоже, проблема заключается в (i) очень большом объеме информации
и (ii) связанной с этим сложности ее обработки с целью добычи значимых и полезных частей или сводок.
42 Концепции больших данных
Тем не менее первый аспект не является ограничением или проблемой
для научного сообщества: современные системы хранения данных, архитектурные решения и коммуникационные протоколы обеспечивают надежную технологическую базу для сбора и хранения такого количества данных
эффективным и действенным способом. Более того, наличие высокопроизводительного научного инструментария и недорогих цифровых технологий
способствовало развитию этого тренда как с технологической, так и с экологической точки зрения. С другой стороны, вычислительная мощность
компьютеров растет не так быстро, как того требует спрос на вычисления
с использованием таких данных, и это ограничивает знания, которые потенциально могли бы быть добыты. В качестве дополнительного аспекта следует
учитывать, что стоимость хранения данных в настоящее время снижается
быстрее, чем стоимость вычислений, и этот тренд усугубляет ситуацию.
Для того чтобы справиться с таким обилием данных (скорость генерирования которых зачастую намного превышает наши возможности по их анализу), появляются приложения, которые обследуют, опрашивают, анализируют,
визуализируют и в целом обрабатывают очень крупные наборы данных: они
получили название приложений по анализу данных. Вычислительная наука
эволюционирует в сторону приложений по анализу данных, которые охватывают интеграцию и анализ данных, управление информацией и машинное
обучение. В частности, анализ данных в больших хранилищах позволяет
отыскивать в данных то, что в них интересно, с помощью масштабируемых
техник анализа и глубокой переработки данных.
Анализ данных в науке помогает ученым формировать гипотезы и обес
печивает поддержку их научной практики и сред решения задач, обеспечивая получение выгод от знаний, которые могут добываться из крупных
источников данных. Продвинутые техники анализа данных и соответствующие инструменты помогают добывать информацию из крупных и сложных
наборов данных, которая может быть полезна для принятия обоснованных
решений во многих деловых и научных приложениях, включая сбор налоговых платежей, рыночные продажи, социальные исследования, бионауки
и физику высоких энергий. Сочетание аналитики больших данных и техник
машинного обучения с масштабируемыми вычислительными системами позволит производить новые знания за более короткое время.
В области науки о данных термин «аналитика данных» часто используется
как синоним термина «анализ данных». Но имеют ли эти два термина одинаковый смысл? Ответ: нет. На самом деле между ними есть небольшая разница. Согласно популярному определению: «Аналитика данных как дисциплина
занимается сбором и изучением сырых данных с целью извлечения значимых
выводов из этой информации». Таким образом, хотя термины «анализ данных» и «аналитика данных» часто используются как взаимозаменяемые, они
несколько отличаются друг от друга: аналитика данных – это более широкий
термин, который включает в себя анализ данных. Анализ данных в сущности
означает «процесс» подготовки и проведения анализа данных с целью добычи значимых знаний. С другой стороны, аналитика данных также включает
Масштабируемый анализ данных 43
в себя инструменты и техники, используемые для этой цели, такие как инструменты визуализации или складирования данных.
Организации используют базовые техники аналитики данных еще начиная с 1950-х годов, чтобы обнаруживать полезную информацию о рыночных
трендах и общей картине. В большинстве случаев такой анализ сводился
к обычному разглядыванию цифр в электронных таблицах с целью добычи
информации, которую можно использовать для принятия будущих решений.
Однако сегодня, чтобы обеспечить организациям конкурентное преимущество, аналитика данных должна уметь выявлять сведения с целью принятия
немедленных решений. Уже много лет большинство организаций собирают
данные – зачастую в огромных объемах, – которые вливаются потоком в их
предприятие, чтобы добывать значимую и ценную информацию для деловых
целей (например, для принятия более качественных решений или планирования маркетинговых стратегий) и научных целей (например, для подтверждения правильности или доказательства ошибочности моделей либо теорий).
Когда данные содержатся на географически распределенных площадках, для анализа научных и деловых данных можно использовать вычислительную мощь распределенных и параллельных систем. И параллельные,
и распределенные алгоритмы аналитики данных подходят для этих целей
в самый раз. Более того, в этом сценарии системы высокопроизводительных вычислений и платформы облачных вычислений обеспечивают эффективную вычислительную инфраструктуру для реализации масштабируемых
приложений по анализу данных и машинному обучению на основе крупных
и распределенных наборов данных (Belcastro и соавт., 2017). В частности,
все больше внимания со стороны промышленных и научных сообществ уделяется облачным вычислениям, рассматривая эту новую вычислительную
инфраструктуру как технологию, играющую ключевую роль для решения
сложных задач и реализации распределенных масштабируемых приложений
(Belcastro и соавт., 2021b).
Аналитика больших данных относится к продвинутым техникам аналитики данных, применяемым к большим наборам данных. Эти техники включают в себя глубокую переработку данных, статистику, визуализацию данных,
искусственный интеллект, машинное обучение и обработку естественного
языка. Использование аналитики больших данных дает ряд преимуществ,
в особенности в крупных компаниях, в плане снижения затрат на хранение
и выполнение запросов к крупным объемам данных, повышения эффективности принятия решений и возможности предоставлять услуги, которые
лучше удовлетворяют потребности заказчиков. Ниже рассматривается несколько областей применения аналитики больших данных:
текстовая аналитика: это процесс выведения информации из текстовых источников (например, документов, социальных сетей, блогов
и веб-сайтов), которая может использоваться для анализа настроений,
классифицирования контента, резюмирования текста и т. д.;
предсказательная аналитика: это процесс предсказания будущих событий или поведения с помощью моделей, разработанных с использовани-
44 Концепции больших данных
ем различных техник из статистики, машинного обучения, глубокой переработки данных и других аналитических методик (Nyce и Cpcu, 2007);
графовая аналитика (или сетевой анализ): анализирует поведение различных связных компонентов с помощью теорий сетей и графов, что
имеет практическую пользу для выяснения структур в социальных сетях (Otte и Rousseau, 2002). Примерами графовой аналитики являются
анализ путей, связности, сообществ и центральности;
предписательная аналитика: это форма продвинутой аналитики,
в рамках которой данные или контент изучаются на предмет оптимизации решений о том, какие действия следует предпринимать, чтобы
максимизировать прибыль с учетом набора ограничений и ключевых
целевых задач. Для нее характерны такие техники, как графовый анализ, симуляция, обработка сложных событий, нейронные сети, рекомендательные механизмы, эвристика и машинное обучение.
Несмотря на все выгоды, о которых говорилось выше, работа с большими
данными – это не ложе из роз. На самом деле процесс обнаружения знаний
из больших данных в целом сложен, в основном из-за характеристик данных,
о которых говорилось выше и которые требуют решения нескольких трудностей. Более того, область аналитики больших данных постоянно развивается, и в ней ежедневно появляются новые и эффективные технологические
решения (то есть платформы, инструменты программирования, фреймворки
и алгоритмы глубокой переработки данных), чтобы справляться с расширением масштабов интереса к большим данным. В этом контексте облачные
вычисления являются актуальным и экономически эффективным технологическим решением по поддержке хранения больших данных и исполнению
сложных приложений аналитики данных.
Сегодня многие организации, компании и научные центры производят
крупные объемы сложных данных и информации и управляют ими. Климатические, астрономические и геномные данные, а также транзакционные
данные компаний – вот лишь несколько современных примеров огромных
объемов цифровых данных, которые необходимо хранить и анализировать,
чтобы отыскивать в них полезные знания. Такое достояние в виде данных
и информации можно эффективно использовать, если они служат источником для генерирования знаний, необходимых для принятия решений. Этот
процесс отличается вычислительной интенсивностью, коллаборативностью
и является распределенным по своей природе.
Разработка алгоритмов анализа данных и приложений для систем высокопроизводительных и облачных вычислений предлагает инструменты
и среды для поддержки параллельного исполнения стратегий анализа, выведения и обнаружения знаний из распределенных данных; эти инструменты
доступны во многих научных и деловых областях. Создание фреймворков
поверх систем высокопроизводительных и облачных вычислений является
благоприятным условием для разработки высокопроизводительных заданий
по анализу данных и техник машинного обучения и отвечает вызовам, поставленным растущим спросом на мощность и абстрактность, возникающим
Параллельные вычисления 45
в сложных сценариях анализа данных в науке, инженерии и бизнесе. Использование общецелевых техник и инструментов анализа данных и машинного
обучения бесспорно способно эффективно поддерживать анализ массивных
и распределенных наборов данных в науке, инженерии, промышленности
и бизнесе.
2.5. Параллельные вычисления
Благодаря своим характеристикам большие данные по своей сути подходят
для широкого спектра приложений в самых разных областях, от физики высоких энергий, биоинформатики и геномики до социально-политического
или финансового моделирования и симуляции. Однако, несмотря на свою
информационную насыщенность, большие данные несут в себе ряд проблем,
связанных с их эффективным и действенным управлением и обработкой
ввиду их огромного объема, высокой разнородности и скорости, с которой
они генерируются. Вместе с тем следует обратить внимание на нецелесо
образность использования последовательных алгоритмов анализа данных
для добычи полезных моделей и закономерностей из больших данных за
разумное время. По этой причине для решения проблем, связанных с большими данными, исследователям данных необходимы высокопроизводительные компьютеры, такие как много- и мультиядерные системы, облака
и мультикластеры, а также параллельные и распределенные алгоритмы и системы (Talia и соавт., 2015).
2.5.1. Базовые понятия и определения
Первым фундаментальным различием, полезным для понимания разных
концепций параллельных систем, является разница между двумя широко
используемыми терминами – конкурентностью и параллельностью:
конкурентность: два или более заданий могут развиваться одновременно;
параллелизм: два или более заданий исполняются одновременно.
Стоит отметить, что в данном случае развитие не обязательно означает состояние исполнения, поэтому параллелизм нуждается в конкурентности, но
не наоборот. Конкурентность, по сути дела, может достигаться и без параллелизма, как это происходит при многозадачности на одноядерной машине1. Однако в этом случае время исполнения не уменьшается по сравнению
1
Параллельные задания исполняются эквидистантно, никогда не пересекаясь в одной точке в одно и то же время, тогда как конкурентные задания могут сходиться
в одной точке в одно и то же время, например при обращении к одной и той же
записи в таблице базы данных. – Прим. перев.
46 Концепции больших данных
с последовательным исполнением. Исходя из этого, параллельные вычисления
можно определить как практику решения задачи размера n путем ее разбиения на k ≥ 2 частей, которые решаются путем привлечения p физических
процессоров одновременно (Navarro и соавт., 2014). Эта парадигма решения
задач, также именуемая «разделяй и властвуй», применима только в том
случае, если задача распараллеливается, то есть может быть выражена в виде
разложения на k несовпадающих подзадач. В качестве примера рассмотрим
скалярное произведение двух векторов a = [a1, a2, ..., an] и b = [b1, b2, ..., bn],
определяемое как:
Вычисления можно легко распараллелить, разложив задачу на k частичных
сумм (шаг «разделяй»), которые будут вычисляться на разных процессорах
одновременно (шаг «властвуй»). Окончательный результат получается путем
комбинирования частичных сумм (что влечет за собой необходимость синхронизации). Формально это выглядит следующим образом:
Для того чтобы правильно сконструировать параллельный алгоритм, очень
важно выявить параллельную природу решаемой задачи. Формально задача
𝒫D с областью определения D может быть двух разных типов:
с параллелизмом данных: приняв, что D – это множество элементов данных, решение задачи может быть выражено как применение функции f
к каждому подмножеству D: f(D) = f (d1) + f(d2 ) + ··· + f(dk );
с параллелизмом заданий: приняв, что F – это множество функций, решение задачи может быть выражено как применение функций из F к D:
F(D) = f1(D) + f2(D) + ··· + fk (D).
2.5.2. Параллельные архитектуры
Системы параллельной и конкурентной обработки данных могут принимать
целый ряд разных форм. Таксономия Флинна классифицирует разные модели параллельных систем в зависимости от множественности потоков инструкций и данных, которыми они могут управлять. Как показано на рис. 2.2
и обсуждается далее (Flynn и Rudd, 1996; Spezzano и Talia, 1999), наиболее
распространенные параллельные архитектуры описываются четырьмя комбинациями.
Параллельные вычисления 47
Один
Много
ПОТОКИ ДАННЫХ
ПОТОКИ ИНСТРУКЦИЙ
Один
Много
SISD
MISD
Традиционный компьютер архитектуры
фон Неймана с одним CPU
Конвейеризированный компьютер
SIMD
Векторные процессоры, компьютеры
с мелкозернистым параллелизмом
данных
MIMD
Мультикомпьютеры, мультипроцессоры
Рис. 2.2 Таксономия Флинна
Один поток инструкций, один поток данных (SISD, от англ. single instruction stream, single data stream): к этому классу относится традиционная
архитектура фон Неймана, на которой основаны все традиционные компьютеры, от персональных компьютеров до больших последовательных вычислительных машин. В такой модели имеется один процессор, который обрабатывает один поток инструкций, исполняемых на одном потоке данных.
Один поток инструкций, несколько потоков данных (SIMD, от англ.
single instruction stream, multiple data stream): к этому классу относятся архитектуры, состоящие из многочисленных вычислительных единиц, которые
одновременно исполняют одну и ту же инструкцию, но работают на разных
данных. Как правило, указанные архитектуры реализуются за счет наличия
главного процессора, который отправляет инструкции, подлежащие одновременному исполнению, множеству вычислительных элементов, которые
их исполняют. Системы SIMD в основном используются для поддержки параллельного исполнения специализированных задач, таких как обработка
изображений и сигналов.
Несколько потоков инструкций, один поток данных (MISD, от англ.
multiple instruction stream, single data stream): этот класс характеризуется
одновременной работой нескольких потоков инструкций на одном потоке
данных. В абстрактном плане указанная архитектура представляет собой
конвейер из нескольких независимо исполняющих единиц, оперирующих
на одном потоке данных, передавая результаты от одной единицы к другой.
До сих пор процессоры MISD не вызывали особого интереса из-за отсутствия
устоявшихся программных конструкций, позволяющих легко соотносить
программы с моделью MISD.
Несколько потоков инструкций, несколько потоков данных (MIMD, от
англ. multiple instruction stream, multiple data stream): этот класс, в котором
несколько процессов исполняются одновременно на нескольких процессорах, работающих на своих собственных или коллективных данных, выражает
наиболее общую модель и представляет собой развитие архитектуры SISD.
Специфически реализация архитектур MIMD происходит за счет межсоединения большого количества обыкновенных процессоров.
48 Концепции больших данных
2.5.3. Аппаратные платформы
Масштабируемость системы параллельных вычислений – это мера ее способности сокращать время исполнения программы пропорционально количеству ее вычислительных элементов. Согласно этому определению, под
масштабируемостью вычислений понимается способность программно-аппаратной параллельной системы эффективно эксплуатировать растущие вычислительные ресурсы при исполнении программного приложения (Grama
и соавт., 2003). Масштабирование как способ улучшения обработки крупных
данных на разных платформах реализуется по-разному. В частности, как
уже говорилось (см. рис. 2.1), горизонтальное масштабирование заключается
в распределении рабочей нагрузки между большим количеством независимых устройств (например, серверов, товарных компьютеров), которые комбинируются с целью наращивания производительности обработки. С другой
стороны, из вертикального масштабирования следует оптимизация работы
одного сервера, который становится быстрее за счет увеличения количества
процессоров (Singh и Reddy, 2015).
Среди главных платформ, предназначенных для горизонтального масштабирования, можно выделить клиент-серверные архитектуры и одноранговые
сети, в которых большое количество подключенных машин (равнозначных
узлов) работают сообща децентрализованным и распределенным образом,
раздавая и потребляя ресурсы. Они представляют собой платформы распределенных вычислений, в которых обмен данными и коммуникация между
клиентами и серверами или между равнозначными узлами могут реализовываться с помощью механизмов передачи сообщений, таких как сокеты или
интерфейс передачи сообщений (MPI, от англ. message passing interface). Что
касается вертикального масштабирования, то здесь мы упоминаем мультиядерные центральные процессоры, графические процессоры (GPU), высокопроизводительные кластеры и полевые программируемые вентильные матрицы
(FPGA), краткое описание которых приводится ниже.
Мультиядерные центральные процессоры: мультиядерные центральные процессоры (multi-core CPU) характеризуются большим количеством
вычислительных ядер, и в них часто используется коллективная память
и один диск (Bekkerman и соавт., 2011). В последнее время резко возросло
количество ядер на чипе, а также количество операций, которые способно
выполнять одно ядро, что повысило параллелизм и позволило эффективно
исполнять алгоритмы аналитики больших данных. Частным случаем таких
систем являются многоядерные центральные процессоры (many-core processors), которые специально разработаны для поддержки высокой степени
параллелизма за счет привлечения большого количества ядер. По сравнению с мультиядерными центральными процессорами, многоядерные цент
ральные процессоры имеют гораздо большее количество ядер (тысячи и более), оптимизированных под более высокую степень явного параллелизма
и пропускную способность, и поэтому широко используются в высокопроизводительной обработке данных. Главные недостатки этих систем связаны
Параллельные вычисления 49
с физическими ограничениями на количество вычислительных ядер и зависимостью от системной памяти в части доступа к данным. И действительно,
даже если данные поместить в системную память, процессоры все равно
будут обрабатывать их со скоростью, значительно превышающей скорость
доступа к памяти, что превращает доступ к памяти в узкое место.
Графические процессоры: графические процессоры (GPU, от англ. graphics
processing units) – это оптимизированные аппаратные компоненты, специально разработанные для ускорения графических операций (Owens и соавт.,
2008). Однако, несмотря на то что они были разработаны в первую очередь для
оптимизации обработки графики, их также можно использовать для ускорения общецелевых вычислений. Благодаря своей массивно-параллельной архитектуре, состоящей из тысяч ядер, общецелевые графические процессоры
(GPGPU, от англ. general-purpose GPU), по сути дела, могут значительно улучшать производительность по сравнению с мультиядерными центральными
процессорами, повышая эффективность чрезвычайно параллельных приложений во многих передовых областях, таких как глубокое обучение. Например, выпущенный компанией Nvidia фреймворк CUDA предлагает простой
в использовании интерфейс программирования на графическом процессоре,
а многие библиотеки глубокого обучения, такие как TensorFlow, позволяют
программисту тренировать огромные модели, эксплуатируя по полной мощь
графических процессоров, совершенно прозрачным образом.
Высокопроизводительные кластеры: высокопроизводительные кластеры, также именуемые суперкомпьютерами, представляют собой машины,
оснащенные огромным количеством ядер (Buyya, 1999). Они опираются на
мощное аппаратное обеспечение, оптимизированное под скорость и пропускную способность, а также на механизмы передачи сообщений. Они также
характеризуются очень низким уровнем ошибок/отказов благодаря высококачественному оборудованию, однако обеспечение этих способностей приводит к высоким денежным затратам на их развертывание и техническое
сопровождение.
Полевые программируемые вентильные массивы: полевые программируемые вентильные массивы (FPGA, от англ. Field programmable gate arrays) представляют собой узкоспециализированные аппаратные устройства,
разработанные специально для определенных областей применения, включая медицину, обработку видео и изображений, телекоммуникации и сетевую безопасность (Brown и соавт., 1992). Их можно эффективно подстраивать под достижение высоких скоростей, что делает их на порядки быстрее
конкурирующих систем. Однако стоимость разработки зачастую оказывается значительно выше, чем у других платформ, из-за сложного аппаратного
обеспечения, создаваемого на заказ. Кроме того, со стороны программного обеспечения их довольно сложно программировать, так как оно должно
осуществляться на языке описания аппаратного обеспечения (HDL, от англ.
hardware descriptive language), что влечет за собой всестороннее понимание
аппаратуры и, в свою очередь, значительное удорожание процесса разработки алгоритмов.
50 Концепции больших данных
2.5.4. Метрики производительности
В параллельных вычислениях используются две главные метрики измерения
прироста производительности, обусловленного исполнением параллельного
приложения, которые принято называть масштабируемостью. Обозначив
через Ts время последовательного исполнения (на одном процессоре) и через
Tn время параллельного исполнения (на n процессорах), указанные метрики
определяются следующим образом:
ускорение – это соотношение между временем последовательного исполнения программы и временем исполнения параллельной версии
этой же программы, определяемое как Sn = Ts /Tn;
эффективность – это эффективное использование каждого процессора
параллельного компьютера при исполнении программы, определяемое как En = Sn /n = Ts /(n · Tn).
Для предсказания теоретического ускорения при использовании нескольких процессоров можно использовать закон Амдала. Он гласит, что теоретическое ускорение ограничено той частью программы, которая фактически
поддается распараллеливанию. Оставшаяся часть программного кода может
исполняться только последовательно, поэтому ее нельзя масштабировать.
Обозначив через n количество процессоров и через F поддающуюся распараллеливанию часть, причем 0 ≤ F ≤ 1, теоретическое ускорение Ŝn формально
определяется следующим образом:
Из закона Амдала можно вывести несколько важных соображений, которые необходимо учитывать при конструировании экстремальных параллельных систем. Сначала будут рассмотрены предельные случаи для величины F:
F = 0: это наихудший случай, при котором ни одна часть программы не
может быть распараллелена. Он приводит к минимально возможному
значению Ŝn, равному 1;
F = 1: это наилучший случай, при котором вся программа может быть
распараллелена в каждой инструкции, что приводит к наилучшему возможному ускорению, Ŝn = n, линейно зависящему от числа процессоров.
Более того, максимально достижимое ускорение для программы, параллелизуемая доля которой равна F, может быть выведено следующим образом:
Из приведенной выше формулы хорошо видно, что даже если увеличивать
уровень параллелизма, теоретическое ускорение будет ограничиваться ве-
Облачные вычисления 51
личиной, обратной 1 – F, то есть нераспараллеливаемой частью программы.
Этот факт имеет два главных следствия:
если программу, которую мы пытаемся оптимизировать, едва можно
распараллелить, то есть F ≈ 0, то введение параллелизма приведет лишь
к незначительному ускорению, независимо от количества имеющихся
процессоров;
за исключением вышеупомянутых особых случаев (F = 0 и F = 1), улучшение, достигаемое за счет повышения уровня параллелизма, имеет
тренд на уменьшение отдачи. В частности, вклад в Ŝn, вносимый увеличением n, становится все меньше и меньше, по мере того как ускорение
.
сходится к
Максимальное ускорение (Smax )
Все эти соображения можно логически вывести из рис. 2.3.
F = 0,2
F = 0,5
F = 0,8
F = 0,95
F = 1 (linear)
Число процессоров (n)
Рис. 2.3 Максимальное ускорение,
достижимое при варьирующемся количестве имеющихся процессоров (n),
с учетом распараллеливаемой части программы (F)
2.6. Облачные вычисления
В последние годы системы облачных вычислений стали популярными вычислительными платформами, обеспечивающими эффективный и действенный
анализ данных как для инженеров-изыскателей, так и для компаний, успешно решая сложную задачу добычи знаний из больших хранилищ данных
за ограниченный промежуток времени. С точки зрения клиента, облачная
платформа – это абстракция для дистанционного, бесконечно масштабируе
мого предоставления вычислительных ресурсов и ресурсов хранения дан-
52 Концепции больших данных
ных. С точки зрения реализации, облачные системы основаны на крупных
наборах вычислительных ресурсов, расположенных где-то «в облаке», которые выделяются приложениям по требованию (Barga и соавт., 2011). Таким
образом, термин «облачные вычисления» можно определить как парадигму
распределенных вычислений, в которой все ресурсы, динамически масштабируемые и часто виртуализированные, предоставляются в виде услуг через интернет. Согласно определению Национального института стандартов
и технологий (NIST) (Mell и соавт., 2011), облачные вычисления можно описать как: «Модель для обеспечения удобного сетевого доступа по требованию
к общему пулу конфигурируемых вычислительных ресурсов (например, сетям,
серверам, хранилищам, приложениям и услугам), которые могут быстро предоставляться и высвобождаться с минимальными управленческими усилиями или
взаимодействием с поставщиками услуг».
Из определения NIST можно выделить пять существенных характеристик
систем облачных вычислений: самообслуживание по требованию, широкий
сетевой доступ, объединение ресурсов в пул, быстрая эластичность и измеримая услуга.
2.6.1. Модели распределения и развертывания
облачных услуг
Облачные системы можно классифицировать по модели предоставления услуг и модели их развертывания. Поставщики облачных вычислений предоставляют свои услуги в соответствии с тремя главными моделями предоставления услуг:
программное обеспечение как услуга (SaaS, от англ. software as a service),
при которой программное обеспечение и данные предоставляются
заказчикам через интернет в виде готовых к использованию услуг.
В частности, программное обеспечение и связанные с ним данные размещаются у поставщиков, а заказчики получают к ним доступ без необходимости использования дополнительного оборудования или программного обеспечения. Примерами программного обеспечения как
услуги являются Gmail, Facebook, Twitter и Microsoft Office 365;
платформа как услуга (PaaS, от англ. platform as a service) – это среда,
включающая в свой состав системы управления базами данных, серверы приложений и разработческие инструменты создания, тестирования и выполнения конкретно-прикладных приложений. Разработчики
могут сосредоточиваться на развертывании приложений, так как облачные поставщики отвечают за техническое сопровождение и оптимизацию среды и базисной инфраструктуры. Примерами платформы
как услуги являются Microsoft Azure, Force.com и Google App Engine;
инфраструктура как услуга (IaaS, от англ. infrastructure as a service) –
это аутсорсинговая модель, при которой заказчики арендуют ресур-
Облачные вычисления 53
сы, такие как центральные процессоры, диски или более сложные ресурсы, например виртуализированные серверы или операционные
системы, для поддержки своих операций (например, Amazon EC2,
RackSpace Cloud). По сравнению с моделью «Платформа как услуга»
модель «Инфраструктура как услуга» требует от пользователя более
высоких затрат на администрирование системы; с другой стороны,
инфраструктура как услуга обеспечивает более высокую степень гибкости, позволяя полностью адаптировать среду исполнения под свои
потребности.
Наиболее распространенными моделями предоставления решений для
аналитики больших данных в облаке являются модели «Платформа как услуга» и «Программное обеспечение как услуга». Модель «Инфраструктура как
услуга» обычно используется не для высокоуровневых приложений аналитики данных, а в основном для хранения данных и вычислений, необходимых для процессов анализа данных. Данная модель предоставления услуг,
по сути дела, является более дорогой, так как нуждается в более высоких
инвестициях в ИТ-ресурсы. Напротив, модель «Платформа как услуга» широко используется для аналитики больших данных, поскольку предоставляет
аналитикам инструменты, комплекты программ, среды и готовые к использованию библиотеки, развернутые и работающие на облачной платформе.
При использовании данной модели пользователям не приходится заботиться о конфигурировании и масштабировании инфраструктуры (например,
распределенной и масштабируемой системы Hadoop), так как за них это
делает поставщик облака. Наконец, модель «Программное обеспечение как
услуга» используется для предоставления конечным пользователям полных
приложений по аналитике больших данных, благодаря которым они могут
исполнять анализ на крупных и/или сложных наборах данных, эксплуатируя
масштабируемость облака для хранения и обработки данных.
Что касается моделей развертывания, то услуги облачных вычислений
предоставляются в трех главных формах:
публичное облако: оно предоставляет услуги широкой публике через
интернет, и пользователи практически не контролируют базисную технологическую инфраструктуру. Поставщики управляют проприетарными центрами обработки данных, предоставляя услуги, построенные
поверх них;
частное облако: оно предоставляет услуги, развернутые во внутренней
сети компании или в частном центре обработки данных. Малые и средние компании сферы ИТ нередко отдают предпочтение именно этой
модели развертывания, так как она предусматривает продвинутые
технологические решения по обеспечению безопасности и контролю
данных, которые недоступны в модели публичного облака;
гибридное облако: это композиция двух или более (частных или пуб
личных) облаков, которые остаются разными сущностями, но связаны
между собой.
54 Концепции больших данных
Как отмечается в работе Ли (Li и соавт., 2010), пользователи получают доступ к услугам облачных вычислений с помощью самых разных клиентских
устройств и взаимодействуют с облачными услугами через веб-браузер или
настольное/мобильное приложение. Программное обеспечение и данные
пользователя исполняются и хранятся на серверах, размещенных в облачных
центрах обработки данных, которые предоставляют ресурсы для хранения
и вычислений. Ресурсы включают в свой состав тысячи серверов и устройств
хранения данных, соединенных между собой внутриоблачной сетью. Передача данных между центром обработки данных и пользователями осуществляется по вычислительной сети широкого охвата. Разные компоненты данной архитектуры используют несколько технологий и стандартов. Например,
пользователи могут взаимодействовать с облачными услугами через вебсервисы на основе SOAP или RESTful (Richardson и Ruby, 2008), а интерфейс
открытых облачных вычислений (OCCI, от англ. Open Cloud Computing Interface) определяет способы предоставления облачными поставщиками своих
вычислительных, информационных и сетевых ресурсов через стандартизированный интерфейс.
2.6.2. Облачные услуги для больших данных
В начале феномена больших данных только крупные компании сферы ИТ,
такие как Google, Amazon, Yahoo!, Facebook и Microsoft, вкладывали большие
ресурсы в разработку собственных или открытых проектов, чтобы справляться с проблемами анализа больших данных. Сегодня анализ больших данных
приобрел очень большую важность и полезность для малого и среднего бизнеса. В целях удовлетворения этого растущего спроса большое сообщество
поставщиков начало предлагать высокораспределенные платформы для анализа больших данных. Среди проектов с открытым исходным кодом фреймворк Apache Hadoop является одной из ведущих платформ с открытым исходным кодом по обработке данных, в создании которой принимали участие
такие гиганты сферы ИТ, как Facebook и Yahoo!.
С 2008 года несколько компаний, таких как Cloudera, MapR и Hortonworks,
начали предлагать корпоративные платформы для фреймворка Hadoop, прилагая большие усилия по улучшению производительности Hadoop в плане
высокомасштабируемого хранения и обработки данных. С другой стороны,
компании IBM и Pivotal начали предлагать свои собственные адаптированные дистрибутивы Hadoop. Другие крупные компании решили предлагать
только дополнительное программное обеспечение и поддержку платформ
Hadoop, разработанных сторонними поставщиками: например, компания
Microsoft решила основывать свое предложение на платформе Hortonworks,
а Oracle – перепродавать платформу Cloudera.
Однако Hadoop – не единственное технологическое решение для аналитики больших данных. За пределами Hadoop появляются и другие решения.
В частности, резидентный анализ стал настолько распространенным трендом,
Облачные вычисления 55
что компании начали предлагать инструменты и услуги по ускорению резидентного анализа, например SAP, считающаяся ведущей компанией со своей
платформой HANA (от англ. High-performance ANalytic Appliance)1. Другие поставщики, включая HP, Teradata и Actian, разработали инструменты для аналитических СУБД с возможностями резидентного анализа. Более того, некоторые поставщики, такие как Microsoft, IBM, Oracle и SAP, выделяются на фоне
своих конкурентов тем, что предлагают полные решения по анализу данных,
включая СУБД, программное обеспечение для интеграции данных, потоковой
обработки, деловой аналитики, резидентной обработки и платформу Hadoop.
В дополнение к этому многие поставщики, такие как Amazon Web Services
(AWS) и 1010data, решили сосредоточить все свои предложения на облачных
технологиях. В частности, AWS предоставляет в облаке широкий спектр услуг
и продуктов для аналитики больших данных, включая масштабируемые СУБД
и решения по поддержке принятия решений. Другие более мелкие поставщики, включая Actian, InfiniDB, Infobright и Kognitio, сосредоточивают свое предложение по большим данным только на СУБД для аналитики.
В оставшейся части раздела представлены главные услуги по облачной
аналитике больших данных и машинного обучения, предоставляемые следующими популярными облачными платформами: Amazon Web Services,
Google Cloud Platform, Microsoft Azure и OpenStack.
2.6.2.1. Amazon
AWS2 – это большой комплект облачных услуг, с помощью которых Amazon
предоставляет разработчикам вычислительные ресурсы и ресурсы хранения
данных своей ИТ-инфраструктуры. Пользователи могут легко компоновать
указанные услуги, собирая свои приложения в рамках модели «Программное
обеспечение как услуга» или интегрируя традиционное программное обес
печение с облачными способностями. Взаимодействие с этими услугами облегчается наличием комплектов для разработки программного обеспечения
(SDK) на главных языках программирования и платформах (например, Java,
.Net, PHP, Python, Kotlin, Javascript). Главными компонентами и услугами AWS
являются следующие:
перенос данных: включает в себя несколько решений по транспортировке данных, предназначенных для безопасного переноса огромных
объемов данных в облако AWS. Эти решения позволяют избегать длительного времени переноса данных, высоких сетевых затрат и проблем
с обеспечением безопасности. Они также поддерживают автоматизацию перемещения данных между облаком AWS и локальными хранилищами;
управление данными: AWS предоставляет широкий выбор СУБД, включая Amazon Relational Database Service (RDS) для реляционных таблиц
1
2
См. https://www.sap.com/products/technology-platform/hana.html.
См. https://aws.amazon.com.
56 Концепции больших данных
и решения на базе NoSQL, такие как Amazon DynamoDB. Также есть
возможность использовать резидентные кеши данных для реальновременных приложений; указанные кеши предоставляются услугой
Amazon ElastiCache;
вычисления: AWS предоставляет услуги Elastic Compute Cloud (EC2) для
создания и организации работы виртуальных серверов и услуги Amazon Elastic MapReduce (EMR) с целью сборки и исполнения приложений
MapReduce. Также доступны инструменты автоматического масштабирования емкости системы и поддержки бессерверных функций через
вычислительную услугу AWS Lambda;
хранение: AWS предоставляет несколько гибких вариантов хранилищ
для долговременного и кратковременного хранения данных. Среди
главных решений по хранению данных находятся услуга Simple Storage
Service (S3), которая обеспечивает масштабируемое объектное хранилище, Amazon Glacier для нечастого доступа и Amazon Elastic Block
Store, обеспечивающая блочное долговременное хранение данных.
Среди других компонентов и услуг, входящих в обширный комплект, предоставляемый комплектом AWS, можно найти решения по сетевому взаимодействию, обеспечивающие низкую задержку между экземплярами и высокую пропускную способность; автоматизацию и оркестровку, позволяющие
автоматизировать динамическое планирование и передачу вычислительных
заявок на обработку; а также услуги обеспечения безопасности, такие как
AWS Identity and Access Management (IAM). Наконец, пользователи могут использовать услуги из набора Amazon AI по созданию и развертыванию приложений, основанных на сложных алгоритмах искусственного интеллекта,
включая Amazon Polly для перевода текста в речь и Amazon Rekognition для
распознавания изображений/лиц.
2.6.2.2. Google Cloud Platform
Google Cloud Platform (GCP)1 – это широкий комплект услуг для облачных
вычислений, предоставляемых компанией Google и работающих на той же
инфраструктуре, что и продукты Google конечных пользователей (например, Gmail). Растущая популярность данной платформы обусловлена рядом
факторов, включая эффективность, низкую задержку и наличие целого ряда
инновационных инструментов обработки и управления большими данными,
таких как BigQuery для хранилищ данных и Google Cloud Dataflow для обработки данных в реальном времени. Далее мы перечислим главные услуги
и компоненты, предоставляемые облачной платформой GCP.
Вычисления: эта услуга обеспечивает вычисления и размещение (хос
тинг) в облаке. Она включает в себя решения в рамках модели «Инфраструктура как услуга», такие как Google Compute Engine, и несколько
решений в рамках модели «Платформа как услуга», в том числе Google
1
См. https://cloud.google.com.
Облачные вычисления 57
App Engine для разработки и размещения веб-приложений в управляемых инженерами компании Google центрах обработки данных и Kubernetes Engine для организации работы контейнеризированных приложений.
Хранение данных: услуги по хранению данных и управлению базами
данных, предоставляемые платформой GCP, включают Google Cloud
Storage и Datastore; SQL-подобные решения, такие как Cloud SQL и услуги NoSQL, такие как Bigtable по управлению базами данных NoSQL
в формате ключ-значения с широкими столбцами, для больших аналитических рабочих нагрузок.
Сетевое взаимодействие: эти услуги обеспечивают связь и распределение нагрузки между ресурсами. К ним относятся Google Cloud DNS, сеть
доставки контента CDN (от англ. Content Delivery Network) и услуги по
обеспечению безопасности, такие как Armor.
Более того, платформа GCP охватывает обширный комплект услуг в таких
передовых областях, как большие данные (посредством BigQuery и Cloud
Data Studio), IoT (посредством Cloud IoT) и искусственный интеллект. Указанные услуги поддерживают широкий спектр сложных заданий – от обработки
и понимания естественного языка (Cloud Natural Language, Speech-to-Text,
Text-to-Speech и Translation API) до обработки изображений и видео (Cloud
Vision API и Cloud Video Intelligence).
2.6.2.3. Microsoft Azure
Azure1 – это облачное предложение Microsoft, которое представляет собой
публичное решение в рамках модели «Платформа как услуга», предоставляющее большой комплект облачных услуг, которые могут использоваться
разработчиками для конструирования облачных приложений или для усиления существующих приложений за счет облачных способностей. Платформа
Azure предоставляет ресурсы по вычислениям и хранению данных по требованию, эксплуатируя вычислительные мощности и возможности центров
обработки данных Microsoft по хранению данных. Azure разработана для
поддержки услуг по обеспечению высокой доступности и динамического
масштабирования, которые состыковывают потребности пользователей
с моделью ценообразования «оплата по мере использования» (pay-per-use).
Платформа Azure может использоваться для хранения крупных наборов
данных, исполнения крупных объемов пакетных вычислений и разработки
приложений в рамках модели «Программное обеспечение как услуга», ориентированных на конечных пользователей. Microsoft Azure включает три
главных компонента/услуги:
вычисления: вычислительная среда для выполнения облачных приложений. Каждое приложение структурировано по ролям: веб для
веб-приложений, работник для пакетных приложений и виртуальная
1
См. https://azure.microsoft.com.
58 Концепции больших данных
машина для размещения и организации работы экземпляров виртуальных машин;
хранение: обеспечивает масштабируемое хранилище для управления
двоичными и текстовыми данными (Blobs, от англ. binary large object),
нереляционными таблицами (Tables) и очередями для асинхронного
обмена данными между компонентами (Queues). В дополнение к этому Microsoft предлагает собственные облачные услуги по управлению
базами данных для реляционных баз данных, такие как Azure SQL Database на основе SQL. Более того, Microsoft предоставляет услугу по
управлению базами данных NoSQL под названием Azure Cosmos DB,
которая поддерживает различные модели данных, включая модель
ключ-значения, столбцовую, документную и графовую модели;
контроллер внутреннего устройства: ориентирован на построение
сети взаимосвязанных узлов из физических машин одного центра обработки данных. На базе этого компонента строятся услуги по выполнению вычислений и хранению данных.
Платформа Microsoft Azure предлагает стандартные интерфейсы, которые
позволяют разработчикам взаимодействовать с ее услугами. Более того, разработчики могут использовать такие среды разработки, как Microsoft Visual
Studio и Eclipse, чтобы легко конструировать и публиковать приложения Azure.
2.6.2.4. OpenStack
OpenStack1 – это облачная операционная система с открытым исходным
кодом, выпущенная на условиях лицензии Apache License 2.0 и представляющая собой частную платформу в рамках модели «Инфраструктура как
услуга», позволяющую управлять большими пулами ресурсов с целью обработки, хранения и обеспечения сетевого взаимодействия в центре обработки данных через веб-интерфейс. Большинство решений по развитию
данной платформы принимаются сообществом разработчиков, вплоть до
того, что каждые полгода проводится саммит разработчиков с целью сбора
требований и определения новых спецификаций для предстоящего релиза.
Модульная архитектура OpenStack состоит из четырех главных компонентов,
описанных ниже.
Вычисления: предоставляет виртуальные серверы по требованию,
управляя пулом вычислительных ресурсов, доступных в центре обработки данных. Данный компонент поддерживает различные технологии виртуализации, такие как VMware и KVM, и предназначен для
горизонтального масштабирования.
Хранение: обеспечивает масштабируемую и избыточную систему хранения данных. В ней используются объектные и блочные хранилища,
которые позволяют хранить и извлекать объекты и файлы в центре
обработки данных.
1
См. https://www.openstack.org.
На пути к экзафлопсным вычислениям 59
Сетевое взаимодействие: отвечает за управление сетями и IP-адресами
в среде OpenStack.
Коллективные услуги: дополнительные услуги, предоставляемые для
облегчения использования центра обработки данных, такие как услуги
по идентификации с целью соотнесения пользователей и услуг, услуги
по обработке образов с целью управления образами серверов и услуги
по управлению базами данных для реляционных баз данных.
Облачная операционная система OpenStack эффективно используется
в ряде компаний и исследовательских лабораторий для предоставления масштабируемой, динамичной инфраструктуры с целью управления большими
данными и аналитики. Согласно опросу пользователей OpenStack, около 30 %
пользователей развернули или протестировали приложения по анализу больших данных. Эти приложения обращаются к многочисленным источникам
данных и их анализируют, предоставляя ценные сведения для большинства
отделов внутри организации. Облачная среда на базе OpenStack предлагает
эффективную работу с большими данными, поддерживая большое количество
веб-запросов на анализ и обучение с быстрым временем развертывания.
2.7. На пути к экзафлопсным
вычислениям
В общих чертах технология высокопроизводительных вычислений (HPC, от
англ. high-performance computing) означает использование параллельной
обработки данных для работы с крупными объемами данных и сложными
вычислениями. Она агрегирует вычислительную мощь огромного количест
ва узлов, обеспечивая высокую скорость, масштабируемость, надежность
и энергоэффективность. Из применения высокопроизводительных вычислений к аналитике данных вытекает концепция высокопроизводительной
аналитики данных (HPDA, от англ. high-performance data analytics), которая
позволяет эффективно выполнять сложные научные рабочие процессы и интенсивные по привлечению данных аналитические приложения.
Мощь инфраструктур высокопроизводительных вычислений, таких как высокопараллельные кластеры, суперкомпьютеры и облака, приходится задействовать в приложениях машинного обучения и искусственного интеллекта,
включая программные системы научного моделирования и симуляции. Однако в ближайшие несколько лет по мере совершенствования исследований
и технологий параллельных вычислений для реализации масштабируемых
решений по анализу больших данных в науке и бизнесе начнут использоваться вычислительные системы экзафлопсного масштаба (Talia и соавт., 2022).
Таким образом, экзафлопсные системы станут новым рубежом высокопроизводительных вычислений, и к ним относятся вычислительные системы,
способные исполнять не менее одного экзафлопса, то есть не менее 1018 опе-
60 Концепции больших данных
раций с плавающей запятой в секунду (FLOPS, от англ. floating point operations
per second). В частности, экзафлопсная система может быть описана разными
атрибутами, которые подробно изложены ниже (Bergman и соавт., 2008):
физические атрибуты: они связаны с суммарной потребляемой мощностью и размером системы (то есть площадью и объемом);
скорость вычислений: это темп исполнения определенного типа операций в секунду, измеряемый во флопсах, инструкциях в секунду (IPS, от
англ. instructions per second) и обращениях к памяти в секунду;
объем памяти: измеряет емкость памяти, доступную в различных частях иерархии хранения, таких как основная память, сверхоперативная
память (скретч-память) и долговечное хранилище;
пропускная способность: это скорость, с которой необходимые для вычислений данные могут перемещаться по системе. Показатели пропускной способности включают пропускную способность локальной
памяти, пропускную способность контрольной точки, пропускную способность ввода-вывода и пропускную способность на кристалле.
Измерение производительности во флопсах стандартно основывается на
64-битных (с плавающей точкой двойной точности) операциях в секунду
и эталоне High Performance LINPACK (Dongarra и соавт., 1979), состоящем
из плотной n×n системы линейных уравнений. Этот эталон используется
в списке суперкомпьютеров TOP500; он ранжирует и подробно описывает
500 самых мощных суперкомпьютеров в мире (Strohmaier и соавт., 2015).
В табл. 2.6 представлены три лучших суперкомпьютера в списке на июнь
Таблица 2.6. Три верхние позиции в списке TOP500 в июне 2023 года
Ранг
Rmax/Rpeak
(петафлопсы)
Название
Модель
№1
1194,00/1679,81
Frontier
HPE Cray EX235a
№2
442,01/537,21
Fugaku
Supercomputer
Fugaku
Ядра
591 872
7 630 848
центрального
(9248 × 64-ядерный (158 976 × 48-ядерный
процессора
оптимизированный Fujitsu A64FX
3-го поколения
@2.2 GHz)
EPYC 64C @2.0 GHz)
Ядра
36 992×220 AMD
–
акселератора
Instinct MI250X
Межсоединение Slingshot-11
Tofu interconnect D
Производитель HPE
Fujitsu
Место, страна
Национальная
Центр
лаборатория в Oak вычислительных
Ridge, США
наук RIKEN, Япония
Год
2022
2020
Операционная Linux (HPE Cray OS) Linux (RHEL)
система
№3
309,10/428,70
LUMI
HPE Cray EX235a
75 264
(1176 × 64-ядерный
оптимизированный
3-го поколения EPYC
64C @2.0 GHz)
4704×220 AMD
Instinct MI250X
Slingshot-11
HPE
EuroHPC JU,
Европейский союз,
Финляндия
2022
Linux (HPE Cray OS)
На пути к экзафлопсным вычислениям 61
2023 года. На первом месте находится суперкомпьютер Frontier, который был
самым быстрым суперкомпьютером в мире и первой настоящей системой
экзафлопсного масштаба, достигшей производительности 1,102 эксафлопса. Системы в списке TOP500 упорядочены по значениям показателей Rmax
и Rpeak. В частности, Rmax – это наивысший результат (в петафлопсах), измеренный с помощью набора эталонных тестов LINPACK, а Rpeak – теоретическая пиковая производительность системы (в петафлопсах).
2.7.1. Главные трудности систем
экзафлопсного масштаба
Внедрение систем экзафлопсного масштаба – это прекрасная возможность
проложить путь к приложениям в очень широком спектре прикладных областей, таких как прогнозирование погоды, моделирование климата и персонализированная медицина (Gagliardi и соавт., 2019). Однако их разработка
и реализация довольно сложны и нуждаются в решении целого ряда проблем
(Bergman и соавт., 2008). В частности, в приложениях экзафлопсного масштаба необходимо избегать или сильно ограничивать синхронизацию, использовать меньше коммуникаций и дистанционной памяти, а также справляться
с возможными программными и аппаратными отказами. Далее будет дано
описание этих проблем, а также главные предложения по их решению.
Энергия: лимит суммарного потребления энергии для этих машин должен
составлять порядка 20 МВт расчетной тепловой точки (TDP, от англ. thermal
design point)1, что приводит к необходимости радикальных изменений во
всем процессе, в силу которого с течением времени происходила эволюция аппаратного обеспечения. В частности, потребление энергии в расчете на вычислительную единицу должно быть значительно снижено, чтобы
достичь хорошего баланса между производительностью, объемом памяти
и стоимостью энергии в расчете на флопс. С точки зрения алгоритмов, все
более важным будет становиться измерение соотношения энергии к решению, чтобы сдерживать порождаемую стоимость работы системы. Учитывая
этот новый тренд, необходимо разработать API-интерфейсы для управления
энергопотреблением аппаратного обеспечения посредством программного
обеспечения, чтобы пользователь мог напрямую управлять энергетическим
бюджетом. Например, если пользователю необходимо минимизировать время на получение решения, то неплохо иметь возможность увеличить частоту
процессоров, при этом осознавая связанные с этим затраты.
Конкурентность: с целью ограничения потребления энергии производители создают новые процессоры с более низкой тактовой частотой и бóльшим
количеством ядер. Из этого вытекает растущая потребность в параллелизме,
1
Также тепловая расчетная мощность, или величина отвода тепловой мощности,
определяется как теоретически максимальное количество тепла, выделяемое CPU
или GPU, которое система охлаждения способна рассеивать. – Прим. перев.
62 Концепции больших данных
чтобы уменьшать время до получения решения. Существуют самые разные
уровни параллелизма, такие как внутриузловой и межузловой, то есть соответственно внутри и между узлами HPC. Векторизация также может использоваться на уровне ядра, создавая дополнительный уровень внутриядерного
распараллеливания. Более того, для повышения производительности одного
узла можно использовать ускорители, добиваясь экстремального внутриядерного параллелизма. Машины с применением этой технологии называются гибридными архитектурами. Будущие машины экзафлопсного масштаба
должны уметь полностью задействовать преимущества этих типов параллелизма, минимизируя накладные расходы и необходимость в синхронизации.
Локальность данных: ключевым аспектом будущих систем экзафлопсного масштаба, характеризующихся разнородными и сложными архитектурами, является минимизация межузловых коммуникаций и перемещения данных. Безусловно, любое вычисление, запрашиваемое приложением,
интенсивным по привлечению данных, будет гораздо эффективнее, если
оно будет исполняться вблизи данных, на которых оно оперирует. Для того
чтобы воспользоваться преимуществами локальности данных, системы экзафлопсного масштаба должны иметь соответствующую топологию. С другой
стороны, приложения должны иметь возможность читать данные локально,
зная топологию узлов, на которых исполняются задания, и местоположение
обрабатываемых данных. Основываясь на концепции стойки (рэка), то есть
несущего каркаса, в котором хранятся жесткие модули, как правило, серверы,
жесткие диски и другое вычислительное оборудование, можно определить
несколько уровней локальности данных:
узловая локальность: это наилучший сценарий, при котором коммуникация не нужна, поскольку задания работают на том же узле, где
находятся данные, на которых они оперируют;
внутристоечная: в этом сценарии данные хранятся на другом узле
стойки, из чего следует необходимость перемещения задания в это
место. Однако это достижимо с низкой задержкой, так как коммуникация происходит в пределах одной стойки;
межстоечная локальность: это наихудший сценарий, поскольку задания должны перемещаться на узлы, расположенные на разных стойках,
что может приводить к снижению производительности из-за накладных расходов на коммуникацию.
Подводя итог, цель этой стратегии состоит в устранении узкого места,
создаваемого перемещением данных. Безусловно, помимо вычислительного времени, значительные накладные расходы в системах экзафлопсного масштаба связаны с коммуникациями и операциями ввода-вывода.
Следовательно, необходимы специальные политики размещения данных1
1
Размещение данных (data placement) охватывает все действия, связанные с перемещением данных, такие как передача, резервирование промежуточного места,
репликация, выделение и высвобождение пространства, регистрация метаданных
и снятие их с регистрации, локализация и извлечение данных. – Прим. перев.
Параллельное и распределенное машинное обучение 63
и ориентирующиеся в них планировщики, чтобы обеспечивать полное использование базисных аппаратных и программных стеков, следуя подходу,
который центрирован на данных. При таком подходе доступность данных
между соседними узлами обусловливает операции, предпринимаемые этими
узлами, что позволяет ограничивать накладные расходы на обмен данными
в системах массивно-параллельной обработки посредством локальной коммуникации, управляемой данными (Talia, 2019).
Память: в связи с вышеупомянутыми энергетическими лимитами будет
совокупно расти объем основной памяти, но не настолько, чтобы гарантировать эквивалентное увеличение памяти в расчете на одно ядро. Поэтому, за
исключением кардинальных изменений в архитектуре памяти, это приведет
к уменьшению объема памяти в расчете на ядро, так как для достижения
вычислительной мощности одного экзафлопса количество ядер должно значительно увеличиться. Следовательно, работающие на таких архитектурах
алгоритмы должны будут адаптироваться к ограниченным ресурсам, минимизируя потребление памяти.
Устойчивость: учитывая увеличение количества компонентов в системах
экзафлопсного масштаба, ожидается и увеличение количества отказов. В связи с этим обстоятельством приложениям необходимо принимать контрмеры,
используя такие механизмы, как контрольная точка и перезапуск. Механизм
контрольных точек обычно нуждается в больших временны́х и энергетических затратах ввиду синхронизации вычислительных единиц и операций
ввода-вывода. Поэтому в машинах экзафлопсного масштаба время контрольной точки может быть даже больше, чем время, прошедшее между двумя
отказами, делая применение перезапуска непригодным. Эту проблему необходимо решать как с архитектурной, так и с алгоритмической точки зрения,
чтобы разрабатывать устойчивые системы экзафлопсного масштаба.
2.8. Параллельное и распределенное
машинное обучение
В техниках машинного обучения нередко используются подходы на основе централизованного исполнения на одном компьютере или в центре обработки данных как в отношении тренировки модели, так и в отношении
ее исполнения. Такие подходы совершенно не годятся, когда данные очень
велики или расположены на многочисленных и самых разных устройствах
хранения. Более того, при возникновении необходимости анализа крупных
наборов данных время последовательного исполнения может становиться очень большим, занимая дни или недели. Наиболее эффективный подход к сокращению времени исполнения предусматривает использование
моделей и инфраструктур параллельных и распределенных вычислений.
Параллельные компьютеры, такие как мультикластеры, облака и системы
64 Концепции больших данных
экзафлопсного масштаба, оснащенные распределенными и параллельными
инструментами и алгоритмами машинного обучения, обеспечивают масштабируемый способ анализа крупных наборов данных и получения результатов
в разумные сроки. Эта стратегия основана на управлении распределенными
данными и задействовании параллелизма, присущего большинству алгоритмов анализа данных и машинного обучения.
2.8.1. Стратегии параллельного обучения
Можно выделить три главные стратегии задействования параллелизма в алгоритмах машинного обучения:
независимый параллелизм задействуется путем параллельного выполнения полностью независимых процессов. Каждый процесс получает
доступ ко всему набору данных или к своему собственному разделу
и не общается и не синхронизируется с другими процессами во время
операций тренировки и обучения;
параллелизм «одна программа, несколько элементов данных» (SPMD, от
англ. single program multiple data) позволяет параллельно выполнять
набор процессов, исполняющих один и тот же алгоритм на разных
разделах набора данных; процессы такого рода взаимодействуют, обмениваясь частичными результатами во время исполнения;
параллелизм заданий является наиболее общей формой параллелизма,
поскольку каждый процесс может исполнять разные алгоритмы на наборе данных (на разных его разделах); процессы могут общаться в соответствии с разными способами, которые требуются параллельным
алгоритмом.
При необходимости эти три базовые модели можно комбинировать, чтобы
реализовывать гибридные параллельные и распределенные алгоритмы машинного обучения. Здесь мы описываем способы использования трех форм
параллелизма для разработки и исполнения параллельных алгоритмов классифицирования, кластеризации и усвоения ассоциативных правил.
2.8.1.1. Параллелизм в алгоритмах классифицирования
Среди алгоритмов классифицирования деревья решений пользуются заслуженной популярностью и являются эффективной техникой, в которой для
классифицирования элементов данных задействуется древовидная структура. В этих деревьях пути от корня к листу соответствуют правилам классифицирования набора данных, тогда как листья дерева представляют классы,
а узлы дерева – значения атрибутов.
Применение независимого параллелизма: независимый параллелизм может
задействоваться в построении дерева решений путем назначения процессу
задания построить дерево решений в соответствии с некоторыми парамет
рами. Если несколько процессов исполняются параллельно на разных вы-
Параллельное и распределенное машинное обучение 65
числительных узлах, то можно одновременно получить набор древовидных
классификаторов. Одно или несколько таких деревьев могут быть выбраны
в качестве классификаторов данных.
Применение параллелизма заданий: при применении параллелизма заданий с каждым поддеревом дерева решений, которое строится, чтобы представлять классификационную модель, ассоциирован один процесс. Поиск
происходит параллельно в каждом поддереве; отсюда степень параллелизма P равна количеству активных процессов в данный момент времени. Этот
подход можно реализовать с помощью схемы фермерского параллелизма,
при которой один процесс-мастер управляет вычислениями, а набору работников назначаются поддеревья. В результате этого единое дерево решений
строится за более короткое время по сравнению с последовательным построением деревьев.
Применение параллелизма «одна программа, несколько элементов данных»:
при параллелизме такого рода набор процессов выполняет одинаковый исходный код классифицирования элементов данных, принадлежащих подмножеству глобального набора данных. n процессов параллельно исполняют
поиск во всем дереве, используя раздел D/n набора данных D. Глобальный
результат получается путем обмена частичными результатами между процессами. Разбивка набора данных может выполняться двумя главными способами: (i) путем разбивки кортежей набора данных D на части посредством
назначения D/n кортежей в расчете на один процессор или (ii) путем разбивки k атрибутов каждого кортежа и назначения k/n атрибутов всех кортежей
каждому обрабатывающему узлу.
2.8.1.2. Параллелизм в алгоритмах кластеризации
Параллелизм в алгоритмах кластеризации можно задействовать как в технике кластеризации данных, так и в вычислении сходства или расстояния
между элементами данных путем вычисления на каждом процессоре расстояния/сходства другого раздела элементов. При параллельной реализации
алгоритмов кластеризации могут применяться три главные параллельные
стратегии, описанные ранее в этом разделе.
Применение независимого параллелизма: при независимом параллелизме
каждый процессор использует весь набор данных D и реализует свое задание
по кластеризации на основе разного количества кластеров ki. Для того чтобы
нагрузка между узлами была сбалансированной вплоть до завершения задания по кластеризации, новое задание по кластеризации назначается процессору, который завершил назначенную ему группировку.
Применение параллелизма заданий: в соответствии с параллелизмом заданий каждый процессор исполняет разные задания, исполняющие алгоритм
кластеризации, и сотрудничает с другими узлами, обмениваясь частичными
результатами. Например, в методах разбивки на разделы вычислительные
узлы могут работать на непересекающихся участках поискового пространства, используя весь набор данных. В иерархических методах процессор может отвечать за компоновку одного или нескольких кластеров. Он находит
66 Концепции больших данных
ближайший соседний кластер, вычисляя расстояние между своим кластером
и остальными. Затем все локальные кратчайшие расстояния обмениваются,
чтобы найти глобальное кратчайшее расстояние между двумя кластерами,
которые должны быть объединены. Новый кластер будет назначен одному из
двух процессоров, которые работали с объединенными кластерами.
Применение параллелизма «одна программа, несколько элементов данных»:
при параллелизме такого рода каждый узел исполняет одинаковый алгоритм
на другом разделе D/n набора данных, чтобы вычислить частичные результаты кластеризации. Затем локальные результаты, полученные на назначенных
разделах, распространяются между всеми процессорами, чтобы вычислить
глобальные значения на каждом процессоре. Глобальные значения используются на всех процессорах для запуска следующего шага кластеризации до
тех пор, пока не будет достигнуто схождение или не будет сделано заданное
количество шагов кластеризации. Стратегия «одна программа, несколько
элементов данных» также может использоваться для реализации алгоритмов кластеризации, где каждый узел генерирует локальную аппроксимацию
модели, которая на каждой итерации отправляется на другие узлы, которые,
в свою очередь, используют ее для улучшения своей модели кластеризации.
2.8.1.3. Параллелизм в алгоритмах усвоения
ассоциативных правил
Алгоритмы усвоения ассоциативных правил широко используются для обнаружения сложных ассоциаций в наборе данных. Обозначим через D набор
транзакций. Задание по добыче ассоциативных правил заключается в генерации всех ассоциативных правил, у которых поддержка (то есть частота,
с которой комбинация встречается в целом) и уверенность (частота, с которой ассоциативное правило соблюдается в наборе данных) больше, чем
указанные пользователем соответственно минимальная поддержка и минимальная уверенность.
Применение независимого параллелизма: независимый параллелизм может
задействоваться для параллельного выполнения алгоритмов усвоения ассоциативных правил, позволяя избегать зависимости данных между разными
процессами. Это можно делать путем разбивки данных на разделы и репликации данных и коллекции часто встречающихся элементов-кандидатов
таким образом, чтобы процессы выполнялись автономно. Например, метод
распределения кандидатов (Agrawal и Shafer, 1996), предложенный для реа
лизации алгоритма Apriori, разбивает коллекции элементов-кандидатов1,
но вместо разбивки транзакций базы данных и их обмена он выборочно их
реплицирует, чтобы каждый процесс мог продвигаться независимо.
1
Коллекция элементов-кандидатов (candidate itemset) – это потенциальная комбинация элементов, которые могут быть найдены в наборе транзакционных данных
и рассматриваются на предмет их соответствия определенным критериям, таким
как минимальная поддержка, в процессе обнаружения ассоциативных правил. –
Прим. перев.
Параллельное и распределенное машинное обучение 67
Применение параллелизма «одна программа, несколько элементов данных»:
при параллелизме такого рода набор данных D разбивается между узлами, тогда как коллекции элементов-кандидатов I реплицируются на каждом
процессоре. Каждый процесс p параллельно подсчитывает частичную поддержку Sp глобальных коллекций на своем локальном разделе набора данных
размера D/n. В конце этой фазы глобальная поддержка S получается путем
сбора всех локальных поддержек Sp. Хотя репликация коллекций элементовкандидатов минимизирует обмен данными, она неэффективно использует
память. Тем не менее благодаря низким коммуникационным накладным
расходам масштабируемость остается хорошей.
Применение параллелизма заданий: в соответствии с параллелизмом заданий набор данных D и коллекции I разбиваются на каждом процессоре. Каждый процесс p подсчитывает глобальную поддержку Si своей коллекции элементов-кандидатов Ip на всем наборе данных D. После сканирования своего
локального раздела D/n процесс каждую итерацию должен сканировать все
дистанционные разделы. Разбивка набора данных и коллекций элементовкандидатов минимизирует потребление памяти. Однако это требует больших
коммуникационных накладных расходов в архитектурах с распределенной
памятью, в результате чего данный подход менее масштабируем, чем предыдущий.
2.8.2. Стратегии распределенного обучения
В техниках распределенного анализа данных и машинного обучения используются системы распределенных вычислений, подключенные через интернет, чтобы хранить наборы данных и выполнять алгоритмы, задействуя
присущий им параллелизм путем обращения к данным на разных вычислительных узлах, и локально выполнять алгоритмы на этих узлах. Такой подход
годится для приложений, которые обычно имеют дело с очень крупными
объемами данных, расположенными на разных серверах, и которые невозможно анализировать на одном компьютере или площадке посредством
традиционных машин за приемлемое время. Стратегии распределенного
обучения предназначены для обработки источников данных, расположенных
на дистанционных площадках, таких как веб-серверы или ведомственные
данные, принадлежащие крупным предприятиям, либо потоков данных, поступающих из сенсорных сетей, социальных сетей или со спутников.
Последовательные машины не подходят для большинства приложений распределенного и повсеместного обучения, которые нуждаются в проведении
анализа источников больших данных. С другой стороны, продолжительное
время отклика, отсутствие должной эксплуатации распределенных ресурсов
и базовые особенности централизованных алгоритмов анализа данных не
очень хорошо работают в распределенных (и параллельных) средах. Поэтому,
как и в случае с параллельными техниками анализа данных, масштабируемые решения для распределенных приложений нуждаются в распределен-
68 Концепции больших данных
ной обработке данных, контролируемой компетентными исследователями
данных с учетом имеющихся ресурсов.
В большинстве распределенных алгоритмов одинаковый исходный код
выполняется на каждой площадке конкурентно и вычисляется по одной
локальной модели на площадку. Затем все локальные модели агрегируются/комбинируются на центральном узле или коллективно используются на
всех узлах с целью получения глобальной модели. Эта схема характерна для
нескольких алгоритмов распределенного машинного обучения. Среди них
наиболее распространенными являются ансамблевое обучение, метаобуче
ние, федеративное обучение и коллективная глубокая переработка данных.
Кроме того, следует отметить, что распределенные алгоритмы могут интегрировать серверы или кластеры, которые на одной площадке выполняют
параллельные алгоритмы, разработанные в соответствии с описанными параллельными подходами. Эта комбинация может использоваться для реализации очень больших распределенных приложений по анализу данных
и машинному обучению, в которых локальные модели, составляющие глобальную модель, вычисляются параллельно в соответствии с техниками, рассмотренными в предыдущем разделе.
2.8.2.1. Метаобучение
Техника метаобучения ориентирована на реализацию глобальной модели,
анализирующей набор распределенных наборов данных. Термин «мета
обучение» можно определить как обучение на усвоенных знаниях (Prodromidis и соавт., 2000). В сценарии классифицирования это достигается путем
усвоения на основе предсказаний, полученных из набора базовых классификаторов, оперировавших на общем валидационном наборе. Первоначальные
тренировочные наборы подаются на вход N алгоритмов обучения, которые
работают на разных узлах, чтобы построить N классификационных моделей (базовых классификаторов). Тренировочный набор метауровня строится путем комбинирования предсказаний базовых классификаторов на
общем валидационном наборе. Наконец, алгоритм метаобучения тренирует
глобальный классификатор на основе тренировочного набора метауровня.
Накопление результатов работы всех моделей (stacking) – это способ комбинирования нескольких моделей в метаобучении; указанный метод применяется к моделям, построенным с помощью разных алгоритмов обучения.
Результаты накапливаются, для того чтобы выяснить, какие именно классификаторы являются надежными, используя для этого другой алгоритм обуче
ния (метаученика), чтобы понять, как скомбинировать результаты базовых
учеников лучше всего.
2.8.2.2. Ансамблевое обучение
Ансамблевое обучение направлено на повышение точности модели путем
агрегирования предсказаний, сделанных набором учеников (Tan и соавт.
2016). Метод ансамблевого обучения конструирует набор классификаторов
Параллельное и распределенное машинное обучение 69
из тренировочных данных и реализует классифицирование путем стратегии
голосования (в случае классифицирования) или посредством усреднения
значений (в случае регрессии) из предсказаний, сгенерированных каждым
классификатором. В результате получается ансамблевый классификатор,
который очень часто демонстрирует более высокую точность классифицирования по сравнению с каждым базовым классификатором, который был
использован для его составления. Главные этапы, из которых состоит стратегия ансамблевого обучения для классифицирования данных, включают
использование инструмента разбивки, который разбивает входной набор
данных на тренировочный и тестовый наборы. Затем тренировочный набор
подается на вход N классификаторов, которые работают на разных узлах,
чтобы построить N независимых моделей. Наконец, голосователь V обращается к N моделям и проводит ансамблевое классифицирование путем назначения каждому экземпляру данных из тестового набора класса, предсказанного большинством из N моделей, произведенных на предыдущем этапе.
Самым важным является вопрос выявления хорошей или оптимальной
стратегии сборки базовых классификаторов. Наиболее часто используемыми подходами являются бустинг и бэггинг1. Бэггинг, который в классифицировании также называют голосованием, а в регрессии – усреднением,
комбинирует предсказанные классификации (предсказания), полученные
из набора моделей или из моделей одинакового типа для разных тренировочных наборов данных. Бэггинг также используется для решения проблемы
нестабильности результатов, присущей сложным моделям, применяемым
к относительно малым наборам данных.
Хотя бустинг, как и бэггинг, объединяет решения разных моделей, сводя
разнообразные результаты в единое предсказание, он рассчитывает единые
модели по-другому. В бэггинге модели получают, по сути дела, равный вес,
тогда как в бустинге весовые коэффициенты используются для придания
большего влияния более успешным моделям.
2.8.2.3. Коллективная глубокая переработка данных
В отличие от других подходов, коллективная глубокая переработка данных
(data mining) строит глобальную модель посредством комбинации частичных моделей, вычисленных на разных площадках. Это в корне отличается
от комбинирования набора полных моделей, сгенерированных на каждой
площадке на разделенных или реплицированных наборах данных. В коллективной глубокой переработке данных глобальная модель составляется непосредственно путем добавления соответствующего набора базисных функций.
Глобальное классифицирование основано на том, что любая функция может
быть выражена распределенным образом с помощью набора соответствую1
Бустинг (модельное усиление) – повышение результативности моделей за счет
комбинирования предсказаний многочисленных базовых учеников. Бэггинг (модельное усреднение) – отнесение объекта в тот класс, куда его отнесло большинство
алгоритмов. – Прим. перев.
70 Концепции больших данных
щих базисных функций, которые могут содержать нелинейные члены. Если
базисные функции ортонормальны, то локальный анализ генерирует результаты, которые могут быть эффективно использованы в качестве компонентов глобальной модели. Если в суммирующей функции присутствует
нелинейный член, то глобальная модель не подлежит полному разложению
между локальными площадками, и необходимо учитывать перекрестные
члены, включающие признаки из разных узлов. В исследовании Каргупта
(Kargupta и соавт., 1999) обсуждаются следующие ниже главные шаги стратегии коллективной глубокой переработки данных:
выявить надлежащее ортонормированное представление для класса
генерируемой модели данных;
сгенерировать приближенные коэффициенты ортонормированного
базиса в каждом узле;
если глобальная функция включает нелинейные члены, то переместить
выборку наборов данных из каждого узла в центральный узел и там
вычислить приближенные базисные коэффициенты, соответствующие
таким нелинейным членам;
скомбинировать локальные модели, чтобы сгенерировать глобальную
модель.
2.8.2.4. Федеративное обучение
Концепция федеративного обучения была впервые представлена компанией
Google в 2017 году. Она предназначена для анализа распределенных сырых
данных без их перемещения на отдельный сервер или в центр обработки данных. В рамках этой стратегии выбирается набор узлов, и всем узлам отправляется первая версия, содержащая модельные параметры модели машинного
обучения. Затем каждый узел исполняет модель, тренирует ее только на локальных данных и сохраняет локальную версию модели.
Федеративное обучение позволяет мобильным устройствам совместно усваивать коллективную модель обучения, держа все тренировочные данные
на борту, что повышает безопасность и конфиденциальность. Это работает
следующим образом: каждое устройство скачивает модель обучения с сервера, улучшает ее, обучаясь на хранящихся там локальных данных, а затем
резюмирует изменения в виде небольшого сфокусированного обновления.
Это обновление модели отправляется на центральный сервер или в облако,
где оно усредняется с другими локальными обновлениями с целью улучшения глобальной коллективной модели. Все тренировочные данные остаются
на устройстве, а индивидуальные обновления не хранятся на сервере, тем
самым обеспечивая конфиденциальность данных.
В целях расширения изначальной стратегии федеративного обучения,
основанной на центральном сервере, были предложены децентрализованные подходы к федеративному обучению. В децентрализованных стратегиях
Параллельное и распределенное машинное обучение 71
центральный сервер удаляется, и узлы могут координировать свои действия
по вычислению глобальной модели обучения самостоятельно. Эта децент
рализованная стратегия позволяет избегать одноточечных отказов, так как
обновления модели передаются только между взаимосвязанными узлами
без участия центрального сервера. Тем не менее ввиду большего количества
сообщений между узлами могут регистрироваться высокие коммуникационные накладные расходы. А это может влиять на выполнение глобальной
задачи усвоения.
Глава
3
Модели
программирования
для больших данных
В этой главе представлены и рассмотрены главные модели программирования, разработанные и используемые для реализации крупномасштабных
приложений по обработке больших данных. В частности, будет дано описание главных характеристик популярных моделей программирования на
основе MapReduce, рабочих потоков, массового синхронного параллелизма
(BSP, от англ. bulk synchronous parallel), разделенного глобального адресного
пространства (PGAS, от англ. partitioned global address space), а также SQLподобные модели.
3.1. Параллельное программирование
для приложений по обработке
больших данных
Поскольку реальный мир по своей сути параллелен, естественность параллельных вычислений совершенно очевидна. Написание последовательных
программ часто влечет за собой навязывание порядка на независимые задания, которые могут выполняться конкурентно. С другой стороны, параллельные машины обеспечивают более высокую вычислительную мощь, чем
один процессор.
Параллельное программирование для приложений по обработке больших данных 73
3.1.1. Необходимость в моделях параллельного
программирования
Модель программирования – это интерфейс, отделяющий высокоуровневые
свойства от низкоуровневых. Она предоставляет конкретные операции для
уровня программирования, находящегося выше, и требует реализации всех
операций на архитектурном уровне, находящемся ниже (Skillicorn и Talia,
1998a).
Модель параллельного программирования – это абстракция для архитектуры параллельного компьютера, которая помогает выражать параллельные
алгоритмы и приложения. Она может представлять самые разные задачи
для самых разных параллельных и распределенных систем. Модели параллельного программирования нередко являются ключевой характеристикой
фреймворков обработки больших данных, так как они влияют на исполнительную парадигму механизмов обработки больших данных, а также на то,
как пользователи конструируют и создают приложения (Wu и соавт., 2017).
Они позволяют отделять вопросы разработки программного обеспечения
от вопросов параллельного исполнения, одновременно с этим обеспечивая
абстракцию и стабильность.
Абстракция гарантируется по той причине, что операции модели находятся на более высоком уровне, чем операции положенных в основу архитектур.
За счет этого упрощается структура программного обеспечения и снижается
сложность его разработки, при этом гарантируя стабильность посредством
стандартного интерфейса. Как следствие модель может снижать усилия на
реализацию (например, компилятора и системы исполнения), так как решения по реализации принимаются не для каждой отдельной программы,
а определяются один раз для каждой целевой системы (Skillicorn и Talia,
1998a).
Модели вычислений, определяющие правила, принципы и процессы, с помощью которых проходят вычисления, тесно связаны с парадигмами параллельного программирования. Первостепенное различие заключается в том,
что модель программирования нуждается в практической заботе об аппаратной и программной реализации, тогда как модель параллельных вычислений
не обязана быть практической.
В последние годы был предложен целый ряд моделей программирования
для больших данных, каждая из которых имеет ту или иную направленность
и набор преимуществ.
3.1.2. Характеристики моделей программирования
Способность выражать высокоуровневые и низкоуровневые механизмы программирования отличает модели программирования на основе их уровня
абстракции (Talia, 2019). Программист использует высокоуровневые масшта-
74 Модели программирования для больших данных
бируемые модели для детализации только высокоуровневой логики приложения, скрывая при этом низкоуровневые детали, которые не требуются для
конструктивной схемы приложения, например инфраструктурно-зависимые
детали исполнения. С помощью таких моделей программист получает поддержку в создании приложения, а производительность приложения зависит
от компилятора, который оценивает исходный код приложения, оптимизируя его исполнение. С другой стороны, низкоуровневые масштабируемые
модели позволяют программистам напрямую взаимодействовать с вычислительными устройствами и устройствами хранения данных, составляющими
базисную инфраструктуру, и таким образом напрямую определять параллелизм приложений. Следующие далее разделы посвящены моделям программирования в противовес системам программирования (Gropp и Snir, 2013;
Belcastro и соавт., 2022). Система программирования, по сути дела, является
реализацией одной или нескольких моделей, которые могут разрабатываться
самыми разными способами (Talia, 2019), например:
с помощью общецелевого или предметно-специфичного языка, либо
языкового расширения, которое позволяет выражать параллелизм
в приложениях. Эта стратегия влечет за собой необходимость разработки новых языков параллельного программирования или интеграцию всестороннего набора параллельных конструкций и структур данных в существующие языки;
с помощью аннотаций в исходном коде приложения, которые указывают компилятору инструкции, подлежащие исполнению конкурентно.
При таком подходе параллельные инструкции отличаются от последовательных конструкций, и они идентифицируются явным образом
в программном коде с помощью специальных символов или ключевых
слов;
включение библиотеки в исходный код приложения с целью усиления
параллелизма в приложении. В настоящее время это наиболее популярный подход, поскольку он ортогонален к базисным языкам.
Поэтому, например, MapReduce и передача сообщений – это модели программирования, а Apache Hadoop и MPI – системы программирования, поддерживающие эти модели.
Учитывая широкий спектр приложений по обработке больших данных
и классов пользователей, среди которых есть как опытные программисты,
так и конечные пользователи, мы рассматриваем модели параллельного программирования на различных (высоких и низких) уровнях абстракции.
3.2. Модель на основе MapReduce
Модель программирования на основе парадигмы MapReduce была разработана компанией Google (Dean и Ghemawat, 2004) в 2004 году с целью преодоления трудностей, связанных с эффективной обработкой больших дан-
Модель на основе MapReduce 75
ных. Она поддерживает чрезвычайно масштабируемые и отказоустойчивые
распределенные вычисления, включая обработку данных на очень крупных
входных наборах данных.
3.2.1. Ключевые идеи, положенные
в основу MapReduce
Парадигма MapReduce была навеяна функциями преобразования map и редукции reduce, имеющимися в языках функционального программирования,
таких как LISP, и позволяет разработчикам создавать распределенные приложения на основе этих двух операций (Marozzo и соавт., 2012). В функциональном программировании интерфейсы программирования определяются
как функции, которые применяются к источникам входных данных, а вычисления обрабатываются как вычисления функций.
Еще одной важной концепцией, лежащей в основании модели MapReduce,
является стратегия «разделяй и властвуй». В частности, целесообразная стратегия работы с большими данными состоит в том, чтобы (i) делить задачу на
более мелкие подзадачи, (ii) исполнять независимые подзадачи параллельно
с использованием нескольких работников и (iii) комбинировать промежуточные результаты, полученные от каждого отдельного работника. В общем
случае работниками могут быть вычислительные потоки в ядре процессора,
ядра в мультиядерном процессоре, несколько процессоров в компьютере или
многочисленные машины в вычислительном кластере.
3.2.2. Модель программирования
Программист определяет две фазы обработки модели MapReduce: преобразование (map) и редукцию (reduce). Функция map принимает на вход пару
(ключ, значение) и производит список промежуточных пар (ключ, значение):
map(k1, v1) → list(k2, v2).
Функция reduce объединяет все промежуточные значения с одинаковым
промежуточным ключом:
reduce(k2, list(v2)) → list(v3).
Параллелизм достигается на обеих фазах: в фазе преобразования, в которой ключи могут обрабатываться конкурентно на разных компьютерах
(вызовы функции map распределяются между компьютерами путем сегментирования входных данных), и в фазе редукции, в которой редукторы, работающие с разными ключами, могут исполняться конкурентно. Как
следствие было продемонстрировано, что алгоритмы MapReduce могут
масштабироваться от одного сервера до сотен тысяч серверов. Более того,
76 Модели программирования для больших данных
подход MapReduce скрывает от программиста детали распараллеливания,
что упрощает его применение. Это одно из главных преимуществ данной
модели, так как требуются только функции map и reduce. Разработчики сосредоточиваются именно на том, какие вычисления необходимо выполнять,
а не на том, как эти вычисления выполняются или как данные отправляются
в процессоры.
В качестве примера приложения MapReduce давайте рассмотрим генерацию инвертированного индекса (Sarkar и соавт., 2015). При наличии большого корпуса документов этот индекс содержит набор слов (или индексных
терминов), указывая идентификаторы всех документов, содержащих каждое
слово. В этом случае можно эффективно задействовать подход MapReduce,
при котором функция map генерирует последовательность пар <word, documentID> по каждому входному документу. Функция reduce берет все пары для
данного слова, сортирует соответствующие идентификаторы документов
и выдает пару <word, list(documentID)>. Как показано на рис. 3.1, инвертированный индекс для входных документов формируется из множества всех выходных пар, созданных функцией reduce. Пример приложения по созданию
инвертированного индекса на крупном наборе веб-документов, основанный
на модели MapReduce, представлен в подразделе «Пример программирования» раздела 4.2.1.
Рис. 3.1 Инвертированный индекс,
генерируемый с помощью модели MapReduce
3.2.3. Программы MapReduce
3.2.3.1. Фазы преобразования и редукции
Вычислительная заявка (job) – это программа MapReduce, состоящая из исходного кода для фаз преобразования и редукции, настроечных параметров
(например, место хранения выходных данных) и набора входных данных,
Модель на основе MapReduce 77
который хранится в базисной распределенной файловой системе. В традиционных вычислительных системах, таких как центры обработки данных
или вычислительные кластеры, распределенные файловые системы, по сути
дела, являются наиболее популярным решением по доступу к входным/выходным данным в системах MapReduce. Это связано с тем, что они могут
анализировать очень крупные наборы данных, от гигабайтов до петабайтов;
и, следовательно, входные данные обычно не помещаются в памяти одного
компьютера.
Каждая вычислительная заявка MapReduce делится на более мелкие единицы, именуемые заданиями (task). Преобразовательные задания называются преобразователями (mapper), а редукционные задания – редукторами
(reducer). Для определения сложных приложений, которые невозможно написать с помощью одной вычислительной заявки MapReduce, пользователям
может потребоваться составлять рабочие потоки из заявок MapReduce. Такие
рабочие потоки легко реализуются, так как результаты на выходе из одной
заявки обычно записываются в распределенную файловую систему и могут
использоваться на входе в очередную вычислительную заявку, в результате
чего программа MapReduce может содержать несколько раундов операций
преобразования и редукции.
Современные системы MapReduce, такие как Hadoop, построены по модели
«мастер–работник» (master-worker). Пользовательский узел отправляет вычислительную заявку узлу-мастеру, который определяет незанятых работников и назначает каждому из них преобразовательное или редукционное
задание. Мастер координирует весь поток вычислительных заявок MapReduce, управляя преобразовательными или редукционными заданиями. По
завершении всех заданий узел-мастер предоставляет результат пользовательскому узлу. Приведенные ниже шаги MapReduce подробно описывают
весь процесс обработки в приложении MapReduce (см. рис. 3.2).
МАСТЕР
Преобра
зователь
Редуктор
Преобра
зователь
Входные
данные
Редуктор
Выходные
данные
Преобра
зователь
Порции данных
Преобра
зователь
Редуктор
Промежуточные
результаты
Окончательные
результаты
Рис. 3.2 Поток исполнения в модели MapReduce
78 Модели программирования для больших данных
1. Мастеру отправляется описатель вычислительной заявки, в котором
описывается задание MapReduce и другая информация, например мес
тоположение входных данных.
2. Мастер запускает несколько процессов-преобразователей и редукторов на разных машинах, основываясь на описателе. Он также собирает входные данные из их местоположения и делит их на несколько
разделов, которые распределяются между разными преобразователями.
3. Каждый процесс-преобразователь использует функцию map (указанную как составная часть описателя вычислительной заявки), чтобы построить список промежуточных пар (ключ-значение) после получения
своего раздела данных (то есть порции данных). Исходный код, определенный пользователем для функции map, задает порядок генерации
пар (ключ, значение) из входных данных.
4. Для всех пар с одними и теми же ключами выделяется один и тот же
процесс-редуктор, в результате чего каждый процесс-редуктор выполняет функцию reduce (указанную в описателе вычислительной заявки),
которая объединяет все данные, ассоциированные с одним и тем же
ключом, чтобы произвести потенциально меньший набор значений.
Пользовательский исходный код для функции reduce определяет способ
комбинирования значений.
5. Затем собираются результаты на выходе из каждого процесса-редуктора (r файлов, где r – это количество редукторов) и отправляются в мес
то, указанное в описателе вычислительной заявки, формируя окончательные выходные данные.
3.2.3.2. Фаза комбинирования
Редукционные задания в потоке исполнения программы MapReduce не могут
начинаться до тех пор, пока не завершится вся фаза преобразования, воздвигая препятствие для высокой производительности. С целью увеличения
скорости можно выполнять шаг комбинирования, включающий фазу миниредукции на локальном результате фазы преобразования. Указанный шаг
выполняет частичную агрегацию данных перед их передачей редукторам
по сети. Это может происходить только в том случае, если функция reduce
ассоциативна и коммутативна: значения можно комбинировать в любом
порядке и получать один и тот же результат. При таком сценарии используется так называемый комбинатор (combiner), чтобы агрегировать результаты
локальных преобразований:
combine(k2, list(v2)) → list(v3).
Во многих случаях одна и та же функция может использоваться как для
комбинирования, так и для окончательной редукции, что позволяет сокращать объем промежуточных данных и сетевой трафик.
Модель на основе MapReduce 79
3.2.3.3. Фаза перетасовки и сортировки
Между фазами преобразования (с комбинированием) и редукции происходит
неявная распределенная операция группировки (group by) на промежуточных
ключах. Этот процесс, именуемый перетасовкой и сортировкой, передает
результаты работы преобразователей редукторам, объединяя и сортируя их
таким образом, что промежуточные данные сортируются по ключам и поступают в каждый редуктор. Поскольку промежуточные ключи не хранятся
в распределенной файловой системе, они выплескиваются на локальный
диск каждого компьютера в кластере. Когда преобразователь завершает
записьсвоих отсортированных выходных файлов, планировщик MapReduce
уведомляет редукторы о том, что выходные файлы этого преобразователя
готовы к извлечению. Редукторы подключаются к каждому преобразователю
и получают файлы, содержащие отсортированные пары (ключ–значение) для
своих разделов. После перемещения данных каждый редуктор берет файлы
у преобразователей, объединяет их, поддерживая порядок сортировки, и начинает редукцию на объединенных входных данных.
3.2.4. Применения и соображения
о производительности
Системы распределенных вычислений широко используются для обработки
данных уже много лет. Эти системы хорошо справляются с вычислительно-интенсивными заявками, однако для управления огромными объемами распределенных данных им требуется большая пропускная способность
сети. С целью смягчения этой проблемы модель MapReduce предусматривает
использование такой функциональности, как локальность данных, чтобы
максимизировать производительность и экономить энергию, размещая вычислительные задания рядом с входными данными (например, на одном
узле, в одной стойке). За счет этого появляется возможность уменьшать узкое
место в пропускной способности сети, которое нередко возникает в системах
распределенного анализа данных.
Более того, поскольку данные оцениваются во время обработки, модель
MapReduce можно использовать для параллельной обработки полуструктурированных или неструктурированных данных, в отличие от реляционных
СУБД, которые предназначены для хранения и обработки структурированных данных. Для обработки очень крупных объемов полуструктурированных
или неструктурированных данных в распределенных/параллельных системах, задействующих сотни или тысячи компьютеров, модель должна выдерживать отказы в работе машин. Когда работник отказывает, задание исполняется повторно другим работником. Необходимость в перезапуске всей
вычислительной заявки MapReduce возникает только в самом худшем случае,
когда отказывает вычислительный узел, на котором находится мастер.
80 Модели программирования для больших данных
Благодаря описанным выше характеристикам модель MapReduce широко
используется для реализации масштабируемых алгоритмов и приложений по
анализу данных, которые выполняются на многочисленных машинах с целью эффективного анализа крупных объемов данных. Она была разработана для применения в различных предметных областях, таких как глубокая
переработка данных и машинное обучение, анализ социальных сетей, финансовый анализ, извлечение и обработка изображений, научная симуляция, автоматический сбор данных поисковым роботом, машинный перевод
и биоинформатика. Модель MapReduce позволяет в полной мере эксплуатировать параллелизм данных, обеспечивая эффективное выполнение таких
приложений в распределенных системах, сложность которых в основном
связана с большим объемом обрабатываемых данных. Сегодня MapReduce
широко рассматривается как одна из наиболее важных моделей параллельного программирования для распределенных систем.
Стоит отметить, что эффективность программ MapReduce не всегда гарантирована. Фундаментальное преимущество этой модели программирования
заключается в том, что она требует написания только фаз преобразования
и редукции. Тем не менее на производительность и масштабируемость могут существенно влиять фаза перетасовки и объем данных, генерируемых
функцией map. Дополнительные модули, такие как комбинатор, помогают
уменьшать объем данных, записываемых на диск и передаваемых по сети.
В системах на основе MapReduce используется еще одна распространенная
оптимизация, которая называется спекулятивным исполнением1. Если узел
доступен, но не справляется с работой (так называемая ситуация «доходяга», от англ. straggler), модель MapReduce запускает спекулятивный (или
резервный) дубликат своих заданий на другом компьютере, чтобы быстрее
завершить вычисления.
Более того, применение парадигмы MapReduce в программах, которым
необходимо умещать данные в основную память одной машины или малого
кластера, как правило, не является эффективным. Фреймворки MapReduce,
по сути дела, призваны быть отказоустойчивыми и восстанавливаться пос
ле отказов узлов во время вычислений. Это достигается за счет хранения
промежуточных результатов в распределенном хранилище. Однако такое
восстановление обходится дорого и становится необходимым только в том
случае, если вычисления предусматривают участие большого количества машин и длительное время вычислений. При наступлении отказа есть возможность перезапуска вычислительной заявки, на исполнение которой требуется
относительно мало времени. Фреймворки, которые содержат все данные
в памяти и перезапускают вычисления при наступлении отказов, в таких
случаях работают быстрее модели MapReduce.
1
Син. упреждающее исполнение. – Прим. перев.
Модель на основе рабочего потока 81
3.3. Модель на основе рабочего потока
Модель программирования на основе рабочих потоков стала полезной ввиду
того, что она помогает преодолевать сложности в научных и деловых приложениях. Широкое распространение систем высокопроизводительных вычислений позволило ученым и инженерам разрабатывать более сложные
программы для доступа к огромным хранилищам данных и их обработки на
платформах распределенных вычислений. Большинство этих приложений
представляют собой рабочие потоки, в которых комбинируются методы анализа данных, научных вычислений и сложной симуляции (Belcastro и соавт.,
2019).
Рабочий поток – это серия действий, событий или заданий, которые должны быть осуществлены, чтобы достичь цели и/или результата. Коалиция по
управлению рабочими потоками (Workflow Management Coalition)1 определяет термин рабочего потока как полную или частичную автоматизацию делового процесса, в ходе которого документы, информация или задания передаются
от одного участника к другому с целью выполнения действий в соответствии
с набором процедурных правил. Такое же описание может быть использовано
для научных процессов, состоящих из многочисленных шагов обработки,
связанных между собой, чтобы описывать данные и/или управлять зависимостями. Здесь термин «процесс» относится к набору заданий, связанных
между собой с целью производства продукта, вычисления результата или
предоставления услуги. Таким образом, каждое задание (или действие) –
это часть работы, которая представляет собой один логический шаг во всем
процессе.
Модель программирования на основе рабочих потоков представляет собой
четко определенные и, возможно, повторяющиеся шаблоны или систематические группировки действий, направленные на достижение определенного
преобразования данных (Talia и Trunfio, 2012). В ней принят декларативный
подход к выражению высокоуровневой логики многих типов приложений,
скрывая при этом низкоуровневые детали, не имеющие существенного значения для разработки приложения. Одним из существенных преимуществ
рабочих потоков является то, что после определения их можно хранить и извлекать, чтобы изменять и/или повторно исполнять. За счет этого у пользователей появляется возможность разрабатывать общие шаблоны и их ре
использовать в многочисленных контекстах. Система управления рабочими
потоками (WMS, от англ. workflow management system) облегчает определение, разработку и исполнение процессов. Критически важной характеристикой такой системы во время исполнения рабочего потока является
координация составляющих рабочий поток операций разных действий (или
их претворение).
1
См. https://wfmc.org/.
82 Модели программирования для больших данных
Рабочий поток программируется как граф, который состоит из конечного
множества ребер и вершин, причем каждое ребро направлено от одной вершины к другой. В модели рабочего потока вершины представляют конкретные задания, действия или этапы внутри совокупного процесса, а ребра –
поток или последовательность заданий, указывающий порядок, в котором
они должны исполняться. Например, рабочий поток анализа данных может
быть сконструирован как серия заданий по предобработке, анализу и пост
обработке. Рабочий поток может быть реализован в виде программы с использованием языка программирования, библиотеки или системы, которая
позволяет выражать базовые шаги рабочего потока и включает механизмы
их оркестровки.
3.3.1. Шаблоны рабочих потоков
Задания рабочего потока могут комбинироваться самыми разными способами (например, в виде последовательных или параллельных конструкций),
что позволяет разработчикам удовлетворять потребности широкого спектра
сценариев применения (см. рис. 3.3). В следующих далее разделах рассмат
риваются основные шаблоны рабочих потоков.
Ветвление
Последовательность
Синхронизация
Повторение
Рис. 3.3 Шаблоны рабочих потоков
3.3.1.1. Последовательность
Шаблон последовательности является одним из самых простых и распространенных шаблонов компоновки заданий в рабочем потоке. Он обозначает
очередность заданий, которые должны быть выполнены в конкретном порядке. Эти задания соединены ориентированными ребрами, указывающими
направление, в котором течет управление, определяя последовательный порядок исполнения заданий.
3.3.1.2. Ветвления
Шаблон ветвления описывает ситуации, в которых ветвь рабочего потока
расщепляется на две или более разных ветвей. При этом можно выделить
три варианта ветвления (Van Der Aalst и соавт., 2003):
Модель на основе рабочего потока 83
ветвь AND, при которой поток управления в ветви расщепляется на
конкурентные потоки исполнения в каждой последующей ветви;
ветвь XOR, при которой поток управления в ветви направляется только в одну из ветвей, генерируемых конструкцией ветвления. Решение
о том, в какую из последующих ветвей направить поток управления,
принимается по результатам оценивания условий, связанных с каждым из исходящих ребер;
ветвь OR, при которой поток управления в ветви расщепляется на конкурентные потоки исполнения в одной или нескольких последующих
ветвях по результатам, как и в ветви XOR, оценивания условий, связанных с каждым из выходящих ребер. При этом, в отличие от ветви XOR,
можно выбирать несколько ветвей.
3.3.1.3. Синхронизация
Шаблоны синхронизации описывают ситуации, в которых несколько потоков
управления в одной или нескольких ветвях должны быть объединены в одну
ветвь. Такие сценарии часто встречаются в реальных рабочих потоках, при
которых исполнение определенного задания должно дожидаться завершения одного или нескольких предшествующих заданий. На практике могут
встречаться три типа синхронизации:
соединение AND, при котором все входящие ветви активны и рабочий
поток нуждается в том, чтобы все они были завершены до перехода
к последующему заданию;
соединение XOR, при котором только одна из входящих ветвей должна
быть завершена до перехода к заданию;
соединение OR, при котором хотя бы одна из входящих ветвей активна
и должна быть завершена до передачи управления последующему заданию.
3.3.1.4. Повторение
Шаблоны повторения описывают различные способы указания повторов.
Наиболее общей формой повторения является шаблон в виде произвольного
цикла, который указывает на повторение одного или нескольких заданий
в рамках рабочего потока. Эти циклы имеют неструктурированную форму
и представлены в шаблоне круговыми путями, а не конкретной конструкцией. Более того, цикл может иметь более одной точки входа или выхода, и не
все задания могут исполняться одинаковое количество раз. В отличие от
этого типа повторения, шаблон в виде структурированного цикла описывает
конкретный набор заданий, повторяющихся с конкретным условием завершения, которое оценивается либо до, либо после каждой итерации. Цикл
имеет только одну точку входа и выхода, и его можно представить в виде
конкретной конструкции в рабочем потоке. Структурированный цикл эквивалентен реализации повторения с помощью цикла while ... do или repeat ...
84 Модели программирования для больших данных
until, тогда как произвольные циклы эквивалентны инструкции goto. Третьим
проявлением повторения является шаблон рекурсии, который описывает ситуации, в которых конкретное задание повторяется путем самовызова.
3.3.2. Ориентированные ациклические графы
Когда в рабочем потоке нет циклов, он называется ориентированным ацик
лическим графом (DAG, от англ. directed acyclic graph), который является
наиболее часто используемой структурой программирования в управлении
рабочими потоками (Talia, 2013). В частности, ориентированный ациклический граф является:
ориентированным: если существует несколько заданий, то каждое из
них должно иметь хотя бы одно предшествующее или последующее
задание, или оба. Однако некоторые ориентированные ациклические
графы имеют несколько параллельных заданий, что означает отсутствие зависимостей;
ациклическим: задания не могут генерировать данные, которые ссылаются сами на себя, потенциально приводя к бесконечному циклу. Это
означает, что в ориентированном ациклическом графе нет циклов.
Парадигма ориентированного ациклического графа эффективна для моделирования сложных процессов анализа данных, таких как приложения
по глубокой переработке данных, которые могут эффективно исполняться
в системах распределенных вычислений, например на облачной платформе.
При этом необходимо учитывать два типа зависимостей: зависимости от
данных и зависимости от управления. В частности, в первом случае результат
на выходе из задания служит входом для последующих заданий, тогда как
в последнем случае определенные задания должны быть завершены до старта другого задания или набора заданий. Ориентированные ациклические
графы могут легко моделировать самые разные типы приложений, в которых
вход, выход и задания одного приложения зависят от заданий другого приложения (см. рис. 3.4).
Задание
Задание
Задание
Входные
данные
Задание
Задание
Задание
Задание
Задание
Задание
Задание
Выходные
данные
Задание
Рис. 3.4 Поток исполнения в ориентированном ациклическом графе
Модель на основе передачи сообщений 85
Задания приложения с использованием ориентированного ациклического
графа и их зависимости можно определять одним из двух способов:
явно, когда зависимости между заданиями определяются с помощью
явных инструкций (например, T2 зависит от T1);
неявно, когда система выводит зависимости между заданиями автоматически (например, T2 читает входные данные О1, которые являются результатом на выходе из T 1), анализируя их взаимосвязи
«вход–выход».
Стоит отметить, что модель на основе ориентированного ациклического
графа является строгим обобщением модели MapReduce. Согласно модели
MapReduce, распределенные вычисления на крупном наборе данных могут
выполняться с помощью двух типов вычислительных фаз: преобразования
и редукции. Одна пара преобразования и редукции выполняет один уровень
агрегации данных, однако сложные вычисления обычно нуждаются в нескольких таких фазах, в результате чего получается ориентированный ацик
лический граф операций. Ориентированные ациклические графы оказались
чрезвычайно полезными в различных фреймворках по работе с большими
данными, включая Spark. Среди преимуществ использования данной модели – более высокая гибкость по сравнению с моделью MapReduce и более
качественная глобальная оптимизация. Ориентированный ациклический
граф можно оптимизировать, по сути дела, переставляя и комбинируя операторы везде, где это возможно. Например, если взять две операции, такие
как преобразование и фильтрация, то оптимизация может заключаться в их
выполнении в обратном порядке, так как фильтрация может уменьшать количество записей, которые подвергаются операции преобразования.
Более сложная модель рабочего потока основана на ориентированных
циклических графах (DCG, от англ. directed cyclic graph), в которых циклы
представляют собой неявный или явный механизм управления циклами или
итерациями. В этом случае граф рабочего потока нередко описывает сеть
заданий, узлы которой представляют услуги либо экземпляры программных компонентов, либо более абстрактные объекты управления. Ребра графа
представляют сообщения, потоки данных или каналы, которые позволяют
услугам и компонентам обмениваться работой или информацией.
3.4. Модель на основе передачи
сообщений
Модель на основе передачи сообщений – это хорошо известная парадигма,
которая обеспечивает фундаментальные механизмы межпроцессной коммуникации (IPC, от англ. inter-process communication) в системах распределенных вычислений, в которых каждый элемент обработки имеет свою
86 Модели программирования для больших данных
собственную частную память. Если говорить о межпроцессной коммуникации, то есть о механизме, предоставляемом операционной системой, который позволяет процессам общаться друг с другом, то они могут общаться
с помощью двух механизмов: коллективной памяти и распределенной памяти, или передачи сообщений. Вследствие этого модели параллельного
программирования обычно классифицируются в зависимости от способа
использования памяти.
3.4.1. От коллективной памяти к передаче
сообщений
Каждый процесс в модели на основе коллективной памяти имеет доступ
к коллективному адресному пространству. Коммуникация между процессами через коллективную память требует коллективного использования некоторых переменных, что полностью зависит от способа, которым программист это реализует. Предположим, имеется два процесса, P1 и P2, которые
исполняются конкурентно и коллективно используют ресурсы или информацию от еще одного процесса. P1 генерирует информацию о конкретных вычислениях или используемых ресурсах и сохраняет ее в коллективной памяти
в виде записи. Когда P2 нужно использовать коллективную информацию, он
просматривает запись в коллективной памяти, принимает к сведению информацию, сгенерированную процессом P1, и действует соответствующим
образом. Процессы могут использовать коллективную память, чтобы извлекать информацию в виде записи из еще одного процесса, а также для других
целей. В дополнение к этому коллективная память может использоваться
процессами для извлечения информации и передачи любой конкретной информации иным процессам.
С другой стороны, в модели на основе передачи сообщений приложение
работает как набор автономных процессов, каждый из которых имеет свою
собственную локальную память и общается с другими процессами посредством отправки и получения сообщений. Когда данные отправляются в сообщении, отправляющий и получающий процессы должны сотрудничать
в передаче данных из локальной памяти одного из них в локальную память
другого. Если два процесса, P1 и P2, из предыдущего примера хотят общаться
друг с другом, то сначала они должны установить коммуникационный канал
и начать обмениваться сообщениями, используя базовые примитивы. Эта
абстракция может реализовываться самыми разными способами. Хотя концепция передачи сообщений и кажется простой, она нуждается в принятии
ряда конструктивных решений, которые на практике определяют множество
разных реализаций передачи сообщений. Более подробно о базовых принципах и примитивах передачи сообщений рассказывается в следующих далее
разделах.
На рис. 3.5 показаны две модели в сравнении.
Модель на основе передачи сообщений 87
Частная память
P1
P2
PN
M1
M2
MN
P1
P2
PN
Коллективная память
Рис. 3.5 Коллективная память в сравнении с передачей сообщений
3.4.2. Примитивы передачи сообщений
В модели на основе передачи сообщений имеется два главных примитива,
которые используются набором процессов для обмена сообщениями во время их исполнения (см. рис. 3.6):
Отправить(получатель, сообщение): процесс отправляет сообщение
другому процессу, обозначенному как получатель;
Получить(отправитель, сообщение): процесс получает сообщение от
другого процесса, обозначенного как отправитель.
Инициализация
Вычисления и общение
Финализация
Рис. 3.6 Поток исполнения в модели на основе передачи сообщений
Метод реализации операций отправки и получения на практике обуславливает разные реализации передачи сообщений. В частности, различают
прямую либо косвенную, буферизованную либо небуферизованную, а также блокирующую либо неблокирующую передачу сообщений.
3.4.2.1. Прямая и косвенная передача сообщений
При прямой передаче сообщений существует связь между двумя процессами, которые обмениваются данными, то есть личность получателя известна,
и сообщение отправляется напрямую. Одним из существенных недостатков
88 Модели программирования для больших данных
прямой передачи сообщений является отсутствие модульности. Изменение
идентификатора процесса, по сути дела, требует обновления каждого отправителя и получателя, имеющего соединение с этим процессом.
С другой стороны, при косвенной передаче сообщений для доставки сообщений задействуются почтовые ящики или порты, которые могут быть
привязаны к получающему процессу. От прямой передачи сообщений она
отличается тем, что тот же порт может быть впоследствии назначен еще одному процессу. Отправитель не знает, какой процесс получит его сообщение.
Более того, несколько процессов могут отправлять сообщения на один и тот
же порт, что обеспечивает многопроцессные связи и более высокую гибкость.
При косвенной передаче сообщений перед примитивами отправки и получения происходит создание почтового ящика:
1. A = создатьПочтовыйЯщик(): создать почтовый ящик и назначить его
переменной A;
2. Отправить(A, сообщение): отправить сообщение в почтовый ящик A;
3. Получить(A): получить сообщение из почтового ящика A.
3.4.2.2. Блокирующая и неблокирующая передача
сообщений
Еще одно существенное различие – блокирующая и неблокирующая передача
сообщений. Термины «блокирующая/неблокирующая» и «синхронная/асинхронная» используются как взаимозаменяемые. Операция отправки является блокирующей, если отправитель должен ждать до тех пор, пока получатель
не получит сообщение. И точно так же, если получатель должен ждать до тех
пор, пока сообщение не будет получено, операция получения является блокирующей. Следовательно, отправляющий процесс создает сообщение с данными, подлежащими коллективному использованию с получающим процессом, включая заголовок с указанием обрабатывающего элемента и процесса,
которым данные должны быть переданы, и вещает его в сеть. После того
как сообщение будет помещено в буфер получателя, отправляющий процесс
продолжает свое исполнение. Получающий процесс должен знать, что он
ожидает данные, и он указывает на свою готовность получить сообщение,
выполняя операцию получения. Если ожидаемые данные еще не поступили,
то получающий процесс будет блокировать (или ждать) до тех пор, пока они
не поступят, в качестве своеобразной синхронизации.
С другой стороны, неблокирующий процесс считается асинхронным. Отправитель доставляет сообщение и продолжает свои операции, а получатель
получает правильное либо нулевое сообщение. Ключевым вопросом в неблокирующем примитиве получения является метод, которым принимающий
процесс узнает о том, что сообщение поступило. Стоит отметить, что отправителю во время передачи сообщения более естественно действовать неблокирующим образом, так как может понадобиться отправка сообщения нескольким процессам. Однако в случае безуспешности отправки отправитель
Модель на основе передачи сообщений 89
ожидает подтверждения от получателя. И точно так же, поскольку информация из полученного сообщения может быть использована для последующего
исполнения, получателю разумнее блокировать. И аналогичным образом
если передача сообщения будет продолжаться отказом, то получатель будет
вынужден ждать неопределенное время. По этим причинам существуют три
принципиально рекомендуемые комбинации:
блокирующая отправка и блокирующее получение, именуемые рандеву-коммуникацией;
неблокирующая отправка и неблокирующее получение;
неблокирующая отправка и блокирующее получение; этот вариант используется чаще всего.
3.4.2.3. Буферизация
Между моделями на основе передачи сообщений можно провести различие,
если принять во внимание размер очереди получателя. Существует три альтернативы:
очередь нулевой емкости (без очереди): отправитель должен ждать до тех
пор, пока получатель не будет готов принять сообщение. Этим обес
печивается соблюдение рандеву;
ограниченная очередь: очередь вмещает не более n сообщений, или
n байт сообщений. Сообщения могут ставиться в очередь до тех пор,
пока очередь не будет заполнена; в противном случае отправитель
будет вынужден блокировать;
неограниченная очередь: отправителям ни разу не приходится ждать.
При принятии этого решения разработчики должны проявлять осторожность, поскольку физические ресурсы ограничены, и слишком
большое количество сообщений в очереди может иметь опасные последствия.
3.4.3. Групповая коммуникация
Коммуникация «один к одному» (также именуемая одноадресной коммуникацией, или коммуникацией «точка–точка») является самым базовым
видом коммуникации на основе сообщений, в которой процесс с одним отправителем посылает сообщение процессу с одним получателем. Некоторые
высокопараллельные распределенные приложения нуждаются в том, чтобы
система передачи сообщений дополнительно включала примитивы групповой коммуникации с целью повышения производительности и простоты
разработки. Существует три формы групповой коммуникации, в зависимости от наличия одного или нескольких отправителей и получателей: «один
ко многим», «многие к одному» и «многие ко многим».
90 Модели программирования для больших данных
3.4.3.1. Коммуникация «один ко многим»
В коммуникации «один ко многим» сообщение, отправленное одним отправителем, получают несколько получателей. Коммуникация «один ко многим»
еще называется многоадресной коммуникацией. В частности, процессыполучатели сообщений создают группу, которая может быть закрытой или
открытой. Закрытая группа – это группа, в которой только члены группы
могут отправлять сообщения в группу, при этом внешний процесс не может
отправлять сообщение всей группе, а только конкретному члену. С другой
стороны, открытая группа – это группа, в которой любой процесс в системе может отправлять сообщение группе в целом. Частным случаем многоадресной коммуникации является широковещательная коммуникация, при
которой сообщение отправляется всем процессорам, подключенным к сети.
3.4.3.2. Коммуникация «многие к одному»
В коммуникации «многие к одному» несколько отправителей посылают сообщения одному получателю. Единственный получатель может быть избирательным или неизбирательным. Избирательный получатель идентифицирует
единственного отправителя; обмен сообщениями происходит только тогда,
когда этот отправитель посылает сообщение. Неизбирательный получатель
задает набор отправителей, и если один из этих отправителей посылает сообщение этому получателю, то происходит обмен сообщениями. В схемах
коммуникации «многие к одному» ключевой трудностью является недетерминизм, так как заранее неизвестно, у какого члена (или членов) группы
станет доступна информация первым.
3.4.3.3. Коммуникация «многие ко многим»
В коммуникации «многие ко многим» несколько отправителей посылают
сообщения нескольким получателям. В схемах коммуникации «многие ко
многим» критически важным является вопрос упорядоченной доставки сообщений. Упорядоченная доставка сообщений гарантирует, что все сообщения будут доставлены в порядке, приемлемом для приложений, отправленных всем получателям.
3.5. Модель на основе массового
синхронного параллелизма
Многие графовые и линейно-алгебраические алгоритмы нуждаются в нескольких итерациях, в ходе которых результат одной итерации подается на
вход следующей. Это не только медленно, но и неэффективно, поскольку
по завершении каждой итерации данные приходится записывать в файлы
Модель на основе массового синхронного параллелизма 91
и перемещать между вычислительными узлами. Например, для каждой фазы
алгоритма обработки графа может требоваться широковещательная трансляция полного состояния графа по сети, что влечет за собой значительные
дополнительные затраты на коммуникацию. Модель на основе массового
синхронного параллелизма (BSP, от англ. bulk synchronous parallel) (Valiant,
1990) – это модель параллельных вычислений, разработанная Лесли Валиантом из Гарвардского университета. В конце 1980-х годов разработка компьютеров, которые могли бы справляться с параллельным программированием,
становилась все более сложной ввиду отсутствия единой эталонной модели.
Валиант выступал за создание парадигмы, которая связывала бы аппаратное
и программное обеспечение так же, как это делала модель фон Неймана, но
для параллельных машин. Парадигма Валианта позволяет программисту
избегать дорогостоящего управления памятью и коммуникациями, достигая
при этом низкой степени синхронизации.
3.5.1. Супершаг
Компьютер, построенный по модели на основе массового синхронного параллелизма, состоит из следующих ниже компонентов (см. рис. 3.7):
1) нескольких обрабатывающих элементов, каждый из которых выполняет локальные вычисления;
2) маршрутизатора, который доставляет сообщения «точка–точка» между
парами вычислительных элементов;
3) аппаратного обеспечения, позволяющего синхронизировать все
или часть обрабатывающих элементов с регулярными интервалами
в L единиц времени, где L – это задержка коммуникации или параметр
периодичности синхронизации.
PE
PE
PE
МАРШРУТИЗАТОР
PE
PE
СИНХРОНИЗАТОР
PE
Рис. 3.7 Архитектура машины
массового синхронного параллелизма
Вычисление состоит из серии супершагов. На каждом супершаге каждому
обрабатывающему элементу назначается задание, которое состоит из некоторой комбинации локальных вычислительных шагов, широковещательных
92 Модели программирования для больших данных
трансляций сообщений и поступлений сообщений от других обрабатывающих элементов. После каждого периода в L единиц времени выполняется
глобальная проверка, чтобы убедиться, что супершаг был завершен всеми
обрабатывающими элементами. Если он был завершен, то машина переходит
к следующему супершагу. На каждом супершаге выполняются следующие
ниже операции (см. рис. 3.8):
1) конкурентные вычисления: каждый процессор выполняет вычисления
с использованием локальных данных в асинхронном режиме;
2) глобальная коммуникация: процессы обмениваются данными между собой в ответ на запросы, поступающие во время локальной компиляции;
3) барьерная синхронизация: когда процесс достигает барьера, он ожидает,
что все остальные процессы достигли того же барьера.
Супершаг
Конкурентные
вычисления
Супершаг
Глобальная
Барьерная
коммуникация синхронизация
Рис. 3.8 Поток исполнения в модели массового синхронного параллелизма
Коммуникация и синхронизация полностью разъединены, обеспечивая
независимость всех процессов в супершаге. Более того, эта техника позволяет
избегать проблем, вызванных с синхронной передачей сообщений между
процессами (например, мертвых блокировок, или тупиков). Благодаря этим
характеристикам программирование в стиле модели на основе массового
синхронного параллелизма отличается особой надежностью и подходит для
конструирования масштабируемых параллельных систем.
3.4.5.1. Коммуникация
По причине разнообразия одновременных операций в параллельной программе и сложности соединений управлять коммуникациями во многих
системах параллельного программирования довольно обременительно.
С другой стороны, модель на основе массового синхронного параллелизма
рассматривает коммуникационные действия во всей массе, то есть в совокупности. В результате этого появляется возможность накладывать временной
лимит на продолжительность отправки пакета данных. Она также трактует
все коммуникационные действия в супершаге как единое целое и принимает
за основу, что все отдельные сообщения, передаваемые в рамках этого целого,
Модель на основе массового синхронного параллелизма 93
имеют постоянный размер. Пусть h – это максимальное количество входящих или исходящих сообщений на конкретном супершаге, а g – способность
коммуникационной сети передавать данные, тогда время, необходимое процессору для отправки h сообщений размера 1 за супершаг, можно определить
как hg. Величина g называется коэффициентом пропускной способности коммуникационной сети, или разрывом, в смысле неэффективности пропускной
способности. Разумеется, отправка сообщения длины m будет занимать больше времени, чем сообщение единичного размера. С другой стороны, модель
на основе массового синхронного параллелизма не проводит различия между
сообщением длины m и m сообщениями длины 1. В таких обстоятельствах легко логически вывести, что затраты равны mg. На практике оценка параметра
g у каждого параллельного компьютера определяется эмпирически.
3.4.5.2. Синхронизация
Характерная для парадигмы массового синхронного параллелизма коммуникация нуждается в синхронизации посредством барьеров. Хотя барьеры
потенциально обходятся дорого, они устраняют риск возникновения мертвых или живых блокировок1, поскольку не могут порождать круговые зависимости между данными. Однако на стоимость синхронизации барьеров
влияют различные факторы:
стоимость, вызванная разницей во времени завершения разных локальных шагов вычислений. Рассмотрим следующий сценарий: все
процессы, кроме одного, закончили свою работу на определенном супершаге и ожидают последний процесс, которому еще предстоит выполнить много работы. С целью устранения этой проблемы каждому
процессу может назначаться задание, пропорциональное остальным;
стоимость обеспечения глобальной состыкованности между всеми
процессорами. Она в значительной степени зависит не только от коммуникационной сети, но и от наличия специализированного оборудования для синхронизации, а также способа обработки прерываний
процессорами.
3.5.2. Стоимость алгоритма массового
синхронного параллелизма
Все рассмотренные выше параметры, то есть h, g и L, связаны между собой.
Если L равно интервалу времени, необходимому для коммуникации и вы1
Живая блокировка (livelock) – это ситуация, в которой процесс зациклен в ожидании условия, которое никогда не станет истинным. Живая блокировка является
«активно ожидающим» аналогом мертвой блокировки (deadlock или тупика), то
есть ситуации, в которой процесс остановлен из-за того, что первое задание ждет
ресурса, удерживаемого вторым заданием, и при этом удерживает ресурс, который
нужен первому заданию. – Прим. перев.
94 Модели программирования для больших данных
числений супершага, а hg обозначает время, затрачиваемое каждым процессором на отправку h сообщений на супершаге, то необходимо обеспечить
соблюдение соотношения L ≥ hg, в силу чего на значение L устанавливается
нижний предел: периодичность должна быть установлена в таком ключе,
чтобы гарантировать по меньшей мере обмен h сообщениями на супершаге
с учетом времени каждого процессора, то есть hg. Кроме того, существует
зависимость между параметрами g и p (общим числом процессоров): при
увеличении значения p страдает коммуникация, так как будет увеличиваться число параллельных заданий, которым придется общаться друг с другом.
Поэтому очень важно удерживать g как можно ниже, чтобы не увеличивать
время коммуникации и обеспечивать эффективность модели. Суммарная
стоимость супершага s может быть выражена как Ts = ws + hs g + L, где w озна
чает суммарную стоимость вычислений, которые произошли на супершаге. Обратите внимание, что процессоры с производительностью менее w
флопсов вынуждены ждать, и это время ожидания называется временем
простоя. Поэтому с учетом суммарного количества супершагов S суммарная
стоимость алгоритма определяется следующим образом:
где W и H – это соответственно суммарная стоимость вычислений и коммуникации.
3.5.3. Модель на основе массового синхронного
параллелизма с коллективной памятью
Модель на основе массового синхронного параллелизма напрямую не поддерживает ни коллективную память, ни широковещательную трансляцию,
ни комбинирование. Однако эти характеристики могут быть реализованы
путем эмуляции параллельной машины с произвольным доступом (PRAM, от
англ. parallel random access machine) на компьютере с массовым синхронным
параллелизмом. Параллельная машина с произвольным доступом состоит из
бесконечного числа процессоров, каждый из которых подключен к коллективной памяти с бесконечной емкостью. Процессоры могут общаться друг
с другом только через коллективную память, с которой их связывает единица
доступа к памяти (MAU, от англ. memory access unit), а вычисления происходят полностью синхронно. Как показано на рис. 3.9, было представлено
несколько вариантов такой машины.
Тискин (Tiskin, 1998) представил вариант машины массового синхронного
параллелизма (BSM) на основе параллельной машины с произвольным доступом (PRAM), разработанный для облегчения программирования массово
синхронно-параллельных программ (BSP-программ) в стиле коллективной
памяти за счет использования локальной памяти для отдельных процессоров
Модель на основе массового синхронного параллелизма 95
и коллективной глобальной памяти. Формально массовая синхронная PRAM
(BSPRAM) состоит из p процессоров с быстрой локальной памятью и одной
коллективной основной памятью. Вычисления на супершагах выполняются,
как и в модели на основе массового синхронного параллелизма. Супершаг
состоит из трех фаз: вход, локальные вычисления и выход, во время которых процессоры читают данные из основной памяти и записывают их в нее.
Между супершагами процессоры синхронизированы, а вычисления внутри
супершага являются асинхронными.
монопольно
конкурентно
ЧТЕНИЕ
ЗАПИСЬ
монопольно
конкурентно
EREW
ERCW
К ячейке памяти может обращаться
не более один процессор одновременно
В ячейку памяти может писать более
чем один процессор одновременно
CRCW
CREW
Ячейку памяти может читать более
чем один процессор одновременно
Ячейку памяти может читать
и в нее может писать более чем
один процессор одновременно
Рис. 3.9 Классические варианты
параллельной машины с произвольным доступом
На рис. 3.10 показан поток исполнения машины BSPRAM. Аналогично
параллельной машине с произвольным доступом, одновременный доступ
к основной памяти на одном супершаге может быть разрешен либо запрещен. Например, в BSPRAM с монопольным чтением и монопольной записью
(EREW BSPRAM) на каждом супершаге каждую ячейку основной памяти можно читать и в нее можно писать только один раз, а в BSPRAM с одновременным чтением и одновременной записью (CRCW BSPRAM) одновременный
доступ к основной памяти не ограничен.
...
...
...
Вход Вычисления Выход Вход Вычисления Выход
Супершаг
Супершаг
...
Вход Вычисления Выход
Супершаг
Рис. 3.10 Поток исполнения в массовой синхронной параллельной машине
с произвольным доступом (BSPRAM)
96 Модели программирования для больших данных
3.6. SQL-подобная модель
С экспоненциальным ростом объема данных, которые необходимо хранить
в распределенных сетевых средах, проявляются ограничения реляционных
СУБД по масштабируемости, что существенно влияет на эффективность запросов и анализа (Abramova и соавт., 2014). Реляционные СУБД не способны
масштабироваться горизонтально на нескольких компьютерах, что затрудняет хранение и управление огромными объемами данных, ежедневно генерируемых многочисленными приложениями. В последние годы приобрела
популярность архитектура «не только SQL» (NoSQL, от англ. not only SQL), или
нереляционная архитектура СУБД, в качестве альтернативы либо дополнения к реляционным СУБД, с целью достижения горизонтальной масштабируемости базовых операций чтения/записи СУБД, развернутых на нескольких
серверах (Cattell, 2011).
3.6.1. От модели NoSQL к SQL-подобной модели
Системы управления базами данных NoSQL устраняют различные трудности,
связанные с хранением и управлением большими данными, обеспечивая
горизонтальное масштабирование непрерывных операций чтения/записи,
распределенных по нескольким серверам. Вместо модели атомарности, состыкованности, изоляции и живучести (ACID, от англ. atomicity, consistency,
isolation, and durability), используемой в реляционных СУБД, в системах
управления базами данных NoSQL обычно придерживаются модели базовой
доступности, мягкого состояния и окончательной состыкованности (BASE, от
англ. basic availability, soft-state, and eventual consistency), что устраняет требование по состыкованности после каждой транзакции с целью поддержания
одновременной обработки нескольких экземпляров на разных серверах (см.
раздел 2.3.7).
Хотя системы NoSQL позволяют эффективно обрабатывать огромные объемы скоротечных данных, многие приложения, такие как обработка финансовых транзакций или персональных данных (например, медицинской информации), по-прежнему нуждаются в соблюдении принципов ACID с целью
защиты пользователей и обеспечения конфиденциальности. Вследствие этого реляционные СУБД, такие как Oracle, MySQL, Microsoft SQL Server и PostgreSQL, остаются более популярными, чем наиболее популярные варианты
систем NoSQL, такие как MongoDB, Redis и Cassandra. Более того, системы
управления базами данных NoSQL зачастую не подходят для анализа данных.
По этим причинам в последние годы много усилий было потрачено на разработку систем на основе MapReduce с целью обеспечения более эффективного
выполнения запросов к реляционным данным и проведения их анализа.
SQL-подобные системы пытаются совместить эффективность программирования в рамках модели MapReduce и ее способности по выполнению
SQL-подобная модель 97
запросов с простотой SQL-подобного языка, чтобы обеспечить построение
простых и эффективных приложений по анализу данных. На самом деле, несмотря на то что программирование на основе MapReduce позволяет решать
сложности, связанные с масштабируемостью, и сокращать время выполнения запросов, низкоквалифицированные специалисты могут сталкиваться
с трудностями, так как для выполнения простых заданий (например, по вычислению суммы или среднего, выборки или подсчета строк) требуется написание сложных функций map и reduce, что приводит к значительной потере
времени (и денег) предприятий. Для решения этого вопроса было создано
несколько технологических решений (например, Apache Hive), призванных
улучшить способности систем MapReduce по выполнению запросов и упрос
тить разработку базовых приложений по анализу данных с использованием
SQL-подобного языка (см. рис. 3.11).
Узел данных
Клиент
SQL
Узел данных
Hive
MapReduce
Узел данных
Кластер Hadoop
Рис. 3.11 Разработка SQL-запросов
на механизмах обработки больших данных
3.6.2. Зачем использовать язык SQL
на больших данных?
Язык SQL де-факто стал инструментом разработчиков, менеджеров СУБД
и исследователей данных для доступа к данным и манипулирования ими.
Он используется в самых разных коммерческих продуктах и приложениях с целью выполнения запросов, модификации и визуализации данных.
Основные преимущества использования SQL-подобной парадигмы можно
описать следующим ниже образом.
Декларативный язык: SQL – это декларативный язык, то есть он описывает, какие именно части данных подвергаются преобразованиям
и операциям. Пользователи могут легко понимать семантику запросов
на языке SQL при их чтении, точно так же, как они понимают буквальные описания. Запросы на языке SQL часто делят на три типа в зависимости от их использования:
98 Модели программирования для больших данных
– язык определения данных (DDL, от англ. data definition language): для
создания или изменения структуры таблиц и других объектов и ограничений по ним в базе данных, включая создание схем и баз данных;
– язык манипулирования данными (DML, от англ. data manipulation language): для вставки, удаления или обновления данных в структурах/
объектах, существующих в таблицах базы данных;
– язык выполнения запросов к данным (DQL, от англ. data querying language): для извлечения/доставки и работы с данными в базе без их
модификации.
Операционная совместимость: поскольку SQL является стандартизированным языком, каждая система может предоставлять свою собственную его реализацию, сохраняя при этом совместимость между
платформами и фреймворками. Несмотря на разные вариации языка
SQL в платформах обработки больших данных, таких как Hadoop Query
Language (HQL) в Hive и Cassandra Query Language (CQL) в Cassandra,
пользователи по-прежнему способны легко понимать синтаксис, используемый для написания программ.
Ориентированность на данные: все операции и примитивы языка SQL
отражают преобразования и модификации входного набора данных
(таблицы данных в SQL). Вследствие этого язык SQL является одной из
наиболее удобных моделей программирования приложений, ориентированных на данные, как в традиционных СУБД, так и в современных
средах управления большими данными.
По этим причинам SQL-подобные системы нередко применяются с целью
преодоления сложности написания программ MapReduce, даже в случае прос
тых операций (таких как агрегирование, выборка или подсчет строк), при
сохранении скорости запросов и масштабируемости. В этих системах используется MapReduce, чтобы автоматически применять некоторые оптимизации
к полному запросу. Первостепенными областями их применения являются
манипулирование данными, запросы к данным и составление отчетов на
массивных репозиториях. В частности, данные должны быть очищены и обработаны до того, как они будут импортированы в место назначения, подходящее для аналитики, например в распределенную файловую систему или
хранилище данных. Для этих целей используются процессы извлечения, преобразования и загрузки (ETL, от англ. extract, transform, load). SQL-подобная
модель позволяет читать неструктурированные данные, анализировать их
по мере необходимости, а затем закачивать в целевое хранилище систем
поддержки принятия решений с помощью серии SQL-подобных запросов.
Подводя итог, ниже мы выделяем несколько первостепенных целей использования SQL-подобной модели для обработки больших данных. Начиная
с систем управления базами данных SQL, таких как Microsoft SQL Server,
MySQL и PostgreSQL, самой главной трудностью с выполнением запросов
к большим данным является то, что запросные операции невозможно масштабировать без значительных затрат. Следовательно, язык SQL должен
поддерживаться распределенными архитектурами, для того чтобы масшта-
Модель на основе разделенного глобального адресного пространства 99
бировать снизу вверх хранение данных и вычисления на разных машинных кластерах. Еще одна цель разработки программного решения на основе
языка SQL по обработке больших данных заключается в том, чтобы избегать
перемещения данных из хранилища (например, распределенной файловой системы) во внешнее хранилище для аналитики. Механизм SQL может
работать с данными, хранящимися в узле данных, совершая вычисление
с меньшими затратами. Кроме того, поскольку доступ к данным в кластере
хранения осуществляется немедленно, сочетание SQL и больших данных
приводит к мгновенной доступности вносимых данных. Такая техника называется «запросами прямо на месте» (query-in-place), и к ее преимуществам
относится низкая задержка импровизированных SQL-запросов к массивным
наборам данных. Поскольку нет необходимости поддерживать отдельную
аналитическую базу данных, сокращается перемещение данных из одной
системы в другую. Все эти факторы приводят к снижению операционных
издержек и повышению совокупной эффективности обработки данных.
3.6.3. Разбиение данных на разделы
Важнейшим аспектом использования инструкций SQL для выполнения запросов к большим данным является разбиение данных на разделы. В результате разбиения данные в таблице делятся на один или несколько столбцов
на основе значений столбцов разбиения, создавая отдельные файлы и/или
каталоги. Простой SQL-запрос в общем случае читает всю таблицу, что делает операции времязатратными. В противоположность этому при выполнении запроса к таблице с использованием стратегии разбиения сканируются
только необходимые разделы, что снижает затраты на ввод-вывод, позволяя
избегать чтения данных, которые заведомо не удовлетворяют запросу. Поскольку данные разбиты по каталогам, запрос быстрее обрабатывает разбитую часть данных, чем полное сканирование. Однако наличие слишком
большого количества разделов приводит к появлению огромного количества
файлов и каталогов в распределенной файловой системе, что дорого обходится узлу-мастеру, так как он должен поддерживать все метаданные в памяти.
3.7. Модель на основе разделенного
глобального адресного пространства
Парадигма разделенного глобального адресного пространства (PGAS, от англ.
partitioned global address space) (De Wael и соавт., 2015) – это модель параллельного программирования, которая пытается повышать продуктивность
программистов и при этом добиваться высокой производительности. Осно-
100 Модели программирования для больших данных
вополагающая концепция данной модели заключается в том, что хотя глобально коллективное адресное пространство повышает продуктивность, для
повышения производительности и поддержки масштабируемости на крупных параллельных архитектурах необходимо разбивать локальные и дистанционные доступы к данным. Для этой цели указанная модель поддерживает
глобальное адресное пространство. Программа на основе данной модели
состоит из нескольких процессов, выполняющих одинаковый исходный код
на разных узлах в одно и то же время. Каждый процесс имеет ранг, который
представляет собой индекс узла, на котором он выполняется. Каждый процесс имеет доступ к глобальному адресному пространству, которое разбито
на локальные адресные пространства. Локальные адреса доступны напрямую, тогда как дистанционные адреса, принадлежащие отличающимся процессам, доступны через вызовы API. Пример модели на основе разделенного
глобального адресного пространства показан на рис. 3.12.
Память
Память
Память
Память
Память
...
Коллективная
Частная
Ядро
CPU
Ядро
CPU
Процесс
Процесс
Узел
...
...
Память
...
...
Ядро
CPU
Ядро
CPU
Ядро
CPU
Процесс
Процесс
Процесс
...
...
Ядро
CPU
Процесс
Узел
Рис. 3.12 Пример модели на основе разделенного
глобального адресного пространства (PGAS)
Языки, основанные на парадигме разделенного глобального адресного
пространства, трактуют адресное пространство как глобальную среду. Это
означает, что вычислительный поток или процесс получает указатель на
данные, которые могут находиться в любой точке системы, а также может
читать или записывать дистанционные данные, локальные для других потоков. Каждый язык парадигмы различает две области памяти в адресном
пространстве: коллективную память, которая содержит данные, доступные
всем вычислительным потокам, и частную память, которая содержит данные,
доступные только тому потоку, которому эта область принадлежит. У каждого вычислительного потока есть своя часть частного пространства, а также
участок коллективного пространства.
Свойства языков данной парадигмы помещаются посередине между моделями на основе передачи сообщений и коллективной памяти. По сути
дела, они сочетают в себе локальность данных (разбиение), характерную
для передачи сообщений, с программируемостью и простотой обращения
к данным, характерными для модели на основе коллективной памяти (глобального адресного пространства).
Модель на основе разделенного глобального адресного пространства 101
3.7.1. Параллелизм в модели на основе
разделенного глобального адресного пространства
Отталкиваясь от модели на основе разделенного глобального адресного пространства, принято выделять три главенствующие модели параллельного
исполнения:
одна программа, несколько элементов данных (SPMD, от англ. Single program multiple data): при запуске программы порождается фиксированное число вычислительных потоков, каждый из которых выполняет
одну и ту же программу;
асинхронная модель разделенного глобального адресного пространства
(APGAS): при запуске программы один вычислительный поток начинает исполнение с точки входа в программу. Предусмотрены конструкции
для динамического порождения новых потоков, выполняющихся в тех
же или дистанционных разделах адресного пространства, где каждый
порожденный поток может исполнять свой исходный код;
неявный параллелизм: исходный код не содержит ни видимого параллелизма, ни видимых директив управления параллелизмом, из чего
следует, что программа описывает один вычислительный поток. В целях ускорения вычислений во время исполнения может порождаться
несколько потоков, но такой параллелизм неявно присутствует в исходном коде программы.
3.7.2. Память и функция стоимости
В модели на основе разделенного глобального адресного пространства пространство памяти делится на места. Место – это вычислительный узел, с которым ассоциирован конкретный вычислительный процесс или поток. Ассоциированный с местом поток может обращаться к ячейкам памяти этого
места с низкой и равномерной стоимостью. Все остальные ячейки памяти,
ассоциированные с другими местами и, следовательно, с другими потоками,
являются дистанционными, и доступ к ним обходится дороже. Фактическая
стоимость варьируется в зависимости от базисного аппаратного обеспечения. Взаимосвязь между разными местами задается на основе топологии
межсоединений, которая связывает их вместе. Наиболее распространена
топология, каждое место в которой идентифицируется числовым индексом
в диапазоне [0, n], где n – это суммарное количество мест. Еще одной распространенной топологией является иерархическое дерево.
Наличие модели на основе коллективной памяти с неравномерным доступом к памяти (NUMA, от англ. non-uniform memory access), заложенной в модель на основе разделенного глобального адресного пространства, подчеркивается функцией стоимости, которая характеризует обращения к памяти.
В языке модели на основе разделенного глобального адресного пространства
102 Модели программирования для больших данных
обычно задействуется двухуровневая стоимость: низкая и высокая. Стоимость местоположений памяти, расположенных близко к источнику запроса доступа, является низкой, а стоимость дистанционных местоположений
памяти – высокой. Вследствие этого в вычислении стоимости участвуют два
элемента: место, из которого исходит запрос на доступ, и место назначения
запроса, где расположены интересующие данные.
3.7.3. Распределение данных по местам
Характер распределения данных по местам может использоваться для классифицирования языков модели на основе разделенного глобального адресного
пространства. Когда программисты могут указывать распределение данных,
то можно говорить о явной модели; в противном случае речь идет о неявной
модели. Можно выделить три распространенные модели распределения:
циклическая, при которой данные разбиваются на поочередно расположенные куски, циклически размещаемые по местам;
блочная, при которой данные разбиваются на одинаковые по размеру
и поочередно расположенные куски, которые размещаются по разным
местам;
блочно-циклическая, при которой данные разбиваются на куски параметрического размера, которые последовательно размещаются в разных местах циклическим образом.
При работе с многомерными данными (например, матрицами) эти три
модели распределения используются по-разному. Например, модель распределения может применяться по одной на каждое измерение. Еще одна
стратегия заключается в использовании распределения для каждого измерения, игнорируя одно из них (например, столбцовое измерение). В противном
случае модель распределения применяется к разглаженным данным.
3.8. Модели для систем
экзафлопсного масштаба
Внедрение систем экзафлопсного масштаба открывает широкие возможности, однако их конструирование и реализация довольно сложны ввиду ряда
проблем, как обсуждалось в разделе 2.7.1. Масштабируемость, сетевая задержка, надежность, воспроизводимость и устойчивость процедур и операций, доступных разработчикам для передачи данных и их управления, – вот
главные проблемы при разработке приложений для систем экзафлопсного
масштаба. Обработка очень крупных объемов данных действительно нуждается в операционных системах и новых алгоритмах, способных масшта-
Модели для систем экзафлопсного масштаба 103
бироваться при загрузке, хранении и обработке огромных объемов данных,
которые, как правило, должны разбиваться на очень малые зерна данных
и анализироваться с помощью тысяч или даже миллионов простых параллельных операций.
3.8.1. Роль моделей программирования
в системах экзафлопсного масштаба
В нескольких отчетах разных организаций было выявлено множество препятствий на пути к вычислениям экзафлопсного масштаба. Эти трудности
включают в себя проблемы с производительностью, масштабируемостью
и продуктивностью, в дополнение к более современным проблемам с энергоэффективностью и надежностью. Недавние архитектуры высокопроизводительных вычислений демонстрируют, как передовые технологии, такие
как графические процессоры, архитектуры «система на чипе» и энергонезависимая память, могут обеспечивать инновационные решения некоторых из
этих проблем. Несмотря на то что раннее использование этих систем продемонстрировало преимущества в производительности и энергопотреблении,
остаются проблемы переносимости и стабилизации производительности.
Все эти проблемы, вместе взятые, мешают научному сообществу принять эти
инновационные решения на вооружение.
Суперкомпьютеры стали необходимым инструментом в широком спект
ре научных областей, включая квантовую физику, прогнозирование погоды,
исследование климата, молекулярное моделирование и физические симуляции. В целях обеспечения будущих научных открытий необходимо разработать эффективные параллельные приложения, способные удовлетворять
потребности в обработке данных. Современные системы высокопроизводительных вычислений состоят из сотен тысяч вычислительных узлов, рас
тущие размеры которых не позволяют программистам получать полное
представление о системе. Ввиду этого программируемость такой системы
существенно влияет на ее суммарную производительность; следовательно,
модели программирования должны обеспечивать высокий уровень масштабируемости, чтобы способствовать созданию экзафлопсных суперкомпьютеров следующего поколения.
Программисты также должны иметь абстракцию, которая позволит им
управлять сотнями миллионов и миллиардами конкурентных вычислительных потоков, наделив их способностью структурировать программы на понятные части, что очень важно для ясности, технического сопровождения
и масштабируемости системы. Кроме того, абстракция позволяет расширять
возможности программирования, создавая новые языки параллельного программирования поверх существующих или даже полностью с нуля. Это превращает абстракцию в важную часть большинства параллельных парадигм
и сред исполнения.
104 Модели программирования для больших данных
3.8.2. Требования к моделям экзафлопсного
масштаба
Компромисс между коллективизацией данных среди вычислительных компонентов и локальными вычислениями – один из наиболее важных аспектов,
который необходимо учитывать в приложениях, работающих на системах
экзафлопсного масштаба и анализирующих большие данные. Масштабируемая модель программирования для систем экзафлопсного масштаба должна
включать как минимум следующие ниже механизмы (Talia, 2019):
параллельный доступ к данным, который увеличивает пропускную
способность доступа к данным за счет разбиения данных на несколько
кусков и одновременного доступа к разным элементам данных;
отказоустойчивость с целью решения проблемы, связанной с возможностью отказа одной из сторон коммуникации во время нелокальной
коммуникации;
локальная коммуникация на основе данных с целью сокращения обмена данными;
обработка данных на ограниченных группах ядер с целью концентрации вычислений на локальностях экзафлопсных машин;
синхронизация близких данных с целью снижения накладных расходов, возникающих при синхронизации между многочисленными дистанционными ядрами;
резидентная аналитика, сокращающая время реакции за счет кеширования данных в оперативной памяти вычислительных узлов;
отбор данных на основе локальности с целью уменьшения задержки
за счет сохранения подмножества необходимых данных в локальном
доступе.
3.8.3. Ограничения существующих
моделей программирования
Достижение экзафлопсного масштаба по количеству вычислительных узлов
нуждается в переходе от текущего управления тысячами потоков к управлению миллиардами потоков, а также адаптации существующих моделей с целью борьбы с возрастающим уровнем отказов. По мнению Гроппа и Снира
(Gropp и Snir, 2013), пятью наиболее существенными свойствами моделей
программирования, на которые влияет переход к экзафлопсному масштабу,
являются планирование потоков управления, коммуникация, синхронизация, распределение данных и представления управления. В следующем далее
разделе обсуждаются ограничения главных моделей программирования для
вычислений экзафлопсного масштаба (Da Costa и соавт., 2015).
Модели для систем экзафлопсного масштаба 105
3.8.3.1. Ограничения модели программирования
на основе передачи сообщений
В настоящее время целью систем экзафлопсного масштаба является использование параллелизма с распределенной памятью, и, следовательно, архитектура передачи сообщений, вероятно, будет внедрена частично. Самая
популярная реализация этой модели – фреймворк MPI на основе интерфейса
передачи сообщений – доказала, что в определенных ситуациях она может
работать с миллионами ядер. Однако указанная реализация построена на
стандартных языках последовательного программирования и дополнена
низкоуровневыми методами передачи сообщений, что требует от пользователей решения всех аспектов распараллеливания, начиная от распределения
данных и работы и заканчивая ядрами, коммуникацией и синхронизацией.
Фреймворк MPI предназначен в первую очередь для статического распределения данных и, следовательно, не подходит для работы с динамическим
распределением нагрузки. Кроме того, было продемонстрировано, что используемые в моделях на основе передачи сообщений схемы коммуникации
«многие ко многим» не масштабируются, так как наиболее часто применяе
мые реализации свободно предполагают полносвязную сеть с плотными
коммуникационными схемами. Другие ограничения связаны, в частности,
с коллективным доступом к запросам ввода-вывода и разбиением данных.
Ввод-вывод, вне всяких сомнений, является самым узким местом в системах
на базе MPI, что указывает на необходимость пересмотра текущей модели.
3.8.3.2. Ограничения моделей программирования
на основе коллективной памяти
Ожидается, что системы экзафлопсного масштаба будут поддерживать сотни
ядер на одном CPU или GPU. В случае параллельных систем среднего размера
использование систем с коллективной памятью является жизнеспособной
альтернативой передаче сообщений, поскольку перекладывает усилия по
распараллеливанию с программиста на компилятор. Наиболее популярные
системы с коллективной памятью используют модель управления паралеллизмом, которая не позволяет управлять распределением данных и задействует немасштабируемые механизмы синхронизации, такие как блокировки
(замки) или атомарные секции. Более того, глобальное представление данных поощряет совместную синхронизацию дистанционных доступов к данным со стороны всех вычислительных потоков, сопоставимую с локальными
доступами, что приводит к неэффективному программированию.
3.8.3.3. Ограничения разнородного программирования
Благодаря преимуществам пиковой производительности и энергоэффективности в высокопроизводительных вычислениях все чаще используются клас
теры разнородных узлов, состоящие из мультиядерных CPU и GPU. Разработ-
106 Модели программирования для больших данных
чики приложений часто используют комбинацию парадигм параллельного
программирования, чтобы полностью воспользоваться вычислительными
способностями таких платформ. Однако разнородные вычисления создают
новую проблему, связанную с разнообразием сред исполнения и моделей
программирования. Конструкция одноузлового оборудования становится
все более разнородной. Подобно этому, многие крупнейшие современные
системы высокопроизводительных вычислений представляют собой клас
теры с самыми разными архитектурами вычислительных устройств. Перенос многоузловых приложений на кластеры с разнородными архитектурами
вычислительных устройств затруднен, а для передачи данных между узлами
приходится использовать коммуникационный слой. Поскольку написание
программ на таких платформах сопряжено с ошибками, для преодоления
этих трудностей необходимы новые абстракции, модели программирования
и инструменты.
3.8.4. Модели программирования
для систем экзафлопсного масштаба
Для того чтобы справиться с ограничениями существующих моделей программирования в системах экзафлопсного масштаба, недавно были предложены новые модели программирования. Эти модели эксплуатируют или
расширяют существующие, принимая во внимание специфические характеристики сред экзафлопсного масштаба.
Среди них Legion (Bauer и соавт., 2012), модель программирования на
основе распределенной памяти, разработанная для обеспечения высокой
производительности на современных параллельных архитектурах с самыми
разными процессорами и глубокими иерархиями памяти. Она построена
на использовании логических областей, с помощью которых определяется
организация данных и обеспечиваются явные взаимосвязи для рассуждений о локальности и независимости. Логическая область идентифицирует
группу объектов и может динамически распределяться, удаляться и храниться в структуре данных. Области также могут подаваться на вход отдельных
функций, именуемых заданиями, которые читают данные в конкретных областях и предоставляют информацию о локальности. Логические области могут
делиться на разрозненные или непересекающиеся подобласти, предоставляя
информацию для определения независимости вычислений.
Charm++ (Kale и Krishnan, 1993) – еще одна модель программирования
на основе распределенной памяти, в которой программа задает коллекции
взаимодействующих объектов, динамически отображаемых в процессоры
системой исполнения. В Charm++ реализована асинхронная модель, управляемая сообщениями и заданиями, с подвижными объектами и адаптивной
параллельной системой исполнения, которая управляет исполнением. Она
руководит взаимным наложением коммуникаций, балансом нагрузки, от-
Модели для систем экзафлопсного масштаба 107
казоустойчивостью, контрольными точками с целью раздельного исполнения и управления питанием. Чрезмерная декомпозиция в рамках модели
Charm++ позволяет программисту делить приложение на множество мелких
объектов, каждый из которых представляет собой грубую работу и/или единицу данных. Количество таких объектов может значительно превышать
количество процессоров. Более того, объекты можно перемещать между процессорами. Благодаря этому операции могут отправлять данные не физическим процессорам, а логическим объектам.
DCEx (Talia и соавт., 2019) – это модель программирования на основе парадигмы разделенного глобального адресного пространства (PGAS), служащая
для реализации ориентированных на данные крупномасштабных параллельных приложений на вычислительных платформах экзафлопсного масштаба.
Она построена на ориентирующихся в данных базовых операциях для приложений, интенсивных по привлечению данных, и позволяет масштабировать
использование огромного количества вычислительных элементов. В модели
DCEx задействуются частные структуры данных и ограничивается объем
данных, которыми обмениваются конкурентные вычислительные потоки.
В потоках используется синхронизация вблизи данных на основе вышена
званной парадигмы, чтобы работать рядом с данными. В основе DCEx лежит
ключевая идея, которая заключается в структурировании программ в параллельные блоки данных, представляющие собой единицы иерархии памяти/
хранилища для параллельных вычислений, коммуникаций и миграции на
основе коллективной и распределенной памяти.
X10 (Charles и соавт., 2005) – это асинхронная модель программирования
на основе парадигмы разделенного глобального адресного пространства
(APGAS), которая вводит местоположения как абстракцию вычислительного
контекста с локально синхронным представлением коллективной памяти.
Вычисления в X10 распределяются между большим количеством мест, каждое из которых хранит некоторые данные и выполняет одно или несколько
действий (то есть легких потоков исполнения), которые могут создаваться
динамически. Деятельность может синхронно использовать одну или несколько областей памяти в том месте, где она находится.
Chapel (Deitz и соавт., 2006) – это также асинхронная модель программирования на основе разделенного глобального адресного пространства (APGAS),
использующая абстракции языка высокого уровня с целью облегчить общее
параллельное программирование. Chapel предлагает модель программирования с применением глобальных представлений (то есть структуры данных
с глобальным представлением и глобальное представление управления), которая повышает уровень абстракции, используемый для определения перетекания данных и управления. Структуры данных с глобальным представлением – это массивы и другие агрегаты данных, размеры и индексы которых
представлены глобально, даже если их реализация может их распределять
по локалям параллельной системы. Локаль – это абстракция единицы унифицированного доступа к памяти в целевой архитектуре: она означает, что
все потоки в пределах локали имеют одинаковое время доступа к любому
108 Модели программирования для больших данных
адресу памяти. Глобальное представление управления означает, что пользовательское приложение начинается с одного логического потока управления, а затем встраивает параллелизм с помощью определенных языковых
концепций.
UPC++ (Zheng и соавт., 2014) – это библиотека на языке C++, которая предоставляет классы и методы, поддерживающие программирование на основе
разделенного глобального адресного пространства (PGAS). UPC++ предлагает
инструменты описания зависимостей между асинхронными вычислениями
и передачей данных, а также для эффективной односторонней коммуникации. Она также позволяет переносить вычисления на данные посредством
вызовов дистанционных процедур (RPC)1, что дает возможность эффективно
реализовывать сложные распределенные структуры данных. Среди главных
характеристик библиотеки UPC++ выделяются интерфейсы, которые по своей конструкции соответствуют стандартному языку C++. Глобальные указатели, асинхронное программирование на основе вызовов дистанционных
процедур и фьючерсы – три первостепенные концепции программирования в UPC++. Глобальные указатели позволяют эффективно использовать
локальность данных, а фьючерсы поддерживают разработку асинхронных
программ, управляя доступностью данных, полученных в результате вычислений.
В качестве заключительной ремарки, алгоритмы, в которых часто используется коммуникация «все ко всем», не очень хорошо масштабируются в средах экзафлопсного масштаба. По этой причине были предложены
гибридные системы, чтобы справляться с проблемами масштабируемости
путем применения фреймворка MPI для межузлового параллелизма и модели программирования с коллективной памятью для внутриузлового параллелизма. Например, системы MPI + X (например, MPI + OpenMP) позволяют
использовать соседние коллективы, чтобы обеспечивать масштабируемые
коммуникационные схемы «все к некоторым», которые ограничивают перенос данных только конкретным областям процессора.
1
Вызов дистанционных процедур (RPC, от англ. remote procedure call) – это программный протокол связи, с помощью которого одна программа запрашивает
услугу у другой программы, находящейся на другом компьютере и в другой сети,
без необходимости разбираться в деталях сети. – Прим. перев.
Глава
4
Инструменты обработки
больших данных
В этой главе описаны языки программирования, библиотеки и инструменты,
используемые для разработки масштабируемых приложений по обработке
больших данных. Описаны характеристики и механизмы программирования
таких фреймворков, как Hadoop, Spark и Storm. По каждому инструменту
программирования приведено несколько реально-практических примеров
приложений по работе с большими данными.
4.1. Главные характеристики
Системы программирования для проведения анализа больших данных отличаются несколькими характеристиками, которые могут использоваться для
классификационных целей. Наиболее важными характеристиками являются
уровень абстракции и тип параллелизма.
Под уровнем абстракции системы понимаются ее программные способности по сокрытию низкоуровневых деталей решения (например, функции, структуры данных или коммуникационного протокола). Низкий уровень абстракции позволяет программистам использовать низкоуровневые
API, механизмы и инструкции, которые характеризуются мощностью, но
нетривиальностью в использовании. Средний уровень абстракции позволяет
программистам определять приложения с помощью ограниченного набора
программных конструкций, скрывая низкоуровневые детали, которые не
являются основополагающими для разработки приложений. Высокий уровень абстракции позволяет разработчикам создавать приложения, используя
высокоуровневые интерфейсы, такие как визуальные IDE или абстрактные
модели, с высокоуровневыми конструкциями, не связанными с работающей
архитектурой.
110 Инструменты обработки больших данных
Тип параллелизма описывает способ, которым система выражает параллельные операции, и способ, которым ее среда исполнения поддерживает
исполнение конкурентных операций на нескольких узлах или процессорах.
При этом можно выделить два главных типа параллелизма: параллелизм
данных, при котором одинаковый исходный код исполняется параллельно
на разных элементах данных, и параллелизм заданий, при котором разные
задания, составляющие приложение, исполняются параллельно.
В главе 5 мы более подробно рассмотрим вышеперечисленные характерис
тики и другие аспекты, которые могут использоваться для классифицирования и сравнения систем.
4.2. Инструменты программирования
на основе модели MapReduce
Наиболее распространенной площадкой с открытым исходным кодом, в которой реализована модель программирования MapReduce, является фреймворк Apache Hadoop. Это общецелевой фреймворк, предназначенный для
управления очень крупными объемами данных и их обработки в параллельных и распределенных системах, в которых две базовые операции модели
MapReduce исполняются конкурентно. Фреймворк Hadoop позволяет разрабатывать интенсивные по привлечению данных масштабируемые приложения, используя разные языки программирования. Используемый в Hadoop
подход к программированию позволяет разработчикам абстрагироваться от
классических вопросов распределенных вычислений, таких как локальность
данных, балансировка рабочей нагрузки, отказоустойчивость и экономия
пропускной способности сети.
За последние годы было разработано и реализовано несколько небольших
реализаций модели MapReduce, таких как Phoenix++ и Sailfish, однако ни
одна из них не достигла такого успеха, как Hadoop. Инструмент Phoenix++
основан на C++ и задействует мультиядерные чипы и мультипроцессоры
с коллективной памятью. Среда исполнения Phoenix++ автоматически берет
на себя создание вычислительных потоков, разбиение данных на разделы,
динамическое планирование заданий и обеспечение отказоустойчивости.
Sailfish – это фреймворк MapReduce для обработки крупных объемов данных,
задействующий пакетное вещание от преобразователей к редукторам с целью повышения производительности приложений. В Sailfish используется
абстракция с поддержкой агрегации данных, именуемая I-файлами, которая
адаптирует изначальную модель, чтобы эффективно пакетировать данные,
записываемые на несколько узлов и читаемые с них.
В следующем далее разделе мы познакомимся с фреймворком Apache Hadoop, представим его программную архитектуру и обсудим поток параллельного исполнения на примерах программирования.
Инструменты программирования на основе модели MapReduce 111
4.2.1. Apache Hadoop
Фреймворк Apache Hadoop1 широко используется для разработки пакетных
приложений, и за годы своего существования он был принят на вооружение
большинством ведущих компаний сферы ИТ, таких как Yahoo!, IBM и Amazon.
Например, Yahoo! использовала его для разработки систем рекламирования,
веб-поиска и масштабирования тестов. Однако он подходит только для пакетной обработки, что приводит к неэффективности в высокоитеративных
приложениях, которые многократно исполняют операции на одном и том
же наборе данных. Это связано с использованием дисковой обработки данных в распределенной файловой системе при вычислении промежуточных
результатов с помощью модели MapReduce (Verma и соавт., 2016). Тем не
менее указанный проект поддерживается большим сообществом пользователей, а его распространенность объясняется широкой поддержкой разных
языков программирования, постоянными обновлениями и исправлениями
ошибок со стороны многочисленного сообщества разработчиков открытого
исходного кода.
Hadoop обеспечивает низкий уровень абстракции, потому что программисты могут определять приложение, используя API-интерфейсы, которые хотя
и являются мощными, не очень-то просты в использовании. Такие API, по
сути дела, построены на вычислительной инфраструктуре и требуют от программиста низкоуровневого понимания системы и среды исполнения, чтобы решать вопросы, связанные с распределенными файловыми системами,
сетевыми компьютерами и распределенным программированием (Wadkar
и соавт., 2014). Для разработки приложения с использованием фреймворка
Hadoop требуется большое количество строк исходного кода и много усилий
разработчиков, если сравнивать с системами, обеспечивающими более высокий уровень абстракции (например, Airflow, Pig или Hive); однако исходный
код, как правило, более эффективен, так как может полностью перенастраи
ваться.
Hadoop предназначен для использования параллелизма данных в фазах преобразования/редукции. Входные данные, по сути дела, разбиваются на куски
и параллельно обрабатываются разными машинами. Куски данных реплицируются на разных узлах, что обеспечивает высокую отказоустойчивость,
а также контрольные точки и восстановление. Однако стратегия разбиения
не гарантирует эффективности при необходимости доступа к крупному количеству малых файлов. Помимо модели программирования MapReduce,
проект Hadoop включает в себя множество других модулей, а именно:
распределенную файловую систему Hadoop (HDFS, от англ. Hadoop Distributed File System), обеспечивающую отказоустойчивость с автоматическим восстановлением, переносимость на разнородное и недорогое
товарное аппаратное обеспечение и операционные системы, высокопроизводительный доступ и надежность данных;
1
См. https://hadoop.apache.org/.
112 Инструменты обработки больших данных
фреймворк для управления ресурсами кластера и планирования вычислительных заявок под названием YARN (то есть «еще один переговорщик по ресурсам», от англ. Yet Another Resource Negotiator);
общецелевой компонент Hadoop Common, включающий в себя служебные средства и библиотеки, которые дополняют другие модули фреймворка Hadoop.
В частности, благодаря внедрению YARN в 2013 году фреймворк Hadoop
превратился из решения по пакетной обработке данных в опорную платформу для нескольких других систем программирования, таких как Storm1
для потокового анализа данных; Hive2 для выполнения запросов к крупным
наборам данных; Giraph3 для итеративной графовой обработки; HBase4 для
произвольного и реально-временного доступа к данным в нереляционной
модели; Oozie5 для управления вычислительными заявками Hadoop; Ambari6
для резервирования, управления и мониторинга кластеров Hadoop; ZooKeeper7 для хранения конфигурационной информации, именования и предоставления распределенных синхронизационных и групповых услуг. На рис. 4.1
представлен общий вид программного стека фреймворка Hadoop.
Ambari
Резервирование, управление и мониторинг кластеров Hadoop
Storm, Flink
(Потоковая
обработка)
Giraph, Hama
(Графы)
Pig, Hive
(Запросы)
Другие
библиотеки
Hadoop
Hadoop MapReduce
Фреймворк распределенной пакетной обработки
YARN
Управление ресурсами кластеров
HDFS
Распределенная файловая система Hadoop
Рис. 4.1 Программный стек фреймворка Hadoop
4.2.1.1. Распределенная файловая система Hadoop
Распределенная файловая система Hadoop (HDFS) была разработана для
хранения крупных объемов данных, обеспечивая при этом быстрое чтение
1
2
3
4
5
6
7
См. https://storm.apache.org/.
См. https://hive.apache.org/.
См. https://giraph.apache.org/.
См. https://hbase.apache.org/.
См. https://oozie.apache.org/.
См. https://ambari.apache.org/.
См. https://zookeeper.apache.org/.
Инструменты программирования на основе модели MapReduce 113
и отказоустойчивость. Для этих целей файлы в системе HDFS распределяются и реплицируются на разных узлах хранения, что облегчает исполнение
параллельных и распределенных приложений. Она поддерживает иерархическую организацию файлов, аналогичную традиционным файловым
системам, и позволяет создавать, переименовывать и удалять файлы, перемещать их из одного каталога в другой и т. д. Как показано на рис. 4.2, архитектура кластера HDFS основана на парадигме мастер–работник и включает в себя два типа узлов: именной узел (мастер) и несколько узлов данных
(работников).
Именной узел (namenode) отвечает за управление распределенной файловой системой, поддерживает дерево файловой системы и хранит имена и метаданные файлов и каталогов. С другой стороны, узлы данных (namenodes)
хранят и извлекают блоки данных, периодически сообщая именному узлу
список блоков данных, которые они хранят. Хотя узлы данных и хранят данные, стоит отметить, что именной узел является фундаментальным компонентом системы, без которого файловая система не может использоваться.
Для придания именному узлу отказоустойчивости архитектура также включает вторичный именной узел, который не является репликой или резервной
копией именного узла, а лишь хранит состояние файловой системы на случай
ошибок именного узла.
Именной узел
•
•
•
•
Имя файла и метаданные
Разрешения файла
Управление блоками
Управление репликами
Вторичный
именной узел
Блок данных
Блок данных
Блок данных
Блок данных
Блок данных
Блок данных
Блок данных
Блок данных
Узел данных
Узел данных
Узел данных
Рис. 4.2 Архитектура
распределенной файловой системы Hadoop (HDFS)
Система HDFS предназначена для хранения файлов в виде последовательности блоков данных, представляющих собой минимальный объем данных, который можно читать или писать. Дефолтный размер блока составляет 128 Мб, но может быть сконфигурирован на пофайловой основе. Более
того, с целью обеспечения отказоустойчивости каждый блок реплицируется
между узлами данных с определенным коэффициентом репликации, который может быть задан во время создания файла и впоследствии может быть
114 Инструменты обработки больших данных
изменен. В целях минимизации стоимости поиска блоки HDFS имеют более
крупный размер, чем блоки, используемые в традиционных файловых системах. В целях повышения эффективности в Hadoop задействуется концепция
локальности данных, чтобы распределять вычислительные заявки между
работниками. Базовый принцип заключается в радикальном повышении
эффективности вычислений за счет их выполнения вблизи обрабатываемых
данных. Вследствие этого обработка не нуждается в переносе крупных объемов данных по сети, что минимизирует перегруженность сети и увеличивает совокупную пропускную способность системы.
4.2.1.2. Поток исполнения во фреймворке Hadoop
Данные в модели программирования MapReduce обрабатываются параллельно путем разбиения совокупной работы на множество независимых заданий.
Хотя парадигма MapReduce состоит из двух главных фаз, преобразования
и редукции, поток обработки данных в Hadoop состоит из отличающихся
фаз, в которых используются и другие компоненты, помимо преобразователя и редуктора, которые могут быть сконфигурированы программистами. В частности, программирование приложения Hadoop предусматривает
указания следующих ниже компонентов, которые используются в потоке
исполнения, как показано на рис. 4.3.
Перетасовка
Порция
Входные
данных
Порция
Преобра
зование
Преобра
зование
Сортировка + отправка
Объединение
Ключ/значение
Ключ/значение
Ключ/значение
Ключ/значение
Ключ/значение
Редукция
Ключ/значение
Ключ/значение
Редукция
Ключ/значение
Ключ/значение
Ключ/значение
Порция
Преобра
зование
Ключ/значение
Ключ/значение
Ключ/значение
Ключ/значение
Рис. 4.3 Поток исполнения во фреймворке Hadoop
Входные данные: представляют собой входные файлы для вычислительной заявки MapReduce, которые обычно хранятся в системе HDFS. Такие файлы имеют произвольный формат (например, обычный текст,
JSON, ZIP, двоичный, CSV).
Входной формат (InputFormat): задает способ разбивки и чтения входных данных для создания входных порций данных.
Инструменты программирования на основе модели MapReduce 115
Входная порция данных (InputSplit): это часть данных, которая будет обрабатываться отдельным экземпляром преобразователя. В частности,
для каждой входной порции создается преобразовательное задание,
поэтому суммарное количество таких заданий будет равно количеству
сгенерированных входных порций. Порция делится на записи, которые
будут обрабатываться преобразователем.
Читающий компонент (RecordReader): преобразовывает входную порцию в пары ключ–значение, пригодные для чтения и обработки преобразователем, в соответствии со сконфигурированной входной порцией.
Например, используя дефолтный формат входных данных (TextInputFormat), он создает пары, в которых ключом является номер строки во
входном файле (назначается с помощью байтового смещения), а значением – текст строки.
Преобразователь (Mapper): реализует первую фазу обработки данных,
применяя к каждой паре ключ–значение функцию преобразования
(или отображения входа в выход), которая выдает на выходе список
пар ключ–значение.
Комбинатор (Combiner): этот компонент, часто именуемый мини-редуктором, выполняет локальное агрегирование данных, получаемых на выходе
из преобразователя. В частности, он помогает минимизировать перенос
промежуточных данных между преобразователями и редукторами. Данные на выходе из комбинатора затем передаются в разделитель.
Разделитель (Partitioner): получает данные на выходе из комбинатора
и выполняет разбиение с помощью функции хеширования. В частности, входящие данные разбиваются по ключам таким образом, чтобы
кортежи с одинаковыми ключами попадали в один и тот же раздел.
Затем каждый раздел отправляется одному редуктору.
Перетасовка и сортировка: каждый сгенерированный разделителем
раздел передается по сети в узлы редукции (фазу перетасовки). Однако
перед отправкой сгенерированных разделителем данных в назначенные узлы-редукторы эти данные сортируются по ключу (данный процесс
выполняется фреймворком Hadoop MapReduce). Затем редуктор просто
объединяет полученные отсортированные сегменты. Стоит отметить,
что сортировка в Hadoop облегчает редуктору работу по определению
момента начала нового задания. Такой подход позволяет экономить
время, поскольку редуктор начинает новое задание, когда очередной
ключ в отсортированных данных отличается от предыдущего.
Редуктор (Reducer): это вторая фаза обработки данных, в которой происходит окончательное агрегирование путем применения функции
редукции к данным.
Записывающий компонент (RecordWriter): записывает выходные пары
ключ–значение, полученные из фазы редукции, в выходные файлы.
В частности, еще один компонент, именуемый выходным форматом
(OutputFormat), задает способ записи выходных пар ключ–значение
в выходные файлы указанным компонентом.
116 Инструменты обработки больших данных
4.2.1.3. Основы
Типичная программа MapReduce состоит как минимум из трех частей:
преобразователя, который расширяет класс Mapper, обеспечивая конкретно-прикладную реализацию метода map;
редуктора, который расширяет класс Reducer, обеспечивая конкретноприкладную реализацию метода reduce;
драйвера, который определяет вычислительную заявку MapReduce и содержит главную часть программы.
Класс Mapper определен следующим образом:
class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
где KEYIN, VALUEIN, KEYOUT и VALUEOUT – это соответственно типы входного ключа,
входного значения, выходного ключа и выходного значения.
Указанный класс содержит следующие ниже методы, которые можно переопределять:
void setup(Context context), который вызывается один раз в начале задания;
void map(KEYIN key, VALUEIN value, Context context), то есть метод преобразования, вызываемый один раз для каждой пары ключ–значение во
входной порции данных;
void cleanup(), который вызывается по завершении преобразовательного задания.
Класс Reducer определен следующим образом:
class Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
Он содержит следующие ниже методы:
void setup(Context context);
void reduce(KEYIN key, Iterable<VALUEIN> values, Context context), который
вызывается для каждого ключа, чтобы обработать все значения, связанные с этим ключом;
void cleanup(), который вызывается по завершении редукционного задания.
Стоит отметить, что при определении конкретно-прикладного комбинатора не обязательно создавать класс reducer. Как уже говорилось в предыдущем разделе, комбинатор – это, по сути дела, редуктор, который работает
в узле-преобразователе из соображений эффективности, так как он может
выполнять локальную агрегацию производимых преобразователями данных
перед отправкой по сети в узлы-редукторы.
Объект Context позволяет преобразователю/редуктору взаимодействовать
с базисной системой исполнения. Он содержит конфигурационные данные
для вычислительной заявки MapReduce, а также интерфейсы, позволяющие
ему эмитировать результат.
Инструменты программирования на основе модели MapReduce 117
Класс Driver отвечает за конфигурирование вычислительной заявки Map
Reduce для работы в Hadoop. В этом классе программисты могут задавать имя
заявки, типы входных/выходных данных, классы-преобразователи и классыредукторы и другие параметры.
Вторичная сортировка: по умолчанию Hadoop сортирует кортежи ключей-значений по ключу перед передачей их редуктору. Однако в некоторых
случаях программисту может понадобиться контролировать упорядочивание данных, например путем установки сортировки по значению, а не по
ключу. Вторичная сортировка позволяет программисту MapReduce управлять
сортировкой кортежей, отправляемых в вызове метода reduce. Она основана на использовании составного ключа <primary_key, secondary_key>. При
определении конкретно-прикладного разделителя кортежи с одинаковым
первичным ключом отправляются в один и тот же узел-редуктор. А именно
разделитель назначает все кортежи с одинаковым первичным ключом одному узлу-редуктору; затем используется конкретно-прикладной компаратор сортировки, чтобы сортировать кортежи по первичному и вторичному
ключам; наконец, путем определения конкретно-прикладного группового
компаратора кортежи группируются по первичному ключу перед отправкой
в вызове метода reduce. Этот шаблон сортировки полезен во многих случаях. В качестве примера предположим, что требуется извлечь ежедневные
маршруты пользователей из крупного набора геолокализованных данных.
Используя составной ключ <user_id, timestamp>, данные могут быть разбиты
и сгруппированы по идентификатору пользователя и отсортировны по временной метке (в возрастающем либо убывающем порядке, как определено
в классе-компараторе сортировки).
4.2.1.4. Пример программирования
Далее приводится пример приложения, демонстрирующий применение
фреймворка Hadoop MapReduce в задаче создания инвертированного индекса для крупного набора веб-документов (Sarkar и соавт., 2015). Инвертированный индекс содержит набор слов (индексных терминов); по каждому слову указываются идентификаторы всех документов, в которых оно
встречается, и количество появлений в каждом документе. Структура данных
в виде инвертированного индекса является центральным компонентом системы индексирования поисковых систем. На рис. 4.4 показан поток данных
и главные компоненты (преобразователь, комбинатор и редуктор) предлагаемого приложения.
Класс MapTask, который реализует преобразователь, выполняет синтактико-структурный разбор строк текста, поступающих из нескольких входных
документов, и эмитирует пару ⟨word, documentID:numberOfOccurrences⟩ для каждого слова, где documentID – это идентификатор документа, а число появлений
numberOfOccurrences установлено равным 1 (см. листинг 4.1). Каждое слово
обрабатывается с помощью обычных шагов обработки естественного языка,
таких как удаление знаков препинания, приведение слов к канонической
118 Инструменты обработки больших данных
форме и выделение основ слов. Для того чтобы сделать сериализацию объектов легче, программистам приходится использовать специальные типы
для ключей и значений. Например, в Hadoop вместо типов String и Integer
используются соответственно типы Text и IntWritable, которые содержат ту
же информацию, применяя гораздо более простую абстракцию поверх байтовых массивов.
Вход/выход преобразователя
Вход/выход комбинатора
Вход/выход редуктора
Рис. 4.4 Поток исполнения предлагаемого приложения Hadoop
Листинг 4.1 Преобразователь для построения инвертированного индекса
public class MapTask extends Mapper<Object, Text, Text, Text> {
private final Text keyContent = new Text();
private final Text valueContent = new Text();
private final static IntWritable one = new IntWritable(1);
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
// Извлечь имя файла из очередной входной порции данных
FileSplit fileSplit = (FileSplit) context.getInputSplit();
String filename = fileSplit.getPath().getName();
StringTokenizer it = new StringTokenizer(value.toString());
while (it.hasMoreTokens()) {
// Удалить знаки препинания, применить лемматизацию и выделить основу слова
String word = process(it.nextToken());
keyContent.set(word);
valueContent.set(filename + ":" + one);
context.write(keyContent, valueContent);
}
}
}
После преобразования слов используется комбинатор, который агрегирует
промежуточные данные, созданные преобразователем, перед их передачей
Инструменты программирования на основе модели MapReduce 119
редукторам. Как показано в листинге 4.2, класс CombineTask реализует логику
комбинатора путем суммирования всех появлений каждого слова, которое
встречается в документе несколько раз, и выдает список пар ⟨word, document
ID:sumNumberOfOccurrences⟩.
Листинг 4.2 Комбинатор для построения инвертированного индекса
public class CombineTask extends Reducer<Text, Text, Text, Text> {
private final Text sumContent = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values,
Context context) throws IOException, InterruptedException {
// Суммировать все появления слова в документе
HashMap<String, Integer> sumMap = new HashMap<>();
for (Text value : values) {
String[] parts = value.toString().split(":");
sumMap.merge(parts[0], 1, Integer::sum);
}
for (Map.Entry<String, Integer> e : sumMap.entrySet()) {
sumContent.set(e.getKey() + ":" + e.getValue());
context.write(key, sumContent);
}
}
}
По каждому слову класс ReducerTask, который реализует редуктор, выдает
список всех документов, содержащих это слово, с указанием количества появлений в каждом документе. В частности, как показано в листинге 4.3, по
каждому слову эмитируется пара ⟨word, List(documentID:numberOfOccurrences)⟩.
Набор всех выходных пар, сгенерированных функцией reduce, образует инвертированный индекс входных документов.
Листинг 4.3 Редуктор для построения инвертированного индекса
public class ReduceTask extends Reducer<Text, Text, Text, Text> {
private final Text result = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context
context) throws IOException, InterruptedException {
StringBuilder fileList = new StringBuilder();
HashMap<String, Integer> sumMap = new HashMap<>();
for (Text value : values) {
String[] parts = value.toString().split(":");
sumMap.merge(parts[0], Integer.parseInt(parts[1]),
Integer::sum);
}
for (Map.Entry<String, Integer> e : sumMap.entrySet()) {
fileList.append(e.getKey() + ":" + e.getValue())
.append(";");
}
120 Инструменты обработки больших данных
result.set(fileList.toString());
context.write(key, result);
}
}
Наконец, в листинге 4.4 показан главный класс (то есть драйвер), используемый для настройки и выполнения приложения. Программист должен
указать классы, которые будут использоваться в качестве преобразователя,
комбинатора и редуктора, формат ввода/вывода этих классов и входной/
выходной пути данных.
Листинг 4.4 Вычислительная заявка на построение инвертированного индекса
public class InvertedIndexJob extends Configured implements
Tool {
@Override
public int run(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(this.getClass());
job.setMapperClass(MapTask.class);
job.setCombinerClass(CombineTask.class);
job.setReducerClass(ReduceTask.class);
// Задать выходной класс ключа и значения для преобразователя
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
// Задать выходной класс ключа и значения для редуктора
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
// Указать входной и выходной пути
FileInputFormat.addInputPaths(job,"webPage1,
webPage2,...");
FileOutputFormat.setOutputPath(job,
new Path(args[0]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
4.3. Инструменты программирования
на основе рабочих потоков
Этот раздел посвящен нескольким фреймворкам, в которых существует поддержка модели программирования на основе рабочих потоков. Благодаря
своей способности моделировать разнообразные и сложные сценарии рабочие потоки используются в широком спектре прикладных областей, включая научную симуляцию, аналитику данных и машинное обучение. По этой
Инструменты программирования на основе рабочих потоков 121
причине были предложены самые разные параллельные и распределенные
структуры, в которых применяется указанная модель программирования
и, в частности, абстракция ориентированного ациклического графа (DAG)
для моделирования исполнения. Среди фреймворков, основанных на рабочих потоках, Apache Spark1 является одним из самых популярных решений
с открытым исходным кодом, в особенности для разработки общецелевых
приложений. Другими популярными фреймворками, использующими преимущества парадигмы рабочих потоков в области потокового анализа данных, являются Apache Storm2 и Apache Flink3.
Кроме того, определенные усилия были направлены на разработку
фреймворков, облегчающих разработку и исполнение приложений на основе рабочих потоков с целью эффективной эксплуатации ресурсов распределенных вычислений и/или ресурсов хранения данных. Среди таких
решений особенно часто используются Apache Airflow 4, COMPSs (Lordan
и соавт., 2014), облачный фреймворк для глубокой переработки данных
DMCF (Data Mining Cloud Framework) (Marozzo и соавт., 2018), Kepler (Ludaescher и соавт., 2006), YAWL (Yet Another Workflow Language) (Van Der Aalst
и Ter Hofstede, 2005) и Pegasus (Deelman и соавт., 2005) с целью облегчения
разработки и исполнения рабочих потоков анализа научных данных в распределенных средах.
Далее, в качестве характерных представителей инструментов программирования на основе рабочих потоков будут подробно рассмотрены три
из упомянутых выше фреймворков: Apache Spark, Apache Storm и Apache
Airflow.
4.3.1. Apache Spark
Apache Spark – это унифицированный аналитический механизм, разработанный для крупномасштабного анализа данных (Salloum и соавт., 2016). Изначально он был сконструирован в 2009 году Матеем Захарией (Matei Zaharia)
в AMPLab Калифорнийского университета в Беркли, а в 2013 году фреймворк Spark был принят в состав фонда Apache Software Foundation. Благодаря
возможности резидентного программирования и модулям более высокого
уровня для нескольких рабочих нагрузок, которые можно комбинировать
в одном приложении, Spark де-факто стал фреймворком для аналитики больших данных. Поверх фреймворка Spark строится немало мощных и надежных библиотек, что делает его, по сути дела, гибкой системой для широкого спектра приложений, таких как Spark SQL5 для работы с SQL-запросами,
1
2
3
4
5
См. https://spark.apache.org/.
См. https://storm.apache.org/.
См. https://flink.apache.org/.
См. https://airflow.apache.org/.
См. http://spark.apache.org/sql/.
122 Инструменты обработки больших данных
MLlib1 для масштабируемых приложений машинного обучения, GraphX2 для
графопараллельных вычислений и Spark Streaming3 для потокового анализа.
Исполнение типового приложения Spark в кластере управляется центральным координатором (то есть главным процессом приложения), который может подключаться к разным менеджерам кластера, таким как Apache Mesos4,
YARN или Spark Standalone (то есть к встроенному в дистрибутив Spark менеджеру кластера).
Инструмент Ambari служит комплексным решением для резервирования,
управления и мониторинга кластеров Spark. Хотя фреймворк Spark не предоставляет собственной распределенной системы хранения данных, он был
разработан для функционирования поверх нескольких источников данных,
таких как распределенные файловые системы (например, HDFS), облачные
объектные хранилища (например, Amazon S3, OpenStack Swift) и системы
управления базами данных NoSQL (например, Cassandra). Комплексный стек
фреймворка показан на рис. 4.5.
Ambari
Резервирование, управление и мониторинг кластеров Spark
MLlib
Spark SQL (Машинное
(SQL)
обучение)
GiraphX
(Графовая
обработка)
Spark
Другие
Streamming библиотеки
(Потоковая
Spark
обработка)
Spark Core
Механизм обработки
Mesos / YARN / Standalone
Управление кластерными ресурсами
HDFS / Amazon S3 / OpenStack Swift / Cassandra
Распределенная файловая система и хранение
Рис. 4.5 Программный стек фреймворка Spark
Несколько крупных компаний, включая eBay, Amazon и Alibaba, используют фреймворк Spark в производстве, чтобы быстро добывать информацию из
данных в аналитических целях. Например, eBay использует решения на базе
Spark по работе с большими данными и машинным обучением для журнальной агрегации и предоставления целевых предложений, чтобы повышать
качество обслуживания заказчиков. Благодаря очень большому сообществу
пользователей и разработчиков развитие фреймворка Spark постоянно расширяется. В частности, много усилий направлено на усовершенствование
1
2
3
4
См. https://spark.apache.org/mllib/.
См. http://spark.apache.org/graphx/.
См. https://spark.apache.org/streaming/.
См. http://mesos.apache.org/.
Инструменты программирования на основе рабочих потоков 123
библиотеки MLlib, которая обеспечивает продвинутую аналитику данных
с привлечением параллельных алгоритмов машинного обучения.
4.3.1.1. Главные концепции
Ядро фреймворка Spark предлагает базовые функциональности, такие как
планирование заданий, управление памятью, механизмы восстановления после отказов, а также разные абстракции для представления данных
и управления вычислениями. В частности, данные представляются в виде
устойчивых распределенных наборов данных (RDD, от англ. resilient distributed dataset), а вычисления на этих наборах данных выражаются в виде
преобразований или действий.
Устойчивые распределенные наборы данных: устойчивые распределенные наборы данных (RDD) – это абстракция распределенной памяти,
представляющая собой набор элементов, распределенных по вычислительным узлам кластера, которыми можно манипулировать параллельно (см.
рис. 4.6). Эти элементы не мутируют, отказоустойчивы и разбиты на разделы
таким образом, что по меньшей мере один раздел сохраняется в основной
памяти каждого узла кластера или, если памяти недостаточно, сохраняется
на локальном диске посредством операции выплескивания (spilling). Раздел – это атомарный кусок данных, и каждый узел-работник может выполнять заданный приложением исходный код в принадлежащем ему разделе
RDD-набора данных. После создания содержимое такого набора данных не
подлежит модификации, но любая модификация создает новый RDD-набор.
За счет этого также существует возможность автоматически восстанавливать
данные в случае отказа, используя информацию о происхождении, чтобы
эффективно пересчитывать потерянные данные. Эта операция полностью
скрыта от пользователя, так как фреймворк Spark знает, как каждый RDDнабор был создан, и в случае отказа RDD-наборы автоматически перестраиваются, используя ориентированный ациклический граф преемственности
набора данных.
Логическое представление
Физическое представление
Раздел 1
Раздел 2
Раздел 10
Рис. 4.6 Пример устойчивого распределенного
набора данных (RDD)
124 Инструменты обработки больших данных
Преобразования и действия: приложение Spark пишется как последовательность операций, выполняемых на RDD-наборах данных. В частности,
такими наборами данных можно манипулировать с помощью двух типов
операций:
преобразования, которые представляют собой крупнозернистые операции, создающие новые RDD-наборы на основе предыдущих. Примерами могут служить операции map, filter и join;
действия, которые выполняют вычисления на RDD-наборе данных или
записывают данные в систему хранения. Эти операции обычно выводят значение, например count, collect, reduce и save.
Во время исполнения приложение Spark представляется в виде ориентированного ациклического графа, состоящего из источников данных, преобразований и операций, а также стоков данных. Важным аспектом преобразований
на RDD-наборах данных является то, что они оцениваются лениво, означая,
что новый RDD-набор не вычисляется сразу, а материализуется только при
инициировании какого-либо действия. За счет этого Spark может выполнять
оптимизацию в ориентированном ациклическом графе операций, например
объединять несколько преобразований filter/map или избегать перемещения
данных при групповом преобразовании, если данные разбиты на разделы.
Преобразования и действия упрощают разработку параллельных приложений. По сравнению с фреймворком Hadoop, для разработки приложения
с помощью фреймворка Spark, по сути дела, требуется меньше строк исходного кода, в особенности при использовании языка программирования
Scala, который предоставляет объектно ориентированный и функциональный интерфейсы программирования высокого уровня. В этом смысле Spark
обеспечивает средний уровень абстракции, поскольку программисты имеют
в своем распоряжении мощные API, скрывающие низкоуровневые детали,
связанные с параллельными и распределенными вычислениями. Тем не менее требуются средние навыки программирования, в основном связанные
с функциональным программированием.
Локальность данных: в большинстве случаев исходный код и данные друг
от друга отделены, что создает необходимость в перемещении данных между
ними. Под локальностью данных понимается процесс переноса вычислений
ближе к месту расположения фактических данных в узле кластера вместо
переноса крупных данных к вычислениям. Как правило, сериализованный
исходный код доставляется с места на место быстрее, чем кусок данных,
поскольку размер исходного кода намного меньше размера данных. За счет
этого минимизируется перегруженность сети и увеличивается совокупная
пропускная способность системы. Фреймворк Spark строит свое планирование на основе этого общего принципа локальности данных (см. рис. 4.7).
Оказывается, что чтение данных в пределах одного узла непосредственно
из основной памяти (как вариант – с диска) проходит быстрее, чем чтение
между узлами одной стойки, тогда как чтение между узлами в разных стойках проходит еще медленнее (скорость доступа к данным ниже). В ситуации,
когда ни на одном незанятом исполнителе нет необработанных данных,
Инструменты программирования на основе рабочих потоков 125
необходимо принимать решение: (а) ждать до тех пор, пока не освободится
занятый процессор, чтобы запустить задание на данных на том же сервере,
либо (b) немедленно запустить новое задание в более отдаленном месте,
требующее перемещения туда данных.
Локально к данным
Узел 1
Данные
Локально к стойке
Узел 1
Задача
Узел 1
Другая стойка
Узел 1
Задача
Узел 2
Узел 2
Узел N
Узел N
Стойка 1
Стойка 2
Узел 2
Узел 1
Узел 1
Задача
Узел 2
Узел 2
Узел N
Узел N
Узел N
Узел N
Стойка 1
Стойка 2
Стойка 1
Стойка 2
Данные
Узел 2
Данные
Рис. 4.7 Локальность данных
Кеширование: как уже неоднократно обсуждалось выше, фреймворк
Spark также поддерживает сохранность RDD-наборов данных в основной
памяти. Эта операция, именуемая кешированием, или резидентными вычислениями, позволяет реиспользовать RDD-наборы в будущем, что делает
фреймворк Spark почти в 100 раз быстрее, чем Hadoop (Verma и соавт., 2016),
в особенности в итеративных алгоритмах. Когда RDD-набор сохраняется,
каждый работник удерживает в основной памяти принадлежащий ему раздел и реиспользует его для других действий. Этот механизм также является
отказоустойчивым благодаря свойству преемственности RDD-наборов, которое позволяет автоматически пересчитывать раздел RDD-набора в случае
его потери. Spark старается хранить в памяти как можно больше данных,
используя эффективные библиотеки сериализации (например, Kryo), чтобы
уменьшать занимаемую ими память. Однако если память переполнена, то
RDD-набор данных может быть выплеснут на диск. По этой причине, несмотря на то что фреймворк Spark можно считать более оптимальной альтернативой фреймворку Hadoop, в некоторых классах приложений у него
есть свои ограничения, которые делают его комплементарным фреймворку
Hadoop. Главное из них заключается в том, что для сокращения времени выполнения данные должны умещаться в оперативной памяти. Оперативная
память является, по сути дела, критически важным ресурсом, и Spark может
страдать от отсутствия процессов автоматической оптимизации, призванных максимизировать резидентные вычисления при минимизации вероятности выплескивания данных, что является одной из главнейших причин
снижения производительности (Cantini и соавт., 2021).
Кадры данных и наборы данных: во фреймворке Spark версии 1.3 была
введена новая абстракция данных под названием DataFrame (кадр данных),
а в версии 2.0 – еще одна абстракция под названием Dataset (набор данных).
126 Инструменты обработки больших данных
Подобно RDD-наборам данных, типы DataFrame и Dataset не мутируют и являются распределенными коллекциями данных. Кроме того, типы DataFrame
организуют данные в именованные столбцы, как таблицы в реляционных
СУБД. Начиная со Spark 2.0 типы Dataset расширяют типы DataFrame и предоставляют объектно ориентированный интерфейс программирования, то
есть кадры данных можно представлять как коллекцию обобщенного типа
Dataset[Row], где Row – это обобщенный и нетипизированный объект. Типы
Dataset обладают преимуществами RDD-наборов данных, такими как устойчивость и поддержка лямбда-функций, а также набором оптимизаций, выполняемых механизмом исполнения Spark SQL. Экземпляры типов DataFrame
и Dataset могут создаваться из структурированных файлов, таблиц Hive, баз
данных или существующих RDD-наборов данных.
Широковещательные переменные и аккумуляторы: широковещательные переменные и аккумуляторы используются для обмена переменными
между узлами кластера. Широковещательные переменные позволяют программисту кешировать переменную, доступную только в режиме чтения,
в каждом узле, избавляя его от необходимости доставлять ее копию. Их можно использовать для эффективного распространения копии большого набора
данных в каждом узле с помощью эффективных алгоритмов широковещательной трансляции. С другой стороны, аккумуляторы похожи на глобальные
переменные, с которыми можно эффективно работать параллельно, так как
они применимы только к ассоциативным и коммутативным операциям. Например, они могут использоваться для агрегирования информации между
исполнителями, такой как счетчики или суммы.
4.3.1.2. Архитектура
Как показано на рис. 4.8, архитектура фреймворка Apache Spark состоит из
узла-мастера, на котором работает программа-драйвер, отвечающая за исполнение приложения, отправленного пользователем, и набора работников.
В Spark используется архитектура «мастер–работник», в которой драйвер является центральным координатором, а узлы-работники выполняют один или
несколько исполнителей, отвечающих за исполнение заданий, назначенных
драйвером, и хранение данных.
В частности, приложение Spark определяется как набор независимых
стадий, выполняемых на пуле узлов-работников и связанных в ориентированном ациклическом графе. Стадия – это набор заданий, исполняющих
одинаковый исходный код на разных разделах входных данных, тем самым
обеспечивая параллелизм данных, так как входные данные делятся на куски
и обрабатываются параллельно разными вычислительными узлами. Spark
также поддерживает параллелизм заданий, когда отдельные стадии одного
и того же приложения исполняются параллельно.
Программа-драйвер отвечает за инициализацию контекста Spark, который
выступает в качестве точки входа в кластер Spark. Контекст Spark может общаться с разными менеджерами кластера, чтобы приобретать ресурсы в клас
Инструменты программирования на основе рабочих потоков 127
тере, такими как Mesos, YARN и Kubernetes, и после подключения он передает
приложение (то есть файл JAR или Python) исполнителям на обработку.
Узел-работник
Исполнитель
Задание
Программа-драйвер
Контекст
Spark
Задание
Кеш
Задание
Менеджер кластера
Узел-работник
Исполнитель
Задание
Задание
Кеш
Задание
Рис. 4.8 Архитектура кластера Spark
Стоит отметить, что каждое переданное драйверу приложение имеет свой
собственный пул процессов-исполнителей, которые выполняют задания
с помощью вычислительных потоков. За счет этого имеется возможность
отделять приложения друг от друга как в плане планирования, так и в плане
исполнения. Однако из этого следует, что обмен данными между разными
приложениями, определенными с помощью разных экземпляров контекста
Spark, невозможен, и их необходимо записывать во внешнюю систему хранения. На рис. 4.9 показано, как в приложении Spark генерируются задания.
Приложение создает RDD-наборы данных, преобразовывает их и исполняет
действия. В результате образуется ориентированный ациклический граф
операторов. Указанный граф компилируется в стадии, то есть последовательСтадия 1
Задание
Задание
Стадия 2
Задание
Задание
Задание
Задание
Задание
Задание
Задание
Задание
Задание
collect
Задание
Задание
Задание
Задание
textFile
flatMap
map
reduceByKey
Рис. 4.9 Поток заданий во фреймворке Spark
128 Инструменты обработки больших данных
ности RDD-наборов данных без операций перетасовки между ними. Каждая
стадия исполняется как набор заданий, причем для каждого раздела обычно
генерируется одно задание.
4.3.1.3. Основы
Типичное приложение Spark нуждается в определении контекста Spark, то
есть объекта SparkContext, который является точкой входа в функциональность фреймворка Spark. Он представляет собой подключение к кластеру
Spark и может использоваться для создания RDD-наборов данных, аккумуляторов и широковещательных переменных. Программа-драйвер Spark использует SparkContext для подключения к кластеру через менеджера ресурсов
(например, YARN или Mesos). Для создания объекта SparkContext требуется
объект SparkConf, в котором хранятся такие конфигурационные параметры,
как имя приложения и основной URL-адрес (то есть URL-адрес кластера
Spark, Mesos или YARN, или специальный строковый литерал «local» для выполнения приложения в локальном режиме). В конфигурацию могут быть
включены и другие конфигурационные параметры, например количество
ядер и объем памяти исполнителя, работающего в узле-работнике.
val conf = new SparkConf().setAppName(appName).setMaster(master)
new SparkContext(conf)
Начиная с версии 2.0 класс SparkSession обеспечивает единую точку входа
для взаимодействия с базисными функциональностями фреймворка Spark
и позволяет программистам использовать API-интерфрейсы DataFrame и Dataset, а также API-интерфейсы SQL, потоковой обработки и графовой обработки. Все имеющиеся в классе SparkContext функциональности также имеются
и в классе SparkSession.
new SparkSession.builder.appName(appName).getOrCreate()
После создания объектов SparkContext или SparkSession программисты могут создавать коллекции данных, которые будут распараллеливаться в клас
тере. Распараллеленные коллекции создаются путем вызова функции parallelize на существующей коллекции (например, массиве), и ее элементы будут
реплицироваться, создавая распределенный набор данных (то есть RDD).
Программисты также могут указывать количество разделов, на которые нужно разбивать набор данных.
val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)
Распределенные наборы данных еще можно создавать из любого источника
хранения Hadoop, включая локальную файловую систему (файл должен быть
доступен узлам-работникам), HDFS, HBase, Amazon S3 и другие. Например,
текстовый файл может быть загружен в RDD-набор с помощью метода textFile.
val distFile = sc.textFile("data.txt")
Инструменты программирования на основе рабочих потоков 129
После загрузки программисты могут применять преобразования и действия к таким распределенным данным, используя функциональный стиль
программирования языка Scala. Например, ниже показано, как обрабатывать
текстовый файл, загруженный с помощью предыдущей инструкции, чтобы вычислить его длину с помощью функций map и reduce. Стоит отметить,
что результат преобразования map не вычисляется сразу, так как в Spark используется стратегия ленивого оценивания. Фреймворк Spark выполняет
вычисления, только когда вызывается действие reduce, распределяя задания
по всем узлам-работникам, а затем агрегируя частичные результаты в программе-драйвере.
val lineLengths = distFile.map(s => s.length)
val totalLength = lineLengths.reduce((a, b) => a + b)
В табл. 4.1 перечислено несколько наиболее распространенных преобразований и действий, поддерживаемых фреймворком Spark. При этом даны
описания типов входных и выходных данных каждого из них. Смысл каждой
операции подробно описан в табл. 4.2.
Таблица 4.1. Самые распространенные преобразования и действия, поддерживаемые
фреймворком Spark
Преобразования
Действия
map(f: T ⇒ U) : RDD[T] ⇒ RDD[U]
filter(f: T ⇒ Bool) : RDD[T] ⇒ RDD[T]
flatMap(f: T ⇒ Seq[U]) : RDD[T] ⇒ RDD[U]
sample(fraction: Float) : RDD[T] ⇒ RDD[T]
groupByKey() : RDD[(K, V)] ⇒ RDD[(K, Seq[V])]
reduceByKey(f: (V, V) ⇒ V) : RDD[(K, V)] ⇒ RDD[(K, V)]
union() : (RDD[T], RDD[T]) ⇒ RDD[T]
join() : (RDD[(K, V)], RDD[(K, W)]) ⇒ RDD[(K, (V, W))]
partitionBy(c: Partitioner[K]) : RDD[(K, V)] ⇒ RDD[(K, V)]
count() : RDD[T] ⇒ Long
collect() : RDD[T] ⇒ Seq[T]
reduce(f: (T, T) ⇒ T) : RDD[T] ⇒ T
take(n: Int) : RDD[T] ⇒ Seq[T]
Таблица 4.2. Смысл самых распространенных преобразований и действий,
поддерживаемых фреймворком Spark
Преобразование/ Смысл
Действие
map(f)
Возвращает новый RDD-набор данных, вычисленный путем
пропускания каждого элемента источника через функцию f
filter(f)
Возвращает новый RDD-набор данных, содержащий те элементы
источника, для которых f возвращает истину
flatMap(f)
Аналогично map, но каждый входной элемент может быть
преобразован в ноль или более выходных элементов
sample(fraction)
Делает выборку части данных, с возвратом или без
groupByKey()
Группирует значения для каждого ключа в RDD-наборе данных
в одну последовательность
130 Инструменты обработки больших данных
Таблица 4.2 (окончание)
Преобразование/ Смысл
Действие
reduceByKey(f)
Объединяет значения для каждого ключа с помощью
ассоциативной и коммутативной функции reduce
union()
Объединяет элементы исходного набора данных и аргумента
join()
Возвращает новый RDD-набор данных, состоящий из пар,
со всеми парами элементов для каждого ключа
partitionBy(c)
Случайно перетасовывает данные в RDD-наборе, создавая либо
больше, либо меньше разделов
count()
Возвращает количество элементов в наборе данных
collect()
Возвращает все элементы набора данных в виде массива
в программе-драйвере
reduce(f)
Агрегирует элементы набора данных с помощью коммутативной
и ассоциативной функции
take()
Возвращает массив с первыми n элементами набора данных
4.3.1.4. Пример программирования
Рассматриваемый ниже пример программирования представляет собой
упрощенную версию задания по анализу рыночной корзины, которое ищет
товарные позиции, часто встречающиеся вместе в наборе транзакций.
В частности, при получении текстового файла транзакций, представленного
в виде набора товарных позиций (по одному в каждой строке), разделенных
запятой, программа выводит все пары товарных позиций, упорядоченные
по количеству появлений вместе (см. рис. 4.10).
Транзакции
Рис. 4.10 Упорядоченные пары товарных позиций,
наиболее часто встречающихся вместе в наборе транзакций
Приложение Spark с использованием абстракции Dataset. Исходный
код примера представлен в листинге 4.5. На первом шаге программисту
необходимо инициализировать объект SparkSession в качестве точки входа
в кластер Spark. Затем используется объект SparkContext объекта SparkSession, чтобы загрузить входной текстовый файл из локальной файловой системы.
Инструменты программирования на основе рабочих потоков 131
Каждая строка входного файла разбивается с использованием запятой
в качестве разделителя и структурно разбирается на массив элементов. Пос
ле этого с помощью преобразования filter сохраняются только те наборы
товарных позиций, в которых есть хотя бы два элемента. Затем эти пары
передаются в конкретно-прикладной метод getAllPairs, который извлекает все пары в объект Array[(String, String)]. Результат этого преобразования разглаживается с помощью метода flatMap и сохраняется в переменной
с именем pairs.
После этого, аналогично приложению для подсчета слов, вычисляются
встречаемости каждой пары. Сначала посредством метода map для всех пар
генерируется новая пара ключ–значение, где ключ – это строковый литерал,
полученный путем конкатенации двух элементов (то есть pair._1 и pair._2),
а значение равно 1. Затем преобразование reduceByKey объединяет значения
по каждому ключу, применяя к ним функцию sum. Результирующий RDD-на
бор данных, хранящийся в pairsOcc, содержит уникальные пары элементов
и их количество (то есть количество появлений вместе).
Наконец, полученный на предыдущем шаге RDD-набор упорядочивается
с помощью преобразования sortBy на основе количества появлений вместе.
Поскольку условие преобразования sortBy, примененное к более чем одному разделу, может возвращать результат, который будет упорядочен лишь
частично, перед печатью упорядоченного списка необходимо исполнить
команду collect, чтобы извлечь все элементы RDD-набора данных со всех
узлов-работников и передать в узел-драйвер.
Листинг 4.5 Анализ рыночной корзины
object MarketBasketAnalysis {
def main(args: Array[String]) {
val inputFile = "transactions.txt"
val spark = SparkSession.builder
.master("local[*]")
.appName("MarketBasketAnalysis")
.getOrCreate()
val sc: SparkContext = spark.sparkContext
val textFile = sc.textFile(inputFile)
val items: RDD[Array[String]] = textFile.map(line => line.
split(','))
val pairs: RDD[(String, String)] = items.filter(items => items
.length >= 2).flatMap(items => getAllPairs(items))
val pairsOcc: RDD[(String, Int)] = pairs.map(pair => (pair._1
+ "-" + pair._2, 1)).reduceByKey((i, j) => i + j)
val ordPairsOcc: RDD[(String, Int)] = pairsOcc.sortBy(x => x.
_2, ascending = false)
ordPairsOcc.collect().foreach(x => println(x._1 + " " + x._2))
132 Инструменты обработки больших данных
}
def getAllPairs(items: Array[String]): Array[(String, String)] =
{
for (i1 <- items; i2 <- items; if (i1.compareTo(i2) < 0))
yield (i1, i2)
}
}
Приложение Spark с использованием абстракции DataFrame. Поскольку фреймворк Spark предоставляет API для структурированных данных и SQL-подобных запросов, ниже будет рассмотрен еще один пример,
в котором используется абстракция кадра данных.
Имея в распоряжении JSON-файл, в котором хранятся данные о сотрудниках ИТ-компании (например, их личная информация, отдел, зарплата и навыки), требуется выполнить несколько запросов, используя методы, предоставляемые API-интерфейсом DataFrame, и инструкции языка SQL.
Как уже говорилось в разделе «Главные концепции», объекты DataFrame
позволяют работать со структурированными данными и организовывать их
в именованные столбцы, аналогично таблицам в реляционных СУБД. Для
этого фреймворк Spark позволяет программистам определять схему загружаемых данных, чтобы давать имя каждому столбцу и ссылаться на него, используя это имя. Как показано в листинге 4.6, с помощью объекта StructType
определяется кадр данных, столбцами которого являются идентификаторы
сотрудников, их имена, фамилии и возраст, отдел, к которому они принадлежат, их зарплата и набор навыков в формате массива (например, Java, Python или C). Программист может указать формат каждого столбца (например,
целое число, строковый литерал или массив), который будет использоваться
для структурного разбора входных данных. Затем указанная схема используется для загрузки входных данных из JSON-файла.
Листинг 4.6 Анализ кадра данных
object DataFrameExample {
def main(args: Array[String]): Unit = {
// Инициализировать объект SparkSession
val schemaEmpl = new StructType()
.add("id", IntegerType)
.add("name", StringType)
.add("surname", StringType)
.add("age", IntegerType)
.add("department", StringType)
.add("salary", IntegerType)
.add("skills", ArrayType(StringType))
val dfEmpl = spark.read.schema(schemaEmpl).json(fileEmpl)
dfEmpl.groupBy("department").count().show()
Инструменты программирования на основе рабочих потоков 133
dfEmpl.agg(min("age"), max("age"), mean("age")).show()
dfEmpl.filter("age >= 30 AND age < 40").agg(mean("salary")).
show()
dfEmpl.filter("age >= 40 AND age < 50").agg(mean("salary")).
show()
}
}
В листинге 4.6 показано, как выполнять несколько запросов с помощью
методов, предоставляемых API-интерфейсом DataFrame. Например, после загрузки входных данных с помощью метода groupBy подсчитывается количество сотрудников в каждом отделе. Затем, используя метод agg, весь набор
данных агрегируется без групп, чтобы вычислить минимальный, максимальный и средний возраст сотрудников. Наконец, вычисляется средняя зарплата
сотрудников с возрастом в диапазоне 30–39 и 40–49 лет.
Еще один способ выполнения запросов состоит в прямом использовании
инструкций языка SQL, как показано в листинге 4.7. Для этого в Spark необходимо создать временное представление таблицы, чтобы получить возможность выполнять SQL-запросы поверх него, используя объект createOrReplaceTempView. Благодаря этому таблица может использоваться для извлечения
данных в инструкциях SQL, например для выбора всех сотрудников компании или, как это так же сделано в листинге 4.6, для подсчета числа сотрудников в каждом отделе с помощью модификатора groupBy.
Листинг 4.7 Анализ кадра данных с помощью инструкций языка SQL
dfEmpl.createOrReplaceTempView("itcompany")
spark.sql("SELECT * FROM itcompany").show()
spark.sql("SELECT department, COUNT(*) FROM itcompany
GROUP BY department").show()
Теперь предположим, что имеется еще один набор с данными о проектах, в которых участвует ИТ-компания. При этом описывается следующая
информация о каждом проекте: идентификатор проекта, его название, описание, бюджет, навыки, необходимые для этого проекта, и идентификаторы
сотрудников, заданные в виде массива. Для того чтобы узнать, какие отделы
компании наиболее вовлечены в данный проект (например, в проект с ИД,
равным 0), необходимо выполнить соединение (join) между кадром данных
сотрудников, содержащим информацию об отделах, и кадром данных проектов с информацией о сотрудниках, задействованных в каждом проекте.
Перед соединением двух кадров данных необходимо выполнить операцию
explode, чтобы возвращать новую строку для каждого элемента заданного
массива идентификаторов сотрудников. Благодаря этому появится возможность объединить два кадра данных с условием, что идентификатор сотрудника в первом кадре данных равен идентификатору сотрудника, участвующего в проекте, из второго кадра данных, и выполнить группировку groupBy
по отделу. Исходный код примера показан в листинге 4.8.
134 Инструменты обработки больших данных
Листинг 4.8 Соединение двух кадров данных
val dfProjExpl = dfProj.filter(dfProj("id") === "0").
withColumn("employee", explode($"employees"))
dfEmpl.join(dfProjExpl, dfEmpl("id") === dfProjExpl
("employee")).groupBy("department").count().show()
4.3.2. Apache Storm
Apache Storm – это система распределенных реально-временных вычислений, позволяющая надежно обрабатывать неограниченные потоки данных.
Изначально указанный фреймворк был создан в 2010 году Натаном Марцем
(Nathan Marz) с идеей системы обработки потоков, которую можно было бы
разрабатывать в одной программе как единую абстракцию, а в 2014 году он
был принят в состав фонда Apache Software Foundation.
До появления фреймворка Storm системы реально-временной обработки
данных разрабатывались с использованием очередей, чтобы писать данные,
и работников, чтобы их читать и обрабатывать. Работники могли отправлять
сообщения другим работникам через очереди для дальнейшей обработки.
При таком подходе требовалось следить за тем, чтобы очереди и работники
всегда были живы, что затрудняло сборку приложений. Бóльшая часть логики
приложения была связана не с деловой задачей, а с тем, куда отправлять /
откуда получать сообщения и как их сериализовывать/десериализовывать.
Когда был предложен фреймворк Storm, он оказался чрезвычайно масштабируемым, простым в использовании и способным обрабатывать данные
с низкой задержкой. Сегодня он широко применяется для реально-временной аналитики такими крупными компаниями, как Twitter, Groupon и Spotify. Например, разработчики Twitter используют фреймворк Storm для обработки многочисленных терабайтов потоков данных в день, фильтрации
и агрегации содержимого, а также для применения алгоритмов машинного
обучения к потокам данных. Сообщество пользователей фреймворка Storm
относительно невелико, однако благодаря удобным функциям и гибкости
указанный фреймворк может использоваться компаниями среднего размера для деловых целей (например, для реально-временного обслуживания
заказчиков, анализа безопасности и обнаружения угроз). Другие типичные
варианты использования фреймворка Storm лежат в сфере онлайнового машинного обучения, непрерывных вычислений и распределенном вызове
дистанционных процедур (RPC).
Фреймворк Storm обеспечивает средний уровень абстракции, так как программисты могут легко определять приложение с помощью базовых абст
ракций (выпускных кранов, потоков, поперечных задвижек1 и топологий,
1
Система Storm предоставляет два компонента особого типа, которые обрабатывают входной поток: выпускной кран (spout) и поперечную задвижку (bolt). Выпускные краны обрабатывают внешние данные, чтобы создавать потоки кортежей.
Краны производят кортежи и отправляют их в задвижки. Задвижки обрабатывают
Инструменты программирования на основе рабочих потоков 135
о которых речь пойдет далее) и тестировать его в локальном режиме без
необходимости запускать его в кластере. Фреймворк написан в основном на
Clojure, диалекте языка Lisp, но в целях удовлетворения запросов большого
числа потенциальных пользователей он также предоставляет API на Java.
Более того, разные языки программирования поддерживаются с помощью
мультиязычного протокола, который позволяет реализовывать поперечные
задвижки и выпускные краны с помощью других языков, включая языки, не
основанные на JVM, такие как Python. Среда исполнения фреймворка Storm
поддерживает параллелизм данных, когда многочисленные вычислительные
потоки параллельно исполняют одинаковый исходный код на разных кусках
данных, и параллелизм заданий, когда разные выпускные краны и поперечные задвижки работают параллельно.
4.3.2.1. Главные понятия
Абстракции для данных и вычислений: предлагаемая фреймворком Storm
парадигма программирования основана на пяти абстракциях для данных
и вычислений:
кортеж: это базовая единица данных, которую может обрабатывать
приложение Storm. Кортеж состоит из списка полей (например, byte,
char, integer, long);
поток: представляет собой неограниченную последовательность кортежей, которая создается или обрабатывается параллельно. Потоки
могут создаваться с помощью стандартных сериализаторов (например,
целых чисел, двойных чисел) либо с помощью конкретно-прикладных;
выпускной кран: это источник данных в потоке. Данные читаются из
различных внешних источников, таких как API социальных сетей, сети
датчиков и системы очередей (например, Java Message Service, Kafka1,
Redis2). Затем они подаются в приложение;
поперечная задвижка: представляет собой обрабатывающую сущность.
В частности, она может исполнять любой тип задания или алгоритма
(например, очистку данных, функции, соединения, запросы);
топология: представляет собой вычислительную заявку. Обобщенная
топология конфигурируется как ориентированный ациклический граф,
в котором выпускные краны и поперечные задвижки представляют
вершины графа, а потоки выступают в качестве его ребер. Она может
работать бесконечно до тех пор, пока ее не остановят.
Базовый принцип фреймворка Storm заключается в параллельном создании и обработке потоков, но они могут представляться в одной программе
как единая абстракция. Этот принцип лег в основу концепции выпускных
кранов и поперечных задвижек: выпускной кран создает новые потоки, а по-
1
2
кортежи из входных потоков и производят выходные кортежи. – Прим. перев.
См. https://kafka.apache.org/.
См. https://redis.io/.
136 Инструменты обработки больших данных
перечная задвижка принимает потоки на входе и генерирует их на выходе.
Основополагающая идея заключалась в том, что краны и задвижки по своей
сути параллельны, как преобразователи и редукторы в Hadoop. Задвижки
просто подписываются на любые потоки, которые им нужны, и детализируют
то, как входящий поток должен разбиваться на разделы. На более высоком
уровне абстракции топология представляет собой сеть выпускных кранов
и поперечных задвижек. Часть создания топологии состоит в определении
потоков, которые каждая задвижка должна получать на входе посредством
группировки потока, задающей способ разбивки потока между задвижками.
Примерами наивной группировки потока являются группировка перетасовкой, группировка по полю и прямая группировка.
При группировке перетасовкой кортежи случайно распределяются между задвижками, чтобы каждая задвижка получала равное количество
кортежей.
При группировке по полю кортежи разбиваются по полю, указанному
в группировке (то есть кортежи с одинаковым полем назначаются одинаковому заданию, тогда как кортежи с разными полями могут обрабатываться разными заданиями).
Наконец, при прямой группировке производитель кортежа определяет,
какое задание потребителя получит этот кортеж.
Другими встроенными во фреймворк Storm группировками потоков являются частичная группировка по ключу, всеобщая группировка, глобальная
группировка, никакая группировка и локальная группировка.
Обработка сообщений: сильная сторона обработки в Storm заключается
в алгоритме, призванном обеспечивать надежность обработки сообщений.
Обычно для обеспечения надежности обработки сообщений используются
промежуточные брокеры, способные восстанавливать сообщения в случае
отказа одной из обрабатывающих единиц. Однако внедрение таких брокеров
в реально-временную систему усложнило бы архитектуру, затруднив поддержание отказоустойчивости и замедлив транзит сообщений между разными
компонентами (то есть выпускными кранами и поперечными задвижками).
В целях обеспечения обработки сообщений и надежности без промежуточных брокеров повторные попытки должны поступать от источника (то
есть из выпускных кранов). Однако отказы могут происходить в любой точке
системы, и очень важно точно идентифицировать эти отказы. С целью решения этих трудностей во фреймворке Storm используется серия заданий
по отслеживанию ориентированного ациклического графа кортежей, генерируемого выпускными кранами. В частности, задание по отслеживанию
подтверждает выпускной кран, порождающий кортеж. Более того, для работы
с большими графами кортежей во фреймворке Storm используется алгоритм
отслеживания, который требует минимального пространства (около 20 байт)
для каждого кортежа. Каждый отслеживатель использует словарь, который
связывает идентификатор кортежа выпускного крана с парой значений:
идентификатором задания, породившего кортеж, который используется для
Инструменты программирования на основе рабочих потоков 137
подтверждения, и 64-битовым целым числом, именуемым «ack val», которое
представляет собой совокупное состояние дерева кортежей. В частности, это
значение получается из операции XOR всех идентификаторов кортежей в дереве, которые были сгенерированы и/или подтверждены.
Таким образом, когда задание по подтверждению (acker task) проверяет,
что число «ack val» стало равным нулю, оно знает, что дерево кортежей завершено. Этот механизм обработки по принципу «хотя бы один раз» обеспечивает семантику обработки без поддержки состояния, гарантируя, что все
сообщения будут обработаны, хотя в случае системных отказов некоторые
могут быть обработаны несколько раз. Для программистов, которым требуются операции с поддержкой состояния, фреймворк Storm предлагает API
микропакетирования под названием Trident, построенный поверх фреймворка Storm и обеспечивающий семантику обработки «ровно один раз».
Планировщик изоляции для многоарендаторской обработки: еще
одним важным аспектом фреймворка Storm является многоарендаторский
подход к обработке, то есть возможность выполнять независимые приложения в коллективном кластере, обеспечивая, чтобы все приложения имели
достаточно ресурсов без влияния других приложений в кластере. Для этого
в Storm используется планировщик изоляции, который позволяет просто
и безопасно коллективизировать кластер по нескольким топологиям. Планировщик изоляции позволяет пользователю определять топологии, которые
должны оперировать на отдельной группе машин внутри кластера, где не
будут работать другие топологии (то есть они будут изолированы). Эти изолированные топологии имеют в кластере приоритет; поэтому при наличии
конкуренции с неизолированными топологиями ресурсы будут выделяться
изолированным топологиям. После того как все изолированные топологии
будут распределены, оставшиеся в кластере машины распределяются между
всеми неизолированными топологиями. Планировщик изоляции решает задачу многоарендаторства, обеспечивая полную изоляцию между топологиями и предотвращая конфликт ресурсов между ними.
4.3.2.2. Архитектура
По многим причинам фреймворк Storm можно считать аналогом фреймворка Hadoop в части реально-временной обработки данных. И действительно,
кластер фреймворка Storm похож на кластер фреймворка Hadoop, но если
в Hadoop есть вычислительные заявки MapReduce, то в Storm есть топологии,
с той разницей, что вычислительная заявка Hadoop MapReduce в конечном
счете всегда завершается, тогда как топология обрабатывает сообщения вечно или до тех пор, пока ее не убьют. Кластер Storm состоит из двух типов
узлов: узла-мастера и узлов-работников. В первом работает демон под названием Нимб (Nimbus), который отвечает за распределение исходного кода
по кластеру, назначение заданий машинам, мониторинг заданий на предмет
отказов и их перезапуск при необходимости. И с другой стороны, в каждом
узле-работнике запускается демон под названием Надсмотрщик (Supervi-
138 Инструменты обработки больших данных
sor), который прослушивает задания, назначенные его машине, и запускает
и останавливает процессы-работники по мере необходимости, основываясь
на том, что ему было назначено Нимбом. Каждый процесс-работник исполняет подмножество топологии, состоящее из многочисленных процессовработников на многочисленных машинах. На рис. 4.11 показана архитектура
типичного кластера фреймворка Storm.
Надсмотрщик
Работники
Надсмотрщик
Работники
Надсмотрщик
Работники
Надсмотрщик
Работники
Узлы-работники
Процессы-работники
ZooKeeper
Нимб
ZooKeeper
ZooKeeper
Узел-мастер
Кластерная координация
Рис. 4.11 Архитектура кластера Storm
Фреймворк Storm изначально, по своей конструкции, является отказо
устойчивым. Если работник умирает, Надсмотрщик просто его перезапускает, а если его невозможно перезапустить (то есть периодические сигналы
в сторону Нимба не проходят), то Нимб переназначает работника на другую
машину. Демоны Нимба и Надсмотрщика в конструктивном плане тоже являются отказоустойчивыми и не имеют состояния, то есть все состояния хранятся в конфигурационном компоненте под названием ZooKeeper (дословно
«Сторож зоопарка»). Как следствие если демоны Нимба или Надсмотрщика
умирают, то они просто перезапускаются, и работники не страдают от их отказа. Благодаря этому в узле Нимба существует мягкая единая точка отказа,
в том смысле, что без Нимба работники будут продолжать работать, а Надсмотрщики продолжат их перезапускать, если потребуется. Однако в отсутствие Нимба работники не будут автоматически переназначаться на другие
машины, когда это необходимо. Свойство отсутствия состояния у Нимба
и Надсмотрщиков гарантируется сторожевым компонентом ZooKeeper, который координирует, обменивается информацией о конфигурации между
приложениями с помощью надежных технологий синхронизации и хранит
все состояния, связанные с кластером и заданиями.
Обобщая все сказанное, кластер фреймворка Storm должен иметь один
узел Нимб, несколько Надсмотрщиков и один экземпляр сторожа ZooKeeper
на машину, которые используются для координации Нимба и Надсмотрщиков. Весь рабочий поток приложения Storm подчиняется следующим ниже
шагам.
Инструменты программирования на основе рабочих потоков 139
1. Нимб ожидает передачи топологии на обработку.
2. После передачи Нимб собирает все задания топологии и порядок их
исполнения и распределяет задания между всеми работающими Надсмотрщиками.
3. Все Надсмотрщики посылают сигналы Нимбу через регулярные интервалы времени, чтобы подтверждать, что они все еще живы. Любой
отказ (например, Надсмотрщиков или Нимба) не будет влиять на работающее приложение.
4. Когда все задания будут выполнены, Надсмотрщик будет ждать нового
задания от Нимба.
5. Когда все топологии будут обработаны, Нимб будет ожидать передачи
новой топологии от пользователя на обработку.
4.3.2.3. Основы
Типичная программа Storm состоит из трех компонентов:
класса-крана TweetSpout, который реализует интерфейс IRichSpout,
чтобы обеспечивать конкретно-прикладную реализацию метода nextTuple();
класса-задвижки SplitHashtag, который реализует интерфейс IRichBolt,
чтобы обеспечивать конкретно-прикладную реализацию метода exe
cute();
главной функции main, в которой с помощью методов setSpout и setBolt определяются топология и узлы ориентированного ациклического
графа.
Класс-кран содержит следующие ниже методы, которые можно переопределять:
void open(), который вызывается при инициализации задания в клас
тере-работнике;
void nextTuple(), который используется для эмитирования кортежей
в топологию через коллектор. Этот метод должен быть неблокирующим, поэтому если у выпускного крана (то есть объекта Spout) нет эмитируемых кортежей, то он должен вернуться;
void ack() и void fail(), которые вызываются, когда фреймворк Storm
обнаруживает, что эмитированный из выпускного крана кортеж успешно завершен либо его не удается завершить;
void declareOutputFields(), который объявляет схему вывода для всех
потоков топологии.
С другой стороны, класс-задвижка содержит следующие ниже методы:
void prepare(), который предоставляет задвижке (то есть объекту Bolt)
выходной коллектор, используемый для эмитирования кортежей;
void execute(), который получает кортеж с одного из входов задвижки
и применяет к нему логику обработки;
void cleanup(), который вызывается по отключении задвижки и должен
очищать все открытые ресурсы;
140 Инструменты обработки больших данных
void declareOutputFields(), который объявляет схему вывода для всех
потоков топологии.
Объект-коллектор, используемый как выпускным краном, так и поперечной задвижкой, предоставляет API эмитирования кортежей в топологию.
Главное различие между коллектором для интерфейса IRichSpout, а именно
SpoutOutputCollector, и коллектором OutputCollector для интерфейса IRichBolt
заключается в том, что выпускные краны могут помечать сообщения идентификаторами, чтобы их можно было подтверждать либо отклонять.
4.3.2.4. Пример программирования
В предлагаемом примере программирования демонстрируется применение
фреймворка Storm для анализа потока твитов с целью извлечения коли
чества появлений каждого хештега. Предлагаемая топология, состоящая из
одного выпускного крана и двух поперечных задвижек, задана следующим
образом:
TweetSpout является единственным источником данных. Указанный источник подключается к Twitter с помощью его API и передает в топологию поток твитов, сгенерированных в разные временные метки;
SplitHashtag получает кортежи из потока и выполняет предобработку.
В частности, он извлекает хештеги из каждого твита и эмитирует их
в топологию для обработки последующими задвижками;
HashtagCounter выполняет операцию подсчета, используя внутренний
словарь в качестве структуры данных, и выводит на консоль текущее
количество появлений каждого хештега, встречающегося в потоке кортежей.
Twitter предлагает свои API1 в качестве веб-сервиса по извлечению твитов,
публикуемых пользователями в реальном времени. Указанные API доступны
на любом языке программирования, и среди них имеется неофициальная
библиотека Java с открытым исходным кодом под названием twitter4j2, которая предоставляет модуль на базе Java для удобного доступа к API Twitter
Streaming через архитектуру, основанную на слушателях. Для использования
API Twitter Streaming необходимо иметь учетную запись разработчика Twitter; это позволит получать информацию об аутентификации OAuth, то есть
ключа потребителя (ConsumerKey), секрета потребителя (CustomerSecret), токена доступа (AccessToken) и секрета доступа (AccessTokenSecret). Показанный
в листинге 4.9 класс TweetSpout использует API Twitter для извлечения твитов
в реальном времени. Указанный класс наследует у класса BaseRichSpout, который реализует обсуждаемый интерфейс IRichSpout и реализует методы open,
nextTuple и declareOutputFields. В частности, в методе open инициализируются
коллектор и очередь для постановки сообщений в очередь, а также устанавливается вся информация об API Twitter. Очередь привязывается к методу
1
2
См. https://developer.twitter.com/en/docs/twitter-api.
См. https://twitter4j.org/.
Инструменты программирования на основе рабочих потоков 141
onStatus, предоставляемому API Twitter, чтобы опрашивать на наличие новых
твитов в методе nextTuple, который эмитирует их в топологию.
Листинг 4.9 Класс Spout для твитов
public class TweetSpout extends BaseRichSpout {
public static final String consumerKey = "...";
public static final String consumerSecret = "...";
public static final String accessToken = "...";
public static final String accessTokenSecret = "...";
private SpoutOutputCollector collector;
private TwitterStream ts;
private LinkedBlockingQueue queue;
public void open(Map map, TopologyContext topologyContext,
SpoutOutputCollector spoutOutputCollector) {
queue = new LinkedBlockingQueue();
collector = spoutOutputCollector;
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setOAuthConsumerKey(consumerKey)
.setOAuthConsumerSecret(consumerSecret)
.setOAuthAccessToken(accessToken)
.setOAuthAccessTokenSecret(accessTokenSecret);
ts = new TwitterStreamFactory(cb.build()).getInstance();
ts.addListener(new StatusListener() {
public void onStatus(Status status) {
queue.offer(status.getText());
}
public void onDeletionNotice(StatusDeletionNotice
statusDeletionNotice) {}
public void onTrackLimitationNotice(int i) {}
public void onScrubGeo(long l, long l1) {}
public void onStallWarning(StallWarning stallWarn) {}
public void onException(Exception e) {}
});
ts.sample();
}
public void nextTuple() {
Object s = queue .poll();
if(s == null) {
Utils.sleep(50);
} else {
collector.emit(new Values(s));
}
}
public void declareOutputFields(OutputFieldsDeclarer
142 Инструменты обработки больших данных
outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("tweet"));
}
}
Показанная в листинге 4.10 задвижка SplitHashtag извлекает хештеги из
содержимого твита, а затем отправляет каждый хештег в задвижку типа
HashtagCounter. Данный класс наследует у класса BaseRichBolt, который реализует обсуждаемый интерфейс IRichBolt и реализует методы prepare, execute
и declareOutputFields.
Метод execute отвечает за чтение входных кортежей (твитов), идентификацию хештегов и эмитирование их отдельно в виде кортежа. Метод getString
используется для извлечения твита из объекта типа Tuple. С помощью объекта Java StringTokenizer твит разбивается на лексемы, и список лексем твита
прокручивается в цикле с целью отыскания хештега, который распознается
по первому символу "#". Наконец, на входном кортеже вызывается метод ack.
Листинг 4.10 Выпускной кран SplitHashtag
public class SplitHashtag extends BaseRichBolt {
private OutputCollector _collector;
@Override
public void prepare(Map conf, TopologyContext context,
OutputCollector collector) {
_collector = collector;
}
@Override
public void execute(Tuple tuple) {
String tweet = tuple.getString(0);
StringTokenizer st = new StringTokenizer(tweet);
while(st.hasMoreElements()) {
String tmp = (String) st.nextElement();
if(StringUtils.startsWith(tmp, "#")) {
_collector.emit(new Values(tmp));
}
}
_collector.ack(tuple);
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer){
declarer.declare(new Fields("hashtag"));
}
}
Как показано в листинге 4.11, класс HashtagCounter поддерживает экземплярную переменную типа Map<String, Integer>, в которой хранятся все пары
хештег–появление. Указанный класс наследует у BaseRichBolt и реализует
Инструменты программирования на основе рабочих потоков 143
методы prepare, execute и declareOutputFields. Метод prepare инициализирует
коллектор и словарь. Метод execute получает на входе кортеж, содержащий
хештег, и после извлечения хештега из объекта типа Tuple с помощью метода getString делает проверку на присутствие хештега в словаре. Если его
еще нет, то хештег вставляется в качестве ключа со значением 0, а счетчик
увеличивается; в противном случае увеличивается только счетчик. После
этого новая пара хештег–появление вставляется в словарь, а входной кортеж
подтверждается. В этом случае, вместо того чтобы выдавать все пары для
каждого входного кортежа, метод cleanup используется как удобный способ
распечатки окончательных пар хештег–появление по закрытии топологии,
хотя обычно он используется для высвобождения ресурсов, требуемых задвижкой.
Листинг 4.11 Поперечная задвижка HashtagCounter
public class HashtagCounter extends BaseRichBolt {
Map<String, Integer> _counts;
OutputCollector _collector;
@Override
public void prepare(Map conf, TopologyContext context,
OutputCollector collector) {
_counts = new HashMap<String, Integer>();
_collector = collector;
}
@Override
public void execute(Tuple tuple) {
String hashtag = tuple.getString(0);
Integer count = _counts.get(hashtag);
if (count == null)
count = 0;
count++;
_counts.put(hashtag, count);
_collector.ack(tuple);
}
@Override
public void cleanup() {
for(Map.Entry<String, Integer> entry: _counts.entrySet()){
System.out.println(entry.getKey()+":" + entry.getValue());
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer
outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("hashtag"));
}
}
144 Инструменты обработки больших данных
Наконец, описанный в листинге 4.12 класс HahtagCountTopology содержит
метод main. Он используется для создания и инициализации топологии приложения с помощью объекта TopologyBuilder и его методов setBolt и setSpout.
Оба метода определяют новый выпускной кран / поперечную задвижку в топологии и принимают следующие ниже входные параметры:
строковый ИД, который будет использоваться в качестве ссылки другими компонентами топологии;
экземпляр объекта-крана / объекта-задвижки;
значение parallelismHint, указывающее на то, сколько заданий для выпускных кранов / поперечных задвижек существует в топологии одновременно. Если значение не указано, то создается только одно задание
для крана/задвижки.
При задании выпускных кранов и поперечных задвижек в TopologyBuil
der программистам необходимо также указывать механизмы группировки. В данном примере кортежи, передаваемые из TweetSpout в задвижки
SplitHashtag, могут случайно перетасовываться с помощью shuffleGrouping,
тогда как кортежи из задвижек SplitHashtag и HashtagCounter должны группироваться по ключу с помощью fieldsGrouping. Это необходимо для того,
чтобы одинаковые хештеги обрабатывались одинаковым заданием-задвижкой. Наконец, объект Config можно использовать для конфигурирования всех
параметров топологии (например, количество работников, режим отладки)
и передавать его в StormSubmitter для передачи топологии в кластер Storm на
обработку.
Листинг 4.12 Топология
public class HashtagCountTopology {
public static void main(String[] args) throws Exception{
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("spout", new TweetSpout, 3);
builder.setBolt("split", new SplitHashtag(), 3).
shuffleGrouping("spout");
builder.setBolt("count", new HashtagCounter()).
fieldsGrouping("split", new Fields("hashtag"));
Config conf = new Config();
String topologyName = "hashtagCounter";
StormSubmitter.submitTopology(topologyName, conf,
builder.createTopology());
}
}
Инструменты программирования на основе рабочих потоков 145
4.3.3. Apache Airflow
Apache Airflow1 – это фреймворк с открытым исходным кодом, предназначенный для разработки, планирования и мониторинга рабочих потоков.
С его помощью можно создавать приложения по обработке данных в виде
ориентированных ациклических графов заданий. Планировщик Airflow выполняет задания на массиве работников, принимая во внимание зависимости, указанные в графе.
Проект Airflow был начат в октябре 2014 года Максимом Бошемином
(Maxime Beauchemin) из Airbnb и официально анонсирован в июне 2015 года.
Поддерживая открытый исходный код с самого первого релиза, в 2016 году
фреймворк был присоединен к инкубаторской программе фонда Apache Software Foundation, а в 2019 году получил статус проекта высшего уровня.
Ориентированные ациклические графы в Airflow определяются в исходном коде Python. Такой подход имеет целый ряд преимуществ, включая
возможность хранения рабочих потоков в системе контроля версий и отката
к предыдущим версиям, разработку рабочих потоков несколькими людьми
одновременно, а также написание тестов для проверки функциональностей.
Подход «рабочие потоки как исходный код» служит трем главным принципам
фреймворка Airflow:
динамичность: конвейеры Airflow конфигурируются как фрагменты
исходного кода на Python, что позволяет динамически генерировать
конвейеры;
расширяемость: фреймворк Airflow содержит операторы для подключения ко многим технологиям, а все компоненты пригодны для расширения, что позволяет легко их адаптировать к средам пользователей;
гибкость: фреймворк Airflow облегчает параметризацию рабочих потоков за счет использования шаблонизатора Jinja2.
Фреймворк Airflow предлагает высокий уровень абстракции, так как программисты могут легко создавать рабочие потоки, комбинируя набор заданий и указывая зависимости между ними. Задание можно легко определять
как конкретно-прикладную функцию Python либо как экземпляр предопределенного оператора, который обеспечивает низкий уровень многословности за счет предоставления готового к использованию исходного кода
многих распространенных операций (например, BashOperator и MySqlOperator
соответственно для исполнения команд bash и запросов на языке SQL). Среда
исполнения Airflow поддерживает параллелизм данных, когда многочисленные задания параллельно исполняют одинаковый исходный код на разных
кусках данных, и параллелизм заданий, когда разные задания (или операторы) выполняются параллельно.
1
2
См. https://airflow.apache.org/.
См. https://jinja.palletsprojects.com.
146 Инструменты обработки больших данных
4.3.3.1. Архитектура
Типичная установка фреймворка Airflow состоит из следующих ниже компонентов:
планировщика, который управляет как запуском запланированных рабочих потоков, так и отправкой заданий исполнителю;
исполнителя, который управляет исполнением заданий. В дефолтной
установке все выполняется внутри планировщика, тогда как исполнители производственного уровня могут передавать исполнение заданий
набору работников. Под работником обычно понимается процесс, запущенный в узле. Каждый процесс-работник отвечает за исполнение
заданий в рамках исполнения рабочего потока;
веб-сервера, предоставляющего пользовательский интерфейс для проверки, запуска и отладки поведения ориентированных ациклических
графов и заданий;
каталога ориентированных ациклических графов, содержащего файлы
графов, читаемые планировщиком, исполнителем и работниками;
базы метаданных, используемой планировщиком, исполнителем и вебсервером для хранения состояния.
На рис. 4.12 показаны взаимодействия между компонентами фреймворка
Airflow.
База
метаданных
Пользовательский
интерфейс
Веб-сервер
Планировщик
Исполнитель
Каталог ориентированных
ациклических графов
Работник
Работник
Работник
Работник
Рис. 4.12 Архитектура установленного фреймворка Airflow
В зависимости от конкретной конфигурации исполнитель будет включать
в себя и другие компоненты, чтобы взаимодействовать со своими работниками, например с очередью заданий. Однако с высокоуровневой точки зрения
исполнитель и его работники могут рассматриваться как единый логический
компонент.
Инструменты программирования на основе рабочих потоков 147
4.3.3.2. Основы
Ориентированный ациклический граф задает зависимости между заданиями, порядок их исполнения и поведение повторных попыток во фреймворке
Airflow. Сами задания описывают действия, подлежащие исполнению, будь
то доставка данных, проведение анализа, запуск других систем или исполнение других операций. В листинге 4.13 приведен простой пример объявления
ориентированного ациклического графа.
Листинг 4.13 Пример объявления ориентированного ациклического графа
# Объявление ориентированного ациклического графа
with DAG(dag_id="daily_backup", start_date=datetime(2023, 1, 1),
schedule="0 0 ***") as dag:
# Определение четырех заданий, исполняющих команды bash
task_A = BashOperator(task_id="task_A", bash_command="mv /
backup/*.tgz /backup/old")
task_B = BashOperator(task_id="task_B", bash_command="tar
czf /backup/http_log.tgz /var/log/http")
task_C = BashOperator(task_id="task_C", bash_command="tar
czf /backup/mail_log.tgz /var/log/mail")
task_D = BashOperator(task_id="task_D", bash_command="echo
backup completed")
# Определение зависимостей заданий
task_A >> [task_B, task_C]
[task_B, task_C] >> task_D
В листинге 4.13 определен ориентированный ациклический граф с именем
daily_backup, начинающийся с 1 января 2023 года и выполняющийся один раз
в день. Определены четыре задания, каждое из которых выполняет отличающийся скрипт bash. Взаимосвязь между заданиями обозначается символом
>>, чтобы устанавливать зависимость и определять последовательность исполнения заданий. Фреймворк Airflow оценивает этот скрипт и исполняет задания в соответствии с указанным интервалом и установленным порядком.
Во фреймворке Airflow распространены три типа заданий:
операторы: предопределенные задания, которые программисты могут
быстро соединять вместе, чтобы собирать большинство частей ориентированного ациклического графа;
датчики: особый подкласс операторов, специально предназначенный
для единственной цели – ожидания наступления внешнего события;
потоки заданий: конкретно-прикладные функции Python, упакованные
в качестве заданий.
Операторы: оператор – это в концептуальном плане не что иное, как
трафарет предопределенного задания, которое можно определять декларативно внутри ориентированного ациклического графа. Airflow предлагает
обширный набор операторов, некоторые из которых встроены в ядро или
предустановлены. Несколько популярных операторов из ядра таковы:
148 Инструменты обработки больших данных
BashOperator: исполняет команду bash;
PythonOperator: вызывает произвольную функцию Python;
EmailOperator: отправляет электронное письмо.
В листинге 4.14 показан пример использования операторов. В данном
примере периодически делается запрос к конечной HTTP-точке на получение информации о текущей погоде в месте, идентифицированном по географическим координатам, соответствующим городу Козенца, Италия. Эта
операция выполняется с помощью SimpleHttpOperator, который делает запрос
методом GET к указанной конечной точке, используя заданный строковый
литерал запроса. Затем PythonOperator получает HTTP-ответ, полученный
оператором SimpleHttpOperator, и правильно его форматирует, чтобы создать
тело сообщения, которое будет вставлено в письмо, отправляемое оператором emailOperator.
Листинг 4.14 Пример операторов
endpoint = "https://api.open-meteo.com/v1/forecast"
latitude = 39.30
longitude = 16.25
parameters = ["temperature_2m_max","temperature_2m_min",
"precipitation_sum","sunrise","sunset",
"windspeed_10m_max","winddirection_10m_dominant"]
timezone = "Europe/Berlin"
today = pendulum.now().strftime("%Y-%m-%d")
weather_query = f"{endpoint}?latitude={latitude}&longitude={longitude}&daily={','.join(
parameters)}&timezone={timezone}&start_date={today}&end_date={today}"
def build_body(**context):
query_result = context['ti'].xcom_pull('submit_query')
weather_info = json.loads(query_result)
daily_info = weather_info["daily"].items()
units = weather_info["daily_units"].values()
list_info = [f"{k}:{v[0]} {unit}" for (k,v),unit in zip(
daily_info, units)]
body_mail = ", ".join(list_info)
return body_mail
with DAG(dag_id="weather_mail",
start_date=datetime(2023, 1, 1),
schedule="0 0 ***") as dag_weather:
submit_query = SimpleHttpOperator(task_id="submit_query",
http_conn_id='', endpoint=weather_query, method="GET",
headers={})
prepare_email = PythonOperator(task_id='prepare_email',
python_callable=build_body, dag=dag_weather)
send_email = EmailOperator(task_id="send_email",
to="user@example.com", subject="Weather today in Cosenza",
html_content="{{ti.xcom_pull('prepare_email')}}")
submit_query >> prepare_email >> send_email
Инструменты программирования на основе рабочих потоков 149
Стоит отметить, что в этом примере, в отличие от листинга 4.13, задания
должны общаться, так как заданию send_email нужен результат prepare_email,
который, в свою очередь, обрабатывает результат запроса, выполняемого
оператором submit_query. Коммуникация внутри фреймворка Airflow опирается на механизм XCom, который позволяет обмениваться сообщениями
или небольшими объемами данных между заданиями. Объекты XCom хранятся в базе метаданных Airflow и содержат автоматически генерируемый
ключ (который при необходимости можно адаптировать), значение и другую информацию, например идентификаторы заданий и ориентированного
ациклического графа. Внутри механизма XCom задействуется формат JSON;
поэтому он нуждается в том, чтобы значения были пригодными для сериализации в формате JSON. Объекты XCom могут явно помещаться в базу метаданных (xcom_push) и извлекаться из нее (xcom_ pull) экземпляром задания ti или
автоматически сохраняться, когда задание возвращает результат на выходе.
Датчики: датчики – это особый тип операторов, предназначенных для
ожидания наступления какого-либо события. Они могут быть основаны на
времени, ожидать файла или внешнего события. Их единственная функция –
ждать до тех пор, пока что-то не произойдет, а когда это происходит, они
успешно завершают свою работу, позволяя исполнять свои нижестоящие
задания.
Поскольку датчики в основном простаивают, у них есть два разных режима
работы, что позволяет повышать эффективность их использования:
зондирование (poke): датчик занимает место работника на все время
своей работы;
перепланирование (reschedule): датчик занимает место работника только во время проверки и спит в течение заданного времени между проверками.
Режимы зондирования и перепланирования можно конфигурировать непосредственно при создании экземпляра датчика. В целом компромисс между ними заключается в задержке: операция, проверяющая каждую секунду,
должна работать в режиме зондирования, тогда как операция, проверяющая
каждую минуту, должна работать в режиме перепланирования.
Поток заданий: API TaskFlow упрощает процесс создания хорошо организованных графов с помощью декоратора @task программистам, которые пишут большинство своих ориентированных ациклических графов, используя
обычный исходный код Python, не полагаясь на операторы. В листинге 4.15
приведен простой пример1, демонстрирующий использование API Taskflow.
Листинг 4.15 Пример, демонстрирующий использование API TaskFlow
from airflow.decorators import task
from airflow.operators.email import EmailOperator
@task
1
См. https://airflow.apache.org/docs/apache-airflow/2.1.1/concepts/taskflow.html.
150 Инструменты обработки больших данных
def get_ip():
return my_ip_service.get_main_ip()
@task
def prepare_email(external_ip):
return {
'subject':f'Server connected from {external_ip}',
'body': f'Your server is connected from the external IP {external_ip}<br>'
}
email_info = prepare_email(get_ip())
EmailOperator(
task_id='send_email',
to='example@example.com',
subject=email_info['subject'],
html_content=email_info['body']
)
В этом примере есть три задания: get_ip, prepare_email и send_email. Первые два объявлены с помощью API TaskFlow и автоматически передают значение, возвращаемое из задания get_ip, в задание prepare_email, не только
устанавливая соединение через XCom, но и автоматически объявляя, что
задание prepare_email находится ниже по течению от задания get_ip. Задание send_email представлено более традиционным оператором, но оно все
же может пользоваться значением, возвращаемым из задания prepare_email,
для установки своих параметров и снова автоматически определяет, что оно
должно находиться ниже по течению от задания prepare_email.
4.3.3.3. Пример программирования
В этом разделе будет продемонстрировано применение API TaskFlow фреймворка Airflow для реализации приложения по ансамблевому обучению. Ансамблевое обучение призвано повышать точность классифицирования путем
агрегирования предсказаний нескольких классификаторов. Среди разных
способов, с помощью которых можно получать ансамблевую модель, здесь
мы воспользуемся техникой голосования. В частности, ансамблевый метод
строится путем подгонки набора базовых классификаторов к тренировочным данным, которые выполняют работу по классифицированию на тестовых экземплярах методом голосования за или против предсказаний, сделанных каждым классификатором. Этот процесс классифицирования, как
правило, придает ансамблевому классификатору более высокую точность,
чем точность любого входящего в него базового классификатора. На рис. 4.13
показана схема ансамблевого обучения для классифицирования данных.
Входной набор данных разбивается с помощью разделителя на тренировочный и тестовый наборы.
Тренировочный набор подается на вход n алгоритмам классифицирования, которые работают параллельно, чтобы построить n независимых классификационных моделей.
Инструменты программирования на основе рабочих потоков 151
Затем инструмент-голосователь выполняет ансамблевое классифицирование, назначая каждому экземпляру тестового набора класс, предсказанный большинством из n моделей, сгенерированных на предыдущем шаге. По этой причине n обычно задается в виде нечетного числа,
чтобы избегать равенства голосов.
Набор данных
Разделитель
Тренировочный набор
Классификатор 1
...
Классификатор N
Модель 1
...
Модель N
Тестовый
набор
Голосователь
Отклассифицированный
тестовый набор
Рис. 4.13 Приложение по ансамблевому обучению
В листинге 4.16 показана реализация описанного выше приложения по
ансамблевому обучению во фреймворке Airflow, в котором генерируются
пять классификационных моделей.
Листинг 4.16 Ансамблевое обучение в Airflow
# создать экземпляр ориентированного ациклического графа
@dag(
schedule=None,
start_date=pendulum.now(),
catchup=False,
tags=["example"],
)
def ensemble_taskflow():
# загрузить и разбить набор данных на тренировочный и тестовый наборы
@task(multiple_outputs=True)
def partition():
X,y = load_dataset(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=13
152 Инструменты обработки больших данных
)
train_data = (X_train.tolist(), y_train.tolist())
test_data = (X_test.tolist(), y_test.tolist())
return {"train": train_data, "test": test_data}
# выполнить подгонку заданных классификационных моделей к тренировочным данным
@task
def train(model:sklearn.base.BaseEstimator, train_data:tuple):
X_train, y_train = train_data
# выполнить подгонку модели и сериализовать ее
model.fit(X_train, y_train)
model_bytes = pickle.dumps(model)
model_str = model_bytes.decode("latin1")
return model_str
# выполнить ансамблевое классифицирование на тестовых данных
@task
def vote(test_data:tuple, models:list):
X_test, y_test = test_data
pred_sum = np.array([0]*len(X_test))
for model_str in models:
# десериализовать модель и предсказать
model_bytes = model_str.encode("latin1")
model = pickle.loads(model_bytes)
pred_sum += model.predict(X_test)
# предсказание устанавливается равным классу большинства
n_models = len(models)
threshold = np.ceil(n_models/2)
preds = [int(s>=threshold) for s in pred_sum]
print(f"Accuracy is: {accuracy_score(y_test, preds):.2f}")
### главный поток ###
# загрузить и разбить набор данных
partitioned_dataset = partition()
train_data = partitioned_dataset["train"]
test_data = partitioned_dataset["test"]
# натренировать параллельно 5 независимых классификаторов
m1 = train(GaussianNB(), train_data)
m2 = train(LogisticRegression(), train_data)
m3 = train(DecisionTreeClassifier(), train_data)
m4 = train(SVC(), train_data)
m5 = train(KNeighborsClassifier(), train_data)
# вычислить точность голосования на тестовых данных
vote(test_data, [m1, m2, m3, m4, m5])
# запустить ориентированный ациклический граф
ensemble_taskflow()
Сначала необходимо создать экземпляр ориентированного ациклического
графа, в котором реализуется рабочий поток ансамблевого обучения. Для
этого используется декоратор @dag, чтобы превратить функцию ensemble_taskflow() в генератор графа. Указанная аннотация также позволяет задавать
несколько опций, таких как теги и расписания для запрограммированных
Инструменты программирования на основе рабочих потоков 153
исполнений. Рабочий поток состоит из следующих ниже заданий, то есть
функций Python, декорированных аннотацией @task:
load: загружает набор данных UCI Breast Cancer1 с помощью функции
load_breast_cancer из модуля sklearn.datasets. Данные и цели возвращаются в кортеж, который будет закодирован в формате JSON и сохранен
в XCom;
partition: получает от задания load загруженный в нем набор данных
и разбивает его на тренировочный и тестовый наборы. После этого
она возвращает разделенные данные в виде словаря. Стоит отметить,
что в этом случае в декораторе задания используется опция multiple_outputs=True, чтобы установить ключи словаря как ключи XCom. На
практике полезно работать с выдаваемым из задания объектом XCom
как со словарем, содержащим конкретно-прикладные ключи (подробнее об этом позже);
train: получает тренировочные данные и экземпляр оценщика sklearn
и выполняет операцию подгонки. На этом шаге должна быть возвращена подогнанная модель, но результат работы оценщиков sklearn не
подходит для непосредственной сериализации в формат JSON, который требуется в XCom. С целью преодоления этого затруднения модель сначала сериализуется в последовательность байтов с помощью
библиотеки pickle, затем она декодируется в сериализуемый строковый
литерал JSON, который возвращается в качестве результата;
vote: это задание получает тестовые данные и список подогнанных
моделей. Сначала оно десериализует входные модели, используя их для
вычисления списка предсказаний. Затем предсказания агрегируются
голосованием, чтобы получить ансамблевую классификацию. В частности, поскольку мы рассматривали задачу двоичного классифицирования с использованием n базовых классификаторов, где n – это нечетное
число, окончательные предсказания вычисляются следующим образом.
n
Обозначим через S t = ∑ i=1 pit сумму предсказаний всех классификаторов
на тестовой выборке t. Тогда ансамблевое предсказание для тестовой
выборки t будет равно 1, если S t ≥ ⌈n/2⌉, либо 0 в противном случае.
После определения всех заданий главный поток объявляется путем компоновки заданий в соответствии со схемой, представленной на рис. 4.13.
В частности, было использовано пять разных классификационных моделей,
предлагаемых библиотекой sklearn: гауссов наивный Байес, логистическая
регрессия, дерево решений, машина опорных векторов и классификатор
на основе k-ближайших соседей. Для каждой модели создается тренировочное задание, которое выполняется параллельно. Обратите внимание,
что, как объясняется в определении задания по разбиению, опция multiple_outputs=True в декораторе задания позволяет извлекать тренировочные
и тестовые данные из объекта XCom, выдаваемого на выходе из задания,
1
См. https://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+(Diagnostic).
154 Инструменты обработки больших данных
так как связанные с ним ключи устанавливаются как ключи XCom. Наконец,
вызовом ансамблевой функции taskflow() вызывается ориентированный
ациклический граф.
4.4. Инструменты программирования
на основе передачи сообщений
В этом разделе в качестве примера инструмента программирования, поддерживающего парадигму передачи сообщений, рассматривается фреймворк на
основе интерфейса передачи сообщений (MPI). Некоторые языки конкурентного программирования, такие как Smalltalk, Occam, Erlang, Scala, Rust и Go,
включают механизмы передачи сообщений с целью обеспечения сотрудничества процессов. Между тем в системах параллельных и распределенных
вычислений наиболее широкое применение в реализации параллельных
и распределенных приложений на основе сообщений нашел фреймворк MPI.
Существует несколько реализаций MPI с открытым исходным кодом, таких
как MPICH, LAM/MPI и Open MPI, которые в основном используются в таких
языках, как Java, C и C++, Fortran и Python.
4.4.1. Интерфейс передачи сообщений
MPI – это стандарт передачи сообщений, определяемый с 1992 года форумом
MPI1, в который входят многие промышленные и академические организации. MPI широко используется научными исследователями, инженерамиизыскателями и промышленностью для разработки параллельных и распределенных приложений в высокопроизводительных инфраструктурах.
Фреймворк MPI включает в себя передачу сообщений «точка–точка», коллективные коммуникации, механизмы групп и коммуникаторов, топологии
процессов, управление средой, создание процессов и управление ими, односторонние коммуникации, расширенные коллективные операции, внешние
интерфейсы и операции ввода-вывода. Определены языковые привязки к C,
Java, Fortran, Python и R. Хотя сообщество пользователей MPI и имеет средний размер, данный проект привлекает массу участников.
Первая версия MPI-1, выпущенная в мае 1994 года, предлагала богатую
коллекцию примитивов обмена сообщениями, основанную на наборе из
восьми базовых функций, которые позволяют полностью выражать параллельные программы и другие 129 продвинутых функций. Параллельная программа MPI-1 состоит из набора похожих процессов, выполняющихся на
разных процессорах и использующих функции MPI для передачи сообщений
1
См. www.mpi-forum.org.
Инструменты программирования на основе передачи сообщений 155
(рис. 4.14). В последних версиях появились примитивы для инициализации,
создания и управления процессами. Последняя версия, MPI-4.1, содержит
долговечные коллективы, разделенные коммуникации и новые механизмы
обработки ошибок.
Узел 1
Узел 2
Узел N
Процесс MPI
Процесс MPI
Процесс MPI
Язык
программирования
Язык
программирования
Язык
программирования
Библиотека MPI
Библиотека MPI
Библиотека MPI
Операционная
система
Операционная
система
Операционная
система
Рис. 4.14 Уровни программной архитектуры MPI
4.4.1.1. Основы
Согласно модели Одна программа, несколько элементов данных (SPMD),
фреймворк MPI предназначен для задействования параллелизма данных, так
как все процессы MPI, составляющие параллельную программу, выполняют
одинаковый исходный код на разных элементах данных. Примерами коммуникационных примитивов MPI «точка–точка» являются:
MPI_Send(buf, leng, type, rank, tag, comm);
MPI_Recv(buf, leng, type, source, tag, comm, status).
Вызовы примитивов MPI_Send и MPI_Recv функционируют следующим образом. Процесс P1 упаковывает данные, чтобы отправить их в буфер процессу
P2. Затем буфер направляется в нужное место. Местонахождение сообщения
определяется рангом процесса. Процесс P2 должен подтвердить, что он хочет
получить сообщение процесса P1, исполнив функцию получения. После этого
происходит передача данных. Процесс P1 получает подтверждение, что данные переданы, и может исполнить очередную операцию.
Параметр leng содержит количество элементов в буфере отправки, параметр type задает тип данных каждого элемента буфера отправки, параметр
rank задает процесс-адресат, а параметр source задает процесс-отправитель.
Параметр tag используется для разграничения сообщений, в случае если
процесс P1 отправляет процессу P2 несколько частей данных. Параметр comm
задает используемый коммуникатор. Параметр status в примитиве MPI_Recv
предоставляет информацию о полученном сообщении.
Коммуникатор задает коммуникационный контекст для операции передачи сообщений. Каждый коммуникационный контекст предоставляет так
156 Инструменты обработки больших данных
называемый «коммуникационный универсум», состоящий из набора процессов, которые коллективно используют этот коммуникационный контекст.
Сообщения всегда принимаются в том контексте, в котором они были отправлены, и сообщения, отправленные в разных контекстах, не мешают друг
другу. По умолчанию MPI предоставляет коммуникатор MPI_COMM_WORLD. Он
позволяет общаться со всеми процессами, которые доступны после инициализации MPI, и процессы идентифицируются по их рангам (номерам)
в группе коммуникатора MPI_COMM_WORLD.
В представленном в листинге 4.17 примере программирования на языке С
показано простое использование коммуникации «точка–точка». В данном
примере процесс с идентификатором 0 (myrank = 0) отправляет пакет данных
(строковый литерал) процессу с идентификатором 1 с помощью операции
отправки MPI_Send. Местоположение, длина и тип буфера отправки задаются
первыми тремя параметрами операции MPI_Send. Отправляемое сообщение
будет содержать 31 символ. В дополнение к этому операция отправки ассоциирует конверт с пакетом. Он определяет место назначения пакета и содержит информацию, которая может быть использована операцией получения
для выбора конкретного сообщения. Последние три параметра операции
MPI_Send вместе с рангом отправителя задают конверт для отправляемого
пакета. В частности, в качестве тега этого сообщения выбрано число 99. Процесс 1 (myrank = 1) получает текст с помощью операции получения MPI_Recv.
Получаемый пакет выбирается в соответствии со значением его конверта,
а текст сохраняется в буфере получения, который будет содержать строковое
сообщение в памяти процесса 1. Первые три параметра операции получения
задают местоположение, длину и тип буфера получения. Следующие три параметра, включающие тег, используются для выбора входящего сообщения.
Последний параметр используется для возврата информации о полученном
сообщении. Обратите внимание, что MPI_Comm_rank определяет ранг (то есть
ИД) вызывающего процесса в коммуникаторе, а MPI_Init и MPI_Finalize используются соответственно для инициализации и завершения среды исполнения MPI.
Листинг 4.17 Простой обмен сообщениями в MPI посредством коммуникации
«точка–точка»
#include "mpi.h"
int main(int argc, char *argv[])
{
char packet[50];
int myrank;
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
if (myrank == 0) /* инструкции процесса с ИД 0 */
{
strcpy(packet,"Hello, this is my first message");
MPI_Send(packet, strlen(packet)+1, MPI_CHAR, 1, 99,
Инструменты программирования на основе передачи сообщений 157
MPI_COMM_WORLD);
}
else
if (myrank == 1) /* инструкции процесса с ИД 1 */
{
MPI_Recv(packet, 50, MPI_CHAR, 0, 99, MPI_COMM_WORLD, &
status);
printf("I received this packet:%s:\n", packet);
}
MPI.Finalize();
}
Фреймворк MPI также предоставляет операцию отправки-получения, которая комбинирует в одной операции отправку сообщения в одно место назначения и получение еще одного сообщения от еще одного процесса. Эти
два процесса (отправитель и получатель) могут быть одинаковыми.
MPI_SENDRECV(sendbuf, sendleng, sendtype, rank, sendtag, recvbuf, recvleng, recvtype,
source, recvtag, comm, status);
Операция отправки-получения на практике применяется для реализации
вызовов дистанционных процедур (RPC) и для исполнения сдвиговых операций в цепочке процессов.
Коллективная коммуникация в MPI реализуется с помощью следующих
ниже примитивов:
MPI_Bcast (inbuf, incnt, intype, root, comm);
MPI_Scatter (inbuf, incnt, intype, inbuf, incnt, intype, root, comm);
MPI_Gather (outbuf, outcnt, outype, inbuf, incnt, intype, root, comm);
MPI_Reduce (inbuf, outbuf, count, type, op, root, comm).
Примитив MPI_Bcast реализует широковещательную коммуникацию, которая позволяет одному процессу отправлять одинаковые данные всем
процессам в коммуникаторе. В то время как MPI_Bcast отправляет одинаковые данные всем процессам, примитив MPI_Scatter отправляет куски массива данных разным процессам. Примитив MPI_Gather является обратной
стороной примитива MPI_Scatter. Вместо того чтобы распределять элементы из одного процесса по многим процессам, примитив MPI_Gather берет
элементы из многочисленных процессов и собирает их в один-единственный процесс. Наконец, подобно примитиву MPI_Gather, примитив MPI_Reduce
принимает массив входных элементов от каждого процесса и возвращает
массив выходных элементов корневому процессу. Выходные элементы содержат редуцированный результат. MPI также содержит функцию, предназначенную для синхронизации процессов: MPI_Barrier(MPI_Comm comm).
Данная функция реализует барьер, и ни один процесс в коммуникаторе не
способен пройти указанный барьер до тех пор, пока все они не вызовут эту
функцию.
Фреймворк MPI обеспечивает низкий уровень абстракции для разработки эффективных и переносимых итеративных параллельных приложений,
158 Инструменты обработки больших данных
даже если производительность может быть ограничена задержкой коммуникации между процессорами. Программисты, работающие с MPI, не имеют никакой возможности воспользоваться какими-то высокоуровневыми
конструкциями и должны вручную решать сложные вопросы распределенного программирования, такие как обмен данными, распределение данных между процессорами, синхронизация и тупики. Эти трудности усложняют отладку приложения и программирование заданий в параллельных
приложениях, ориентированных на конечных пользователей, в которых
для упрощения решаемых разработчиком заданий требуются языки более
высокого уровня. Тем не менее благодаря эффективности, вытекающей
из низкоуровневой модели программирования, MPI широко используется
и реализован на очень большом количестве параллельных и последовательных архитектур, таких как системы массово-параллельной обработки,
сети рабочих станций, кластеры и решетки. Как уже упоминалось, в MPI-1
не было предусмотрено создание процессов, и оно было введено позднее
в версии MPI-2 (Geist и соавт., 1996) посредством функции MPI_Comm_spawn,
порождающей несколько одинаковых процессов, и функции MPI_Comm_spawn_
multiple, порождающей несколько процессов, или один и тот же процесс
с несколькими наборами аргументов. Текущая версия MPI-4 содержит расширения с целью улучшения поддержки гибридных моделей программирования и отказоустойчивости.
4.4.1.2. Пример программирования
Далее будет рассмотрено приложение MPI, написанное на Java, для параллельного подсчета символов посредством набора процессов, читающих из
текстового файла. Исходный код примера написан на Java + Open MPI1. Синтаксис используемых здесь операций MPI соответствует формату синтаксиса
Java. В частности, при наличии входного файла размером M байт и N процессов каждый процесс читает кусок размером байт и подсчитывает каждый
символ в приватной структуре данных. Процесс-мастер с рангом, равным 0,
получает частичные подсчеты от всех процессов в группе с заданным тегом
и их агрегирует, как показано на рис. 4.15.
В листинге 4.18 показан исходный код приложения. В нем используются
следующие примитивы MPI: (i) Init и Finalize соответственно для инициализации и завершения работы программы; (ii) bcast для передачи сообщений
от мастера всем процессам; и (iii) send и recv для коммуникации «точка–
точка» между мастером и другими процессами. Для работы приложения его
исходный код должен быть скомпилирован командой mpijavac и исполнен
командой mpirun -N, где N – это количество процессов.
1
См. https://www.open-mpi.org/.
Инструменты программирования на основе передачи сообщений 159
M байт
M/N байт Ранг
0
a: n0[0]
b: n0[1]
…
z: n0[25]
M/N байт Ранг
1
a: n1[0]
b: n1[1]
…
z: n1[25]
M/N байт Ранг
N–1
a: nN–1[0]
b: nN–1[1]
…
z: nN–1[25]
Ранг
0
a: n0[0] + n1[0] + nN–1[0]
b: n0[1] + n1[1] + nN–1[1]
…
z: n0[25] + n1[25] + nN–1[25]
Рис. 4.15 Архитектура предлагаемого приложения MPI
Листинг 4.18 Параллельный подсчет количества символов в MPI
static public void main(String[] args) throws MPIException,
IOException {
int tag = 42;
// Инициализировать массив сообщений для каждого процесса
// и массив результатов для мастера
int[] partial_counter = new int[26];
int[] res = new int[26];
int[] split_size = new int[1];
String fileName = args[0];
MPI.Init(args);
Comm comm = MPI.COMM_WORLD;
int rank = comm.getRank();
int master_rank = 0;
int ntasks = comm.getSize();
if (rank == master_rank){ // Сообщение вычисляет размер раздела
Path path = Paths.get(fileName);
long bytes = Files.size(path);
split_size[0] = (int) (bytes / ntasks);
}
// Размер раздела широковещательно транслируется всем процессам
comm.bcast(split_size, split_size.length, MPI.INT, 0);
int size = split_size[0];
byte[] readBytes = new byte[size];
try (InputStream inputStream = new FileInputStream(
fileName)) {
// Каждый процесс определяет кусок, в котором предстоит выполнить работу
int start = rank * size;
inputStream.skip(start);
inputStream.read(readBytes, 0, size);
// Подсчитать и сохранить все символы куска
for (byte b : readBytes) {
160 Инструменты обработки больших данных
char c=(char) b;
if (Character.isLetter(c)){
int index = (int) Character.toLowerCase(c) (int) 'a';
partial_counter[index] += 1;
}
}
// Каждый процесс сообщает частичный результат подсчета мастеру
comm.send(partial_counter, partial_counter.length,
MPI.INT, master_rank, tag);
}
if (rank == master_rank) {
// Мастер агрегирует частичные результаты
for (int i = 0; i < ntasks; i++) {
Status status = comm.recv(partial_counter,
partial_counter.length, MPI.INT, MPI.
ANY_SOURCE, tag);
for (int j = 0; j < partial_counter.length; j++)
res[j] += partial_counter[j];
}
}
MPI.Finalize();
}
При запуске программы исполняется только процесс-мастер. Вслед за
методом MPI.Init внутри процесса-мастера создается N – 1 дополнительных
процессов, чтобы достичь количества параллельных процессов, N, заданного
в команде mpirun. С целью идентификации каждого процесса в библиотеке
MPI используется целочисленный идентификатор, именуемый рангом, который для процесса-мастера равен 0 и увеличивается при каждом создании
нового процесса. Благодаря этому мастер может проверять условие rank ==
master_rank, чтобы выполнить следующие две операции: (i) определить размер куска для каждого процесса и (ii) агрегировать частичные количества
символов, полученные процессами. Общение осуществляется с помощью
дефолтного коммуникатора (то есть MPI_COMM_WORLD), который группирует
все процессы, чтобы обеспечивать обмен сообщениями. Затем каждый процесс, включая мастер, продолжает выполнять несовпадающие версии программы. В частности, получив от мастера широковещательное сообщение
о размере раздела, процессы читают назначенный кусок данных, подсчитывают количество появлений каждого символа и сохраняют результат в приватной структуре (partial_counter). Наконец, каждый процесс отправляет
результаты частичного счетчика мастеру, чтобы вычислить окончательный
результат.
Инструменты программирования на основе массового синхронного параллелизма 161
4.5. Инструменты программирования
на основе массового синхронного
параллелизма
На основе модели массового синхронного параллелизма (BSP) разработано
несколько фреймворков и библиотек, которые применяются для обработки
крупных наборов данных, в частности для эффективных графовых вычислений. Многие из них являются реализацией модели Pregel (Malewicz и соавт.,
2010), предложенной Google в 2010 году и представляющей собой абстракцию
обмена сообщениями в рамках массового синхронного параллелизма, призванной выражать графопараллельные итеративные алгоритмы. Среди них
вычислительные фреймворки с открытым исходным кодом Apache Hama
и Giraph, построенные поверх фреймворка Apache Hadoop.
Они предназначены для масштабирования крупных наборов данных
в распределенной среде. Apache Flink предлагает встроенную поддержку
итеративного пакетного программирования в рамках массового синхронного параллелизма в API Dataset, а также предоставляет вершинно-центричный графовый API, а именно Gelly, в котором задействуются эффективные
итерационные операторы Flink для поддержки крупномасштабных итеративных графовых процессов. Apache Spark тоже предлагает свою собственную оптимизированную версию системы графовой обработки Google Pregel
в рамках библиотеки GraphX. Она основана на графовом высокоуровневом
расширении API-интерфейсов Spark RDD, которое позволяет программистам
задействовать примитивы графовых вычислений и распределенные операции с параллельностью данных на RDD-наборах данных Spark. Далее будет
приведено подробное описание данной библиотеки и показано, как ее применять для разработки эффективных приложений с параллелизмом графов.
4.5.1. Spark GraphX
Граф – это структура данных, состоящая из множества вершин, также именуе
мых узлами, которые соединены ребрами. Граф может быть (i) ориентированным, если его ребра имеют определенную ориентацию; (ii) циклическим,
если существует хотя бы один замкнутый путь; либо (iii) взвешенным, если
каждому ребру назначен вес, который может представлять произвольное
свойство. Графы хорошо подходят для представления нелинейных взаимо
связей между объектами, что привело к их применению в различных прикладных областях, начиная от анализа сетевого взаимодействия и соци-
162 Инструменты обработки больших данных
альных сетей и заканчивая поисковыми и рекомендательными системами,
используемыми крупными компаниями, включая Amazon, Netflix и Google.
Хотя высокая гибкость и выразительность графов превращают их в мощную абстракцию, усиливающую аналитику данных, инструменты и фреймворки параллельной обработки данных, такие как Hadoop или Spark, не очень
хорошо подходят для работы с графами. И действительно, несмотря на то
что они предназначены для высокомасштабируемой и отказоустойчивой
обработки данных, в них используются абстракции данных, не учитывающие графовую структуру, что приводит к чрезмерному перемещению данных и снижению производительности. Это влечет за собой необходимость
в особенных технических решениях, специально разработанных для эффективных вычислений с параллельностью графов. Среди них библиотека
Spark GraphX предлагает постоянно растущий набор графовых операторов,
включая операторы свойств и структурные операторы, которые можно использовать для выполнения операций преобразования, создания подграфов,
изменения ориентации ребер в ориентированном графе на обратную или
вычисления маскированной версии входного графа. В комплект поставки
также входят некоторые базовые запросы на основе графов и алгоритмы
анализа графов, такие как определение связных компонент, ранга PageRank и подсчет количества треугольников. В дополнение к этому библиотека
GraphX предоставляет оптимизированную версию системы графовой обработки Google Pregel, предложенной Google в 2010 году (Malewicz и соавт.,
2010) и предназначенной для выражения графопараллельных итеративных
алгоритмов. Оператор Pregel представляет собой абстракцию обмена сообщениями в рамках массового синхронного параллелизма (BSP), выполняющую серию супершагов, в которых вершина получает агрегацию входящих
сообщений с предыдущего супершага, вычисляет новое значение для своего
свойства в соответствии со специфичной для задания логикой и, наконец,
отправляет сообщения соседним вершинам на следующем супершаге.
4.5.1.1. Графовая абстракция
На высоком уровне библиотека GraphX расширяет RDD-наборы Spark, вводя
новую графовую абстракцию – ориентированный мультиграф, свойства которого привязаны к каждой вершине и ребру. В частности, граф содержит два
RDD-набора данных: один для ребер, другой для вершин. Эта абстракция обес
печивает единый интерфейс для представления данных с учетом базисной
графовой структуры, сохраняя при этом эффективность RDD-наборов Spark.
Она и впрямь позволяет программистам одновременно использовать графовые концепции, эффективные примитивы графовых вычислений, а также
распределенные операции с параллелизмом данных, характерным для Spark.
Библиотека GraphX также обеспечивает дополнительное представление
о базисной графовой структуре посредством концепции реберной тройки,
EdgeTriplet, которая расширяет информацию, предоставляемую реберным
RDD-набором данных, добавляя свойства исходной и конечной вершин.
Инструменты программирования на основе массового синхронного параллелизма 163
В частности, реберный RDD-набор данных содержит только свойство, прикрепленное к каждому конкретному ребру, а также идентификаторы исходной и конечной вершин, то есть набор кортежей (src_id, dest_id, attr).
Свойства исходной и конечной вершин можно найти в вершинном RDD-на
боре данных, который содержит кортеж (vertex_id, attr) по каждой вершине
графа. Таким образом, реберная тройка непосредственно предоставляет полную информацию о двух взаимосвязанных вершинах вместе с атрибутами,
привязанными к ним и к ребру, которое их соединяет, то есть кортеж (src_id,
dest_id, src_attr, edge_attr, dst_attr). Это представление легко вычисляется, начиная с реберного и вершинного RDD-наборов данных, как показано
в листинге 4.19.
Листинг 4.19 Выведение представления EdgeTriplet
SELECT src.id, dst.id, src.attr, e.attr, dst.attr
FROM edges AS e LEFT JOIN vertices AS src, vertices AS dest
ON e.srcId = src.Id AND e.dstId = dst.Id
Библиотека GraphX предлагает несколько вариантов создания графа из
набора вершин и ребер в RDD. В частности, граф может быть создан из RDDнаборов, содержащих вершины и ребра, с помощью метода Graph.apply либо
непосредственно из реберного RDD-набора с помощью Graph.fromEdges.
Метод Graph.fromEdgeTuples позволяет создавать графы из кортежей ребер,
то есть из RDD-набора, содержащего пары вершин. Библиотека GraphX также
поддерживает дедупликацию ребер и обрабатывает отсутствующие атрибуты
при построении графов посредством дефолтных значений.
В целях обеспечения разработки масштабируемых графопараллельных
распределенных приложений генерируемые в GraphX графы разбиваются на
разделы. В частности, с целью снижения затрат на коммуникацию и хранение данных применяется вершинный подход к разбиению графов. Данный
процесс состоит в разбиении графа вдоль его вершин, что соответствует привязке ребер к машинам и расширению вершин на несколько машин.
Оператор Graph.partitionBy позволяет пользователям задавать конкретную
логику привязки ребер, выбирая из множества алгоритмов разбиения, таких
как 2D-разбиение или другие предлагаемые в GraphX эвристики.
4.5.1.2. API Pregel
В общем случае характеристики вершин графа рекурсивно зависят от характеристик их соседей, что по существу превращает графы в рекурсивные
структуры данных. В связи с этим многие важные графовые алгоритмы итеративно вычисляют характеристики вершин до тех пор, пока не будет достигнута фиксированная точка или условие схождения. Эти итерационные
алгоритмы были выражены с использованием самых разных графопараллельных абстракций. Среди них библиотека GraphX предлагает вариант API
Pregel.
164 Инструменты обработки больших данных
Оператор Pregel – это абстракция передачи сообщений в рамках массового
синхронного параллелизма, в которой вычисления состоят из последовательности супершагов. Во время супершага фреймворк вызывает пользовательскую функцию (UDF, от англ. user-defined function) для каждой вершины;
данная функция задает ее поведение и исполняется параллельно. Каждая
вершина может изменять свой статус и читать сообщения, отправленные на
предыдущем супершаге, или отправлять новые, которые будут доставлены на
следующем супершаге. Коммуникация обычно происходит между вершиной
и ее окрестностью, хотя теоретически сообщение может отправляться любой вершине с идентификатором, известным отправителю. В предлагаемом
библиотекой GraphX API Pregel топология графа служит ограничением на
вычисления, и, как и в стандартном Pregel, характеристики вершин и ребер
пересчитываются итеративно до тех пор, пока не будет достигнуто схождение. Тем не менее есть некоторые отличия, которые позволяют существенно
повышать эффективность распределенных графовых вычислений. В частности, в отличие от Pregel, библиотека GraphX выполняет вычисление сообщений параллельно как функцию реберной тройки и имеет доступ к свойствам вершин-отправителей и вершин-получателей. В дополнение к этому
вершины могут посылать сообщения только своим соседям, и те, которые не
получили сообщения в течение супершага, пропускаются. В конце концов,
когда обрабатываемых сообщений больше нет, оператор Pregel прекращает
итерации цикла и возвращает окончательное состояние графа.
4.5.1.3. Основы
Библиотека GraphX предлагает богатый набор графовых функций и свойств,
которые способны эффективно помогать программистам в сборке эффективных и лаконичных приложений с параллельностью графов. Имея входной
граф, можно легко получить полезную информацию, например количество
ребер (val numEdges: Long) и вершин (val numVertices: Long). Также можно получить информацию о степенях в виде:
val inDegrees: VertexRDD[Int];
val outDegrees: VertexRDD[Int];
val Degrees: VertexRDD[Int].
Как уже говорилось в предыдущем разделе, графовая абстракция предоставляет единый интерфейс для работы с графом, задействуя как топологическую информацию, так и эффективность представления на основе RDDнаборов данных. По этой причине он может представляться как коллекция
вершин, ребер и троек:
val vertices: VertexRDD[VD];
val edges: EdgeRDD[ED];
val triplets: RDD[EdgeTriplet[VD, ED].
GraphX также предлагает несколько функций-отображений для преобразования атрибутов, связанных с вершинами, ребрами и тройками, путем
указания пользовательской функции преобразования:
Инструменты программирования на основе массового синхронного параллелизма 165
def mapVertices[VD2](map: (VertexId, VD) => VD2): Graph[VD2, ED];
def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2];
def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2].
Среди множества других предлагаемых библиотекой GraphX полезных
функций стоит упомянуть операцию соединения, которая может использоваться для соединения данных из внешних RDD-наборов данных с графом.
В частности, имея входной RDD-набор, оператор joinVertices соединяет вершины графа, которые совпадают с вершинами, содержащимися во входном
RDD-наборе. Затем возвращается новый граф, в котором обновленные значения соединенных вершин вычисляются путем применения пользовательской
функции преобразования, а несовпадающие вершины графа сохраняют свои
изначальные значения.
def joinVertices[U](table: RDD[(VertexId, U)])(mapFunc: (VertexId,
VD, U) => VD): Graph[VD, ED]
Более общей версией этой функции является функция outerJoinVertices,
которая применяет заданную пользователем функцию преобразования ко
всем вершинам графа. В этом случае совпадающие вершины обновляются
как обычно, а вершины, у которых нет совпадающего значения во входном
RDD-наборе, получают тип Option.
def outerJoinVertices[U, VD2](other: RDD[(VertexId, U)])(mapFunc: (
VertexId, VD, Option[U]) => VD2): Graph[VD2, ED]
Опциональные значения широко используются в ситуациях, когда значение может быть задано, а может быть и не задано. В частности, в языке программирования Scala Option[T] ведет себя как контейнер для нуля или одного
элемента типа T, поэтому он может быть либо объектом типа Some[T], либо
объектом типа None, который представляет значение, которое отсутствует.
При извлечении значения, связанного с опциональным типом, дефолтное
значение, которое будет использоваться, если значение опции не будет установлено, указывается с помощью метода getOrElse(x), где x – это дефолтное
значение.
В дополнение к описанным функциям библиотека GraphX предлагает
реализацию вершинно-центрированной модели вычислений Pregel, призванной обеспечивать реализацию высокопараллельных крупномасштабных
приложений по обработке графов. Программист может использовать API
Pregel-подобного массового синхронного параллелизма (BSP) посредством
следующей ниже функции.
def pregel[A]
(initialMsg: A,
maxIter: Int = Int.MaxValue,
activeDir: EdgeDirection = EdgeDirection.Out)
(vprog: (VertexId, VD, A) => VD,
sendMsg: EdgeTriplet[VD, ED] => Iterator[(VertexId, A)],
mergeMsg: (A, A) => A): Graph[VD, ED]
166 Инструменты обработки больших данных
Указанная функция принимает два набора входных параметров: первый
задает входной граф, начальное сообщение (заданного типа A), количество
итераций и ориентацию ребер; второй ожидает три пользовательские функции, как указано ниже:
vprog: (VertexId, VD, A) => VD: кодирует поведение вершины. Эта функция вызывается на каждой вершине, получившей сообщение, и вычисляет обновленное значение вершины;
sendMsg: EdgeTriplet[VD, ED] => Iterator[(VertexId, A)]: эта функция применяется к исходящим ребрам вершин, получивших сообщения в текущей итерации;
mergeMsg: (A, A) => A: определяет, как два полученные вершиной сообщения должны объединяться в одно сообщение того же типа. Реализация
данной функции должна быть коммутативной и ассоциативной.
По окончании вычислений на выходе возвращается результирующий граф.
4.5.1.4. Пример программирования
В этом разделе приводится пример использования оператора Pregel в действии, с демонстрацией его применения для реализации знаменитого алгоритма PageRank, который используется поисковой системой Google для
ранжирования страниц веб-сайтов. Этот алгоритм был впервые представлен
в 1998 году Лоуренсом Пейджем (Lawrence Page) и Сергеем Брином (Sergey
Brin) в их статье о первоначальном прототипе поисковой системы Google
(Brin и Page, 1998). Основная идея алгоритма PageRank заключается в том,
что более важные веб-сайты, скорее всего, будут получать больше ссылок
с других веб-сайтов. Таким образом, он призван оценивать степень важности
того или иного веб-сайта путем определения качества ссылок, указывающих на этот веб-сайт. Точнее говоря, PageRank моделирует процесс, который приводит пользователя на заданную страницу. Обычно пользователь
попадает на эту страницу через последовательность случайных ссылок, то
есть следуя по пути, соединяющему несколько страниц. Однако в конечном
итоге пользователь может перестать кликать по исходящим ссылкам и совершить случайный переход, отыскав другой URL-адрес, до которого нельзя
добраться с текущей страницы напрямую. Вероятность того, что пользователь продолжит переходить по исходящим ссылкам, является ослабевающим
коэффициентом, обычно равным d = 0,85, тогда как вероятность случайного
перехода равна 1 – d = 0,15. Формально этот процесс выражается следующим
ниже уравнением:
Ранг PageRank страницы pi представляет собой вероятность того, что пользователь, находящийся на странице pi, попадет на страницу pi. Он задается
суммой двух вероятностей:
Инструменты программирования на основе массового синхронного параллелизма 167
учитывая, что вероятность того, что пользователь перестанет кликать
по ссылкам, присутствующим на pi, и совершит случайный переход,
равна 1 – d, и исходя из допущения, что вероятность попасть на каждую
из N доступных страниц одинаково распределена, этот член выражает
вероятность попасть на страницу pi посредством случайного перехода;
учитывая d – вероятность того, что пользователь перейдет по исходящей ссылке, M(pi) – множество страниц, ссылающихся на страницу pi,
и L(pi) – количество ссылок на каждой странице pi ∈ M(pi), этот член дает
вероятность того, что пользователь попадет на страницу pi по ссылке,
существующей на странице pi, исходя из допущения, что вероятность
перехода пользователя по каждой ссылке на странице pi одинаково
распределена.
В листинге 4.20 показана реализация алгоритма PageRank с использованием API Pregel, предоставляемого библиотекой GraphX.
Листинг 4.20 Реализация алгоритма PageRank в GraphX с использованием
API Pregel
object Pagerank {
// Создать сеанс Spark
val spark = SparkSession
.builder
.master("local")
.appName("Spark-GraphX-PageRank")
.getOrCreate()
val sc: SparkContext = spark.sparkContext
// Собрать пример графа
val vertices: RDD[(VertexId, Double)] = sc.parallelize(Seq(1, 11)
.map(x => (x.asInstanceOf[Long], x.asInstanceOf[Double])))
val edges: RDD[Edge[PartitionID]] = sc.parallelize(Seq(Edge(2L, 3L, 1),
Edge(3L, 2L, 1), Edge(4L, 2L, 1), Edge(4L, 1L, 1),
Edge(5L, 4L, 1), Edge(5L, 6L, 1), Edge(5L, 2L, 1), Edge(6L, 5L, 1),
Edge(6L, 2L, 1), Edge(7L, 2L, 1), Edge(7L, 5L, 1),
Edge(8L, 2L, 1), Edge(8L, 5L, 1), Edge(9L, 2L, 1), Edge(9L, 5L, 1),
Edge(10L, 5L, 1), Edge(11L, 5L, 1)))
val graph: Graph[Double, PartitionID] = Graph(vertices, edges)
val numVertices = graph.numVertices
val startGraph: Graph[Double, Double] = graph
// Привязать степень с каждой вершиной
.outerJoinVertices(graph.outDegrees) {
(vid, vdata, deg) => deg.getOrElse(0)
}
// Установить вес в ребрах на основе исходящей степени
.mapTriplets(e => 1.0 / e.srcAttr)
// Установить атрибуты вершины величине первоначальной догадки
.mapVertices((id, attr) => 1)
// Задать коэффициент ослабевания
168 Инструменты обработки больших данных
val d = 0.85
// Определить пользовательские функции Pregel для PageRank
def vertexProgram(id: VertexId, attr: Double, msgSum: Double):
Double = (1-d)/numVertices + d * msgSum
def sendMessage(edge: EdgeTriplet[Double, Double]): Iterator[(
VertexId, Double)] = Iterator((edge.dstId, edge.srcAttr * edge.attr))
def messageCombiner(a: Double, b: Double): Double = a+b
def main(args: Array[String]) = {
// Исполнить Pregel фиксированное число итераций.
val finalGraph = Pregel(graph = startGraph, initialMsg = 0.0,
maxIterations = 50)(vprog = vertexProgram, sendMsg =
sendMessage, mergeMsg = messageCombiner)
// Нормализовать до единичной суммы (равной 1)
// (требуется для работы со строковыми узлами)
val rankSum = finalGraph.vertices.values.sum()
val rankGraph = finalGraph.mapVertices((id, rank) => rank / rankSum)
// Показать верхние 5 узлов, упорядоченных по уменьшающемуся рангу PageRank
rankGraph.vertices.top(5)(Ordering.by(_._2)).foreach(println)
}
}
Исходный код начинается с создания объекта SparkSession, передав типичные параметры, такие как мастер или имя приложения. Затем из сеанса
создается контекст SparkContext. Далее создается пример графа, который состоит из двух RDD-наборов, содержащих ребра и вершины. Затем этот граф
подготавливается к вычислению ранга PageRank путем установки веса каждого ребра, равного исходящей степени вершины-источника, и установки
начального значения ранга PageRank каждой вершины, равного начальной
догадке 1.0. После этого устанавливается коэффициент ослабевания; он равен d = 0,85, и определяются три пользовательские функции. В частности,
(i) функция vertexProgram реализует вышеупомянутую формулу PageRank,
принимая на входе сумму нормализованных распределений PageRank соседних страниц; (ii) функция sendMessage кодирует, как страница делится своим
рангом PageRank поровну между своими внешними соседями; (iii) функция messageCombiner реализует сумму вкладов, которая будет использоваться
в программе vertexProgram для обновления ранга страницы, получающей эти
вклады. Наконец, показана главная функция, в которой оператор Pregel исполняется в течение 50 итераций, а после вычисления в рамках массового
синхронного параллелизма выводятся верхние пять узлов по рангу PageRank.
Обратите внимание, что для того, чтобы справиться с наличием стоковых
узлов, результат на выходе из оператора Pregel дополнительно ренормализуется, дабы обеспечить вероятностное распределение среди всех имеющихся
страниц.
Результаты предложенного примера представлены на рис. 4.16, на котором
каждый кружок представляет собой страницу, аннотированную вычислен-
Инструменты SQL-подобного программирования 169
ным рангом PageRank. Вершины показаны разными градациями зеленого
цвета в соответствии с окончательно вычисленным рангом PageRank.
3,3 %
38,4 %
34,3 %
3,9 %
3,9 %
1,6 %
8,1 %
1,6 %
1,6 %
1,6 %
1,6 %
Рис. 4.16 Результат работы алгоритма PageRank
в предлагаемом примере
4.6. Инструменты SQL-подобного
программирования
В этом разделе будет рассмотрено несколько фреймворков с поддержкой
SQL-подобной модели программирования. Эти системы пытаются скомбинировать присутствующие во фреймворке Hadoop эффективность и способности выполнять запросы с простотой использования SQL-подобного языка,
чтобы обеспечить разработку простых и эффективных приложений по анализу данных.
Система складирования данных Apache Hive1, построенная на базе Hadoop
для чтения, записи и управления данными в крупномасштабных инфраструктурах, является одной из наиболее часто используемых систем в этом
контексте. Еще один фреймворк на базе Hadoop под названием Apache Pig2
использует SQL-подобный язык для исполнения приложений по обработке
потоков данных в крупномасштабных инфраструктурах.
От них немного отличается массивно-параллельный механизм запросов
Apache Impala (Kornacker и соавт., 2015), который работает в средах обработки
1
2
См. https://hive.apache.org/.
См. https://pig.apache.org/.
170 Инструменты обработки больших данных
данных Hadoop, обеспечивая низкую задержку и высокий параллелизм для
аналитических запросов в Hadoop, предлагая при этом опыт работы, похожий на работу с реляционной СУБД.
Далее будут подробно рассмотрены основы Apache Hive и Apache Pig как
ярких представителей инструментов SQL-подобного программирования.
4.6.1. Apache Hive
Hive – это система складирования данных на базе фреймворка Hadoop, позволяющая пользователям писать запросы на SQL-подобном декларативном
языке, именуемом HiveQL, которые затем компилируются в вычислительные
заявки MapReduce и выполняются в Hadoop. Ее можно рассматривать как
механизм SQL, способный автоматически компилировать SQL-запросы в набор заявок MapReduce, которые выполняются в кластере Hadoop, с дополнительными функциональностями по управлению данными и метаданными.
Причины разработки такой системы основываются на том, что хотя модель
MapReduce и является очень гибкой парадигмой программирования, она
слишком низкоуровневая для рутинных заданий по анализу данных. Решение использовать язык SQL в системе Hive в качестве языка более высокого
уровня было продиктовано стремлением облегчить аналитикам и исследователям данных переход от традиционных реляционных СУБД к распределенной обработке данных в Hadoop, используя знакомый и широко распространенный язык SQL. Система Hive поддерживает практически бóльшую часть
стандарта языка SQL, а также несколько расширений, призванных упрощать
взаимодействие с базисной платформой Hadoop. Например, системы управления базами данных на основе SQL организовывают данные в таблицы,
которые затем делятся на типизированные столбцы. В системе Hive используется аналогичный подход, но, в отличие от традиционных СУБД, в ней
не требуется определять структуру таблиц перед импортом данных. Вместо
этого она позволяет проецировать табличную структуру на базисные данные
прямо во время исполнения запроса. Эта способность называется «схемой
прямо во время чтения», то есть данные проверяются на соответствие схеме
при исполнении запроса к ним. Система Hive также позволяет исполнять
одинаковый запрос на разных участках данных, поддерживая параллелизм
данных. Более того, она обеспечивает высокий уровень абстракции, поскольку
программист может разрабатывать приложение по обработке данных с помощью языка HiveQL, опираясь на традиционные концепции реляционных
СУБД.
Сегодня система Apache Hive широко используется аналитиками для выполнения запросов к данным и генерирования отчетов на крупных наборах
данных. Она поддерживается большим сообществом пользователей и используется несколькими крупными компаниями, такими как Facebook, Netflix, Yahoo! и Airbnb. Например, Netflix использует систему Hive для выполнения импровизированных запросов и аналитики.
Инструменты SQL-подобного программирования 171
4.6.1.1. Главные концепции
Система Hive предлагает полный набор операций языка определения данных
(DDL) и языка манипулирования данными (DML). Она поддерживает такие
важные функциональности, как создание, изменение, просмотр и удаление
таблиц. Кроме того, она облегчает загрузку, вставку, обновление, удаление
и слияние данных в файловой системе. Эти способности делают систему
Hive универсальным вариантом, который может заменить существующие
инфраструктуры хранилищ данных, выступая в качестве адаптера между
платформой Hadoop и большой экосистемой инструментов анализа данных,
построенных на основе реляционных СУБД (например, приложения по извлечению, преобразованию и загрузке данных (ETL) и по работе с управленческой аналитикой). Однако важно отметить, что система Hive специально
разработана для онлайновой аналитической обработки (OLAP), а не для онлайновой обработки транзакций (OLTP). Кроме того, в отличие от реляционных СУБД, таких как SQL Server, система Hive не обеспечивает реально-временной доступ к данным.
Используемые в языке HiveQL абстракции опираются на традиционные
понятия реляционных СУБД (например, таблица, строка, столбец). В дополнение к этому система Hive предоставляет три разных типа функций для
манипулирования данными: пользовательские функции (UDF), пользовательские агрегатные функции (UDAF, от англ. user-defined aggregate function)
и пользовательские функции генерации таблиц (UDTF, от англ. user-defined
table-generating function). Просто пользовательские функции оперируют на
одной строке данных и выдают на выходе одну строку, например length, round
и factorial. Пользовательские агрегатные функции оперируют на нескольких
строках и выдают на выходе одну строку, например sum, average и count. Наконец, пользовательские функции генерации таблиц оперируют на одиночных
строках и выдают на выходе несколько строк, например explode, posexplode
и inline. Такие функции позволяют легко писать собственные конкретноприкладные функции на разных языках программирования, например на
Java или Python.
4.6.1.2. Архитектура
Архитектура системы Hive состоит из следующих ниже компонентов, также
показанных на рис. 4.17 (Huai и соавт., 2014):
пользовательский интерфейс (UI): служит точкой входа для пользователей, которые могут отправлять запросы и выполнять другие операции
в системе через веб-интерфейс или интерфейс командной строки (CLI);
драйвер: этот компонент получает запросы и реализует дескрипторы
сеанса, предоставляя API исполнения и доставки, смоделированные на
основе интерфейсов JDBC/ODBC;
компилятор: отвечает за синтактико-структурный разбор запросов,
выполнение семантического анализа выражений запросов и гене-
172 Инструменты обработки больших данных
рацию плана исполнения. В частности, он преобразовывает запрос
в абстрактное синтаксическое дерево (AST), а затем, после проверки на
ошибки компиляции, абстрактное синтаксическое дерево преобразовывается в ориентированный ациклический граф. Компилятор задействует метаданные таблиц и разделов, полученные из метахранилища;
метахранилище: этот компонент использует реляционную СУБД для
хранения структурной информации о различных таблицах и разделах
в хранилище, например метаданных о долговременных реляционных
сущностях и о том, как они отображаются в распределенную файловую систему Hadoop (HDFS). Эта информация включает также сведения
о столбцах, типах столбцов, сериализаторах и десериализаторах для
чтения и записи данных, а еще о соответствующих местоположениях
файлов HDFS;
механизм исполнения: выполняет план исполнения, сгенерированный
компилятором в виде ориентированного ациклического графа. Механизм исполнения управляет зависимостями между стадиями графа
и исполняет их на соответствующих компонентах системы;
распределенная файловая система Hadoop (HDFS): это базисная распределенная файловая система, используемая для хранения данных
в Hadoop.
Apache Hive
Apache Hadoop
Драйвер
MapReduce
Запросы Hive
Компилятор
Механизм
исполнения
HDFS
Метахранилище
Рис. 4.17 Архитектура системы Apache Hive
Типичный поток исполнения запроса на языке HiveQL в архитектуре системы Hive выглядит следующим образом. Пользовательский интерфейс
инициирует исполнительный интерфейс к драйверу, который создает для запроса дескриптор сеанса и отправляет его компилятору, чтобы тот сгенерировал план исполнения. Компилятор получает необходимые метаданные из
метахранилища, используя их для проверки типов выражений и применения
набора оптимизаций к абстрактному синтаксическому дереву (например,
Инструменты SQL-подобного программирования 173
обрезку разделов на основе предикатов запроса). Затем дерево операторов
преобразовывается в ориентированный ациклический граф, состоящий из
нескольких вычислительных заявок MapReduce, которые передаются на обработку базисному механизму MapReduce. Когда пользователи записывают
файлы в HDFS, применяется библиотека SerDe сериализации-десериализации данных, которая сериализует и десериализует данные в заданном файловом формате. После завершения всех вычислительных заявок MapReduce
драйвер возвращает результаты запроса пользователю.
4.6.1.3. Основы
Как уже говорилось, система Hive поддерживает распространенные операции
языка определения данных и языка манипулирования данными посредством
языка HiveQL. Этот раздел посвящен вопросу о том, как с помощью языка,
предложенного системой Hive, выражаются главные операции.
Инструкции HiveQL для языка определения данных: язык определения
данных предлагает синтаксис для создания и изменения объектов базы данных, таких как таблицы и индексы. Например, чтобы создать таблицу на языке HiveQL, программисты могут использовать следующую ниже инструкцию:
CREATE [REMOTE] (SCHEMA|DATABASE) [IF NOT EXISTS] database_name
[LOCATION hdfs_path] [ROW FORMAT row_format] [FIELDS TERMINATED BY char];
Как говорилось в разделе, посвященном архитектурным аспектам, система Hive хранит схемы таблиц в метахранилище, которое используется для
хранения всей информации о таблицах и разделах. Дефолтным метахранилищем является реляционная СУБД Apache Derby1, которую можно задать
явным образом с помощью опции SCHEMA (или эквивалентной DATABASE).
Используя аналогичный синтаксис, система Hive позволяет пользователю
изменять и удалять строки или удалять всю таблицу. Другие распространенные инструкции языка определения данных подробно описаны в табл. 4.3.
Таблица 4.3. Распространенные инструкции языка определения данных,
используемые в HiveQL
Инструкция DDL
SHOW
ALTER
DESCRIBE
TRUNCATE
DELETE
Смысл
Показать базы данных, таблицы и свойства
Изменить записи в существующей таблице
Описать столбцы таблицы
Удалить строки таблицы
Удалить данные таблицы
Инструкции HiveQL для языка манипулирования данными: язык манипулирования данными предлагает синтаксис, используемый для вставки,
удаления и обновления данных в базе данных. Например, для загрузки дан1
См. https://db.apache.org/derby/.
174 Инструменты обработки больших данных
ных в таблицу с помощью языка HiveQL программисты могут использовать
следующую ниже инструкцию:
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE
tablename [PARTITION (partcol1=val1, partcol2=val2,"...");]
При импорте данных в таблицы система Hive не выполняет никаких преобразований, тогда как операции загрузки – это просто операции копирования/
перемещения, которые перемещают данные в места расположения таблиц
Hive. Также стоит отметить, что результаты запроса могут вставляться в таб
лицы с помощью синтаксиса, аналогичного предыдущему. Опция OVERWRITE
перезапишет все существующие данные в таблице или разделе.
INSERT OVERWRITE TABLE tablename [IF NOT EXISTS]
select_statement FROM from_statement;
Другими способами изменения данных в Hive являются операции UPDATE
и DELETE, которые можно использовать с помощью следующих ниже инструкций:
UPDATE tablename SET column = value [, column = value "..."]
[WHERE expression];
DELETE FROM tablename [WHERE expression];
4.6.1.4. Пример программирования
В предлагаемом приложении демонстрируется применение системы Hive
для хранения данных о пользовательских рейтингах коллекции фильмов
и выполнения запросов к этим данным с помощью языка HiveQL.
В качестве первого шага нужно создать таблицу, в которой будут храниться
данные. Это показано в листинге 4.21, где таблица создается путем указания
ее схемы, то есть четырех столбцов с именами userid, movieid, rating и timestamp.
Листинг 4.21 Создание таблицы Hive
CREATE TABLE data (
userid INT,
movieid INT,
rating INT,
timestamp DATE)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t';
После создания таблицы из текстового файла, хранящегося в локальной
файловой системе (см. листинг 4.22), извлекаются данные, перезаписывая
все содержимое таблицы. Стоит отметить, что если убрать ключевое слово
LOCAL, то Hive будет искать файл в HDFS.
Инструменты SQL-подобного программирования 175
Листинг 4.22 Загрузка данных в таблицу Hive
LOAD DATA LOCAL INPATH '<path>/data'
OVERWRITE INTO TABLE data;
После этого, как показано в листинге 4.23, можно выполнить некоторые
статистические аналитические расчеты, например подсчитать количество
строк (то есть количество рейтинговых оценок, высказанных пользователями) в таблице с помощью встроенной функции COUNT.
Листинг 4.23 Подсчет строк
SELECT COUNT(*)
FROM data;
Используя традиционные ключевые слова SQL, такие как SELECT, FROM, GROUP
BY и ORDER BY, можно выполнить несколько простых запросов, например найти
самые высокорейтинговые фильмы (см. листинг 4.24).
Листинг 4.24 Самые высокорейтинговые фильмы
SELECT movieid, COUNT(rating) AS num_ratings
FROM data
GROUP BY movieid
ORDER BY num_ratings DESC
Более того, система Hive позволяет пользователям выполнять более сложный анализ данных с использованием других языков программирования.
Например, можно определить скрипт на языке Python с именем week_mapper.
py, который будет выполнять шаги по обработке строк, как показано в лис
тинге 4.25. В частности, этот скрипт соотносит временную метку каждого отзыва на фильм с неделей года ее создания и агрегирует результаты. Функция
isocalendar() возвращает тройку с (год, неделя, день); поэтому isocalendar()
[1] возвращает номер недели.
Листинг 4.25 Скрипт на языке Python
import sys
import datetime
for line in sys.stdin:
line = line.strip()
userid, movieid, rating, timestamp = line.split('\t')
week = datetime.datetime.fromtimestamp(float(timestamp)).
isocalendar()[1]
print '\t'.join([userid, movieid, rating, str(week)])
Приведенный в листинге 4.25 скрипт Python можно легко включить в инст
рукции HiveQL и использовать в качестве функции, как показано в листинге 4.26. В частности, с помощью модификатора TRANSFORM пользователи могут
176 Инструменты обработки больших данных
вставлять свои собственные конкретно-прикладные функции в поток данных, чтобы исполнять конкретно-прикладной скрипт. По умолчанию перед
отправкой в пользовательский скрипт столбцы преобразовываются в строки
и разграничиваются знаком табуляции. Именно по этой причине в листинге 4.25 строка разделяется табуляцией, а затем новая строка снова собирается
с использованием того же знака. Наконец, выполняется запрос COUNT, группируя новые данные по неделям года.
Листинг 4.26 Запрос Hive
add FILE week_mapper.py;
INSERT OVERWRITE TABLE data_new
SELECT TRANSFORM
(userid, movieid, rating, timestamp) USING
'python week_mapper.py'
AS (userid, movieid, rating, week)
FROM data;
SELECT week, COUNT(*)
FROM data_new
GROUP BY week;
4.6.2. Apache Pig
Apache Pig – это высокоуровневый фреймворк на основе модели рабочих потоков, служащий для исполнения программ MapReduce в Hadoop с помощью
языка Pig Latin. В 2008 году, когда была выпущена первая версия фреймворка Pig, успех интернет-компаний стал в значительной степени зависеть от
их способности анализировать огромные объемы собираемых ежедневно
данных, в результате чего специальная аналитика стала приобретать все
большее значение. Несмотря на существование параллельных СУБД, они
оставались слишком дорогими, что привело к широкому распространению
процедурных моделей программирования и систем для анализа данных,
таких как MapReduce на базе фреймворка Hadoop. Несмотря на широкое
распространение, модель программирования MapReduce характеризовалась
низким уровнем абстракции, нуждалась в узкоспециализированных программистах и затрудняла техническое сопровождение и реиспользование. Для
решения этих проблем был предложен пакет Pig, призванный преодолеть
разрыв между высокоуровневыми декларативными запросами на языке SQL
и низкоуровневым процедурным стилем модели программирования Map
Reduce (Gates и соавт., 2009). Как и во фреймворке Hive (см. раздел 4.6.1),
запросы во фреймворке Pig пишутся на специальном языке под названием
Pig Latin и затем конвертируются в планы исполнения, которые выполняются
в Hadoop как вычислительные заявки MapReduce.
Система программирования Pig позволяет составлять высокоуровневые
операции манипулирования данными, используя SQL-подобный стиль (на-
Инструменты SQL-подобного программирования 177
пример, операции параллельного программирования, такие как FOREACH,
FLATTEN и COGROUP), сохраняя при этом главные характеристики, типы данных
и рабочие нагрузки MapReduce. В дополнение к этому фреймворк Pig задействует систему исполнения множественных запросов, чтобы одновременно
обрабатывать весь скрипт или пакет запросов целиком. Таким образом, он
поддерживает как параллелизм данных, который используется путем разбие
ния данных на куски и их параллельной обработки, так и параллелизм заданий, когда несколько запросов исполняются параллельно на одних и тех же
данных.
По этим причинам фреймворк Pig широко используется для разработки
запросов к данным, простого анализа данных и приложений по извлечению,
преобразованию и загрузке данных (ETL), собирающих данные из нескольких источников, таких как потоки, HDFS или файлы. Среди компаний и организаций, использующих фреймворк Pig в своей работе, можно отметить
такие, как LinkedIn, PayPal и Mendeley. Благодаря скриптовому языку Pig
Latin фреймворк Pig обеспечивает средний уровень абстракции, то есть по
сравнению с другими системами, такими как Hadoop, от разработчиков во
фреймворке Pig не требуется писать сложные и длинные исходные тексты.
4.6.2.1. Главные понятия
Модель данных: фреймворк Pig предоставляет вложенную модель данных,
которая позволяет работать со сложными и ненормализованными данными
(Olston и соавт., 2008b). Он поддерживает скалярные типы, такие как int, long,
double, chararray (то есть строковый тип) и bytearray. Более того, он предоставляет три сложные модели данных, а именно map, tuple и bag:
map – это ассоциативный массив, или словарь, в котором ключом является строковый литерал, а значение может быть любого типа;
tuple – это упорядоченный список элементов данных, также именуемых полями, в котором каждое поле – это порция данных. Элементы
кортежа могут быть любого типа, что позволяет создавать сложные
вложенные типы;
bag – это пакет, или коллекция кортежей, подобная принятой в реляционной СУБД. Таким образом, кортежи в пакете bag соответствуют
строкам в таблице, хотя, в отличие от реляционной таблицы, пакеты
Pig не нуждаются в том, чтобы каждый кортеж содержал одинаковое
количество полей. Пакет также обозначается как отношение.
В скрипте Pig может использоваться большой набор инструкций с целью
облегчения разработки распространенных заданий на данных, таких как
LOAD, FILTER, JOIN и SORT. Скрипты Pig могут вызываться приложениями, написанными не только на языке Pig Latin, но и на многих других языках программирования (например, Java, Python и JavaScript), а в них могут задействоваться конкретно-прикладные пользовательские функции для проведения
расширенной аналитики.
178 Инструменты обработки больших данных
Оптимизация запросов: каждый скрипт Pig транслируется в набор вычислительных заявок MapReduce, которые автоматически оптимизируются
механизмом Pig с помощью нескольких встроенных правил оптимизации,
таких как устранение неиспользуемых инструкций или применение фильт
ров при загрузке данных. Оптимизация потока данных может быть логической либо физической (Olston и соавт., 2008a).
Логические оптимизации изменяют заданный пользователем логический
граф потока данных, генерируя новый граф, который семантически эквивалентен изначальному, но может оцениваться более эффективно. И наоборот,
физические оптимизации касаются того, как логический граф потока данных
транслируется в физический план исполнения, например в серию вычислительных заявок MapReduce. Фреймворк Pig создает логический план для
каждого заданного пользователем пакета. Когда логический план построен,
никакой обработки не происходит – она начинается только тогда, когда пользователь вызывает команду STORE на пакете. В этот момент логический план
для этого пакета преобразовывается в физический план, который затем исполняется. Такое ленивое исполнение имеет свои выгоды, так как допускает
резидентную конвейеризацию и другие оптимизации, такие как переупорядочивание фильтров на многих командах языка Pig Latin.
4.6.2.2. Архитектура
С точки зрения архитектуры, фреймворк Pig состоит из четырех главных
компонентов, как показано на рис. 4.18:
парсера, который обрабатывает все инструкции языка Pig Latin, проверяя синтаксис и типы данных на наличие ошибок. На выходе он производит ориентированный ациклический граф, в котором логические
операторы скриптов представлены в виде узлов, а потоки данных –
в виде ребер;
Apache Pig
Apache Hadoop
Парсер
MapReduce
Скрипты
Pig Latin
Оптимизатор
Компилятор
HDFS
Механизм
исполнения
Рис. 4.18 Архитектура фреймворка Apache Pig
Инструменты SQL-подобного программирования 179
оптимизатора, который применяет на сгенерированном парсером
графе оптимизационные операции по повышению скорости запросов,
такие как разделение, слияние, проекция, вытеснение/отодвигание,
преобразование и переупорядочение. Например, вытеснение и проекция опускают ненужные данные или столбцы, уменьшая объем обрабатываемых данных;
компилятора, который генерирует последовательность вычислительных заявок MapReduce, начиная с результата на выходе из оптимизатора. Этот процесс включает в себя и другие оптимизации, например
перестановку порядка исполнения;
механизма исполнения, который исполняет сгенерированные компилятором вычислительные заявки MapReduce в среде исполнения Hadoop.
Выходные данные могут быть показаны на экране с помощью команды
DUMP либо сохранены в HDFS посредством функции STORE.
4.6.2.3. Основы
Как уже говорилось в подразделе 4.6.2 «Главные концепции», инструкции
языка Pig Latin выражаются с помощью пакетов. Пакет – это коллекция кортежей (то есть упорядоченных наборов полей), которые могут создаваться
с помощью нативных типов данных, поддерживаемых фреймворком Pig, как
простых (например, int, long, float, double, chararray и boolean), так и сложных
(то есть кортежей, словарей или вложенных пакетов), либо путем загрузки
данных из файловой системы. Важно отметить, что отношения обозначаются
именем (или псевдонимом), которое назначается в инструкции языка Pig
Latin. Пример такой инструкции, которая загружает данные о студентах (то
есть имена и возраст) из файла, показан ниже:
A = LOAD 'file' USING PigStorage() AS (name:chararray, age:int);
Далее перечислены другие распространенные инструкции языка Pig Latin,
выражающие реляционные операторы (обратите внимание, что кортеж заключен в круглые скобки ( ), пакет заключен в фигурные скобки { }, а словарь – в квадратные скобки [ ]):
FILTER, которая выбирает кортежи из отношения на основе некоторого
условия. Ее синтаксис таков:
alias = FILTER alias BY expression;
JOIN (внутренняя либо внешняя), которая выполняет внутреннее/внешнее соединение двух или более отношений на основе значений общих
полей. Ее синтаксис выглядит следующим образом:
alias = JOIN alias | left-alias BY { expression };
FOREACH, которая генерирует преобразования на основе столбцов данных. Ее синтаксис выглядит следующим образом:
alias = FOREACH { block | nested_block };
180 Инструменты обработки больших данных
Обычно инструкция FOREACH используется в сочетании с модификатором GENERATE, который позволяет работать со столбцами данных (вместо
инструкции FILTER, которая работает со строками). Ее синтаксис выглядит следующим образом:
X = FOREACH A GENERATE f1;
STORE, которая сохраняет или записывает результаты в файловую систему. Ее синтаксис выглядит следующим образом:
STORE alias INTO 'directory' [USING function];
Более того, поскольку фреймворк Pig поддерживает определение пользовательских функций программистом, он позволяет регистрировать JAR-файл
для использования в скрипте и назначать псевдонимы пользовательским
функциям.
REGISTER path;
DEFINE alias {function | ['command' [input] [output] [stderr]]};
4.6.2.4. Пример программирования
Рассматриваемое здесь приложение реализует анализатор настроений на
основе словаря с помощью фреймворка Pig. Имея словарь слов, связанных
с положительным или отрицательным настроением, настроение текста (например, высказывания, отзыва, твита или комментария) вычисляется путем
суммирования баллов положительных и отрицательных слов в тексте и вычисления среднего балла, как показано на рис. 4.19.
Предложения
с выражением мнения
или отзыва
Баллы
предложений
Пакет слов
Лексемизация, очистка,
выделение основ
Словарь мнений
Рис. 4.19 Структура приложения по проведению анализа настроений
Поскольку фреймворк Pig не предоставляет встроенной библиотеки для
анализа настроений, он задействует внешние словари, чтобы связывать слова с их настроениями и определять семантическую направленность слов
мнения (Kumar и Sebastian, 2012; Belcastro и соавт., 2020).
Как уже говорилось, разработчики могут включать продвинутую аналитику в скрипт посредством определения пользовательских функций. Например,
пользовательская функция PROCESS в листинге 4.27 призвана обрабатывать
кортеж путем удаления знаков препинания в качестве шага предобработки.
При необходимости в метод exec, реализованный на языке Java, можно добавлять и другие функциональности.
Инструменты SQL-подобного программирования 181
Листинг 4.27 Обработка пользовательской функции в Java
public class Processing extends EvalFunc<String> {
@Override
public String exec(Tuple tuple) throws IOException {
if (tuple == null || tuple.size() == 0 ||
tuple.get(0) == null)
return null;
String str = (String) tuple.get(0);
// Удалить знаки препинания и, при необходимости,
// применить лемматизацию, выделить основы слов и пр.
String clean = str.toLowerCase().replaceAll
("\\p{Punct}", "");
...
return clean;
}
}
Исходный код на языке Pig Latin для анализатора настроений описан
в листинге 4.28. Сначала регистрируется определенная на Java пользовательская функция и определяется ее псевдоним PROCESS, чтобы использовать
ее в качестве встроенной функции (см. листинг 4.28).
Листинг 4.28 Регистрация пользовательской функции Java
REGISTER PigUDF.jar;
DEFINE PROCESS main.Processing;
Затем из HDFS загружаются данные, относящиеся к текстовым отзывам,
в виде CSV-файла значений, разделенных знаком табуляции, и аналогичным
образом загружается словарь настроений слов. При загрузке данных из файла
пользователь может указывать его схему посредством именованных столбцов, как показано в листинге 4.29.
Листинг 4.29 Загрузка данных об отзывах и словаря настроений слов
-- Загрузить данные из HDFS
reviews = LOAD 'hdfs://hostname:port/pigdata/reviews.csv' USING
PigStorage ('\t') AS (id:int, text:chararray);
-- Загрузить словарь настроений слов
dictionary = LOAD 'hdfs://hostname:port/pigdata/dictionary.txt'
USING PigStorage('\t') AS (word:chararray,rating:int);
После загрузки данных каждая строка лексемизируется и обрабатывается
в соответствии с функцией, определенной в листинге 4.27. В частности, как показано в листинге 4.30, с помощью инструкции FOREACH каждая строка входных
данных сначала обрабатывается с помощью ранее зарегистрированной пользовательской функции, а затем лексемизируется, в результате чего на выходе
получается массив лексем. Этот массив последовательно разглаживается с помощью встроенного модификатора FLATTEN. Модификатор GENERATE используется совместно с инструкцией FOREACH, чтобы на выходе получить тройки в форме
⟨review id, text, word⟩, которые хранятся в пакете с именем words.
182 Инструменты обработки больших данных
Листинг 4.30 Лексемизация и предобработка
-- Лексемизировать и обработать текст каждого отзыва
words = FOREACH reviews GENERATE id,text, FLATTEN(TOKENIZE(
PROCESS(text))) AS word;
В частности, исходный код листинга 4.31 сначала выявляет все совпадения
между словами из отзыва и словами из словаря, соединяя созданный выше
промежуточный пакет и слова из словаря. Полученные результаты сохраняются в пакете с именем matches, который затем прокручивается в цикле,
чтобы назначить балл каждому слову. Результат этой операции, хранящийся
в пакете с именем matches_rating, представляет собой тройку в форме ⟨review_id, text, rating⟩.
Листинг 4.31 Соединение слов со словарем и назначение балла настроения
-- Соединить каждое слово со словарем и назначить балл настроения
matches = JOIN words BY word, dictionary BY word;
matches_rating = FOREACH matches GENERATE words::id AS id,
words::text AS text, dictionary::rating AS rate;
Пара ⟨review_id, text⟩ используется для выполнения операции группировки и сбора всех найденных в словаре баллов (то есть пакета с именем
group_rating), как показано в листинге 4.32. После группировки для каждого
отзыва используется встроенный оператор AVG, который агрегирует все баллы
слов, и окончательный рейтинг отзыва вычисляется как среднее значение
баллов его лексем. Наконец, выходной пакет avg_ratings сохраняется в файле
в базисной файловой системе HDFS.
Листинг 4.32 Вычисление среднего рейтинга и сохранение результатов в HDFS
-- Сгруппировать и вычислить средний рейтинг отзыва
group_rating = GROUP matches_rating BY(id,text);
avg_ratings = FOREACH group_rating GENERATE group,AVG($1.$2) AS rate;
-- Сохранить результаты
STORE avg_ratings INTO 'ratings' USING PigStorage(',','-schema');
4.7. Инструменты программирования
на основе разделенного глобального
адресногопространства
В этом разделе будет рассмотрено несколько инструментов с поддержкой
модели программирования на основе разделенного глобального адресного
пространства (PGAS), которая является компромиссом между моделями программирования на основе распределенной и коллективной памяти. В част-
Инструменты программирования на основе разделенного глобального адресного 183
ности, модель на основе разделенного глобального адресного пространства
была разработана с целью реализации глобального адресного пространства
памяти, которое разделено логически, а его части локальны для отдельных
процессов. Главная цель указанной модели состоит в ограничении обмена
данными и изолировании отказов в очень масштабных системах.
Асинхронная модель на основе разделенного глобального адресного пространства (APGAS) – это вариант PGAS, поддерживающий как локальное, так
и дистанционное асинхронное создание заданий. В отличие от модели PGAS,
асинхронная модель PGAS не нуждается в том, чтобы все процессы исполнялись на схожем оборудовании, и поддерживает динамическое порождение
нескольких заданий. Несколько вычислительных потоков, по сути дела, могут быть активны одновременно в одном месте, используя как локальные,
так и дистанционные данные. В дополнение к этому она не требует, чтобы
все места в вычислениях были однородными.
В последние годы было предложено несколько языков для модели на основе разделенного глобального адресного пространства с целью поддержки
эффективных вычислений в крупных распределенных системах, таких как
DASH (Fuerlinger и соавт., 2016), X10 (Charles и соавт., 2005), Chapel (Deitz
и соавт., 2006), pPython (Byun и соавт., 2022) и UPC (Consortium UPC, 2005).
Далее будет подробно рассмотрена библиотека UPC++, то есть реализация
UPC, разработанная в Berkeley Lab. Это полное программное решение с хорошим техническим сопровождением, служащее для разработки приложений
с использованием модели на основе разделенного глобального адресного
пространства. Более того, разработчики библиотеки UPC++ распространяют
готовые к использованию Docker-контейнеры и подробные руководства, что
намного упрощает ее освоение и использование.
4.7.1. UPC++
UPC++ (Zheng и соавт., 2014) – это библиотека на основе C++, которая предлагает классы и функции, призванные оказывать поддержку программирования асинхронной модели на основе разделенного глобального адресного
пространства (APGAS). Помимо доступа к локальной памяти, как в стандартном C++, в модели APGAS каждый поток, именуемый рангом, имеет доступ
к глобальному адресному пространству, выделяемому внутри коллективных сегментов, которые распределены по рангам. Такая модель памяти, как
показано на рис. 4.20, делает библиотеку UPC++ пригодной для написания
параллельных программ, которые эффективно выполняются и хорошо масштабируются на параллельных компьютерах с распределенной памятью,
состоящих из сотен тысяч ядер.
184 Инструменты обработки больших данных
Глобальное адресное пространство
Коллективный
сегмент
Коллективный
сегмент
Коллективный
сегмент
Коллективный
сегмент
Частный
сегмент
Частный
сегмент
Частный
сегмент
Частный
сегмент
Ранг 0
Ранг 1
Ранг 2
Ранг N
Рис. 4.20 Асинхронная модель
разделенного глобального адресного пространства памяти (APGAS)
В библиотеке UPC++ используется промежуточный программный компонент сетевой коммуникации в глобально-адресном пространстве GASNet (от англ. global-address space networking), который представляет собой
языково-независимый слой, обеспечивающий сетенезависимые коммуникационные примитивы, включая дистанционный доступ к памяти (RMA, от
англ. remote memory access) и активные сообщения (AM, от англ. active messages). Благодаря высокой производительности и переносимости компонент
GASNet использовался для реализации параллельных языков и библиотек
с глобальным адресным пространством, таких как вышеупомянутые UPC++,
Legion и Chapel. В библиотеке UPC++ все операции дистанционного доступа
к памяти по умолчанию асинхронны, а интерфейсы по своей конструкции
являются композитными и похожими на те, что используются в обычном
языке C++. Рассмотренные далее в этом разделе главные абстракции программирования включают глобальные указатели, вызовы дистанционных
процедур, фьючерсы и коллективные объекты.
Библиотека UPC++ обеспечивает низкий уровень абстракции для разработки крупных итеративных параллельных приложений, способствуя мелкозернистому контролю над параллелизмом и эффективному потреблению
вычислительных ресурсов. Тем не менее производительность приложений
может снижаться из-за широкого использования коммуникаций, а их отладка бывает затруднена. Ввиду отсутствия высокоуровневых конструкций
она отличается высокой степенью многословности, что вынуждает программистов вручную преодолевать некоторые трудности распределенного
программирования, такие как обмен данными и синхронизация. В библиотеке UPC++ во время исполнения используется параллелизм данных, так как
входные данные распределены по нескольким рангам и обрабатываются
параллельно.
4.7.1.1. Основы
Все программы с использованием библиотеки UPC++ включают две фундаментальные операции:
Инструменты программирования на основе разделенного глобального адресного 185
upcxx::init(), которая инициализирует среду исполнения UPC++
и должна вызываться до использования любых функций UPC++;
upcxx::finalize(), которая закрывает среду исполнения UPC++ и не позволяет использовать какие-либо функции UPC++ после нее.
Программа UPC++ выполняется с фиксированным количеством вычислительных потоков, именуемых рангами, каждый из которых исполняет одну
копию программы. Каждый ранг имеет свой идентификатор, то есть целое
число от 0 до N – 1, где N обозначает количество рангов, доступ к которому
можно получить с помощью операции upcxx::rank_me().
Асинхронные вычисления: вычисления в программе UPC++ могут разделяться между разными рангами, позволяя им исполнять свои операции
параллельно. Затем с одного из рангов собирается окончательный результат,
что означает существование точки синхронизации, в которой ожидается
результат каждого ранга. С целью повышения уровня параллельности в биб
лиотеке UPC++ задействуются асинхронные вычисления, которые позволяют
частично дублировать (перекрывать) коммуникации и вычисления, активно
эксплуатируя время ожидания. Это достигается за счет использования объектов future, которые представляют собой конструкции UPC++, характеризующиеся значением и состоянием, указывающим на доступность (готовность)
или недоступность значения. В качестве примера давайте рассмотрим операцию upcxx::allreduce, которая определяется следующим образом:
template<typename T, typename BinaryOp>
upcxx::future<T> upcxx::allreduce(T &&value, BinaryOp &&op,
upcxx::team &team = upcxx::world());
Эта операция выполняет глобальную редукцию значения типа T по всем
рангам путем применения двоичной функции (например, sum). В дополнение
к этому поддерживается использование коллективов. Коллективы (teams) –
это упорядоченные множества рангов, к которым можно применять коллективные операции. Так, единственным поддерживаемым коллективом
в библиотеке UPC++ является upxx::world(), который включает в себя все
ранги. Показанная выше операция редукции возвращает тип future⟨T⟩, что
позволяет выполнять асинхронные вычисления. В частности, ожидающий
результата ранг не блокируется до тех пор, пока это значение не будет доступно, и тем временем может выполнять другие операции, не связанные
с ним. Эта логика реализуется методом wait() в upcxx::future, который внут
ренне проверяет состояние объекта future, выполняя цикл до тех пор, пока
объект не завершится и не станет готовым.
Коллективные объекты: в соответствии с моделью APGAS библиотека UPC++ позволяет программистам работать с объектами в коллективном
пользовании между разными рангами. В частности, коллективный объект
выделяется внутри сегмента коллективной памяти и доступен посредством
глобального указателя, который задается как upcxx::global ptr<T> gptr.
Как показано на рис. 4.21, библиотека UPC++ определяет в глобальном
адресном пространстве две разные области памяти, в которых объекты могут
выделяться посредством одного из следующих ниже подходов:
186 Инструменты обработки больших данных
используя upcxx::new <T>, новый объект типа T выделяется в коллективном сегменте текущего ранга. Каждый ранг может ссылаться на
этот объект через частный глобальный указатель на свой локальный
коллективный сегмент;
используя стандартное ключевое слово new языка С++, происходит типичное динамическое выделение в частной локальной памяти ранга.
0
1
2
N
mine:
mine:
mine:
mine:
gptr:
gptr:
gptr:
gptr:
Ранг 1
Ранг 2
Ранг N
Ранг 0
Коллективные
Частные
Рис. 4.21 Глобальные указатели UPC++
4.7.1.2. Пример программирования
В этом разделе представлен пример, включенный в библиотеку UPC++, который показывает, как вычислять оценку π методом Монте-Карло, используя
программирование на основе APGAS. Метод Монте-Карло для оценки π работает путем генерации большого количества точек на двумерной плоскости,
координаты которых (x, y) являются равномерно распределенными случайными величинами в интервале [0, 1]. Указанный метод подсчитывает среди
всех случайно сгенерированных точек количество точек, которые попадают
в сектор окружности единичного радиуса с центром в точке (0, 0), то есть во
все точки, удовлетворяющие уравнению: x2 + y2 ≤ 1. Этот процесс генерации
точек изображен на рис. 4.22.
Пусть N – это суммарное количество сгенерированных точек, а M – количество точек, попадающих в сектор окружности. Тогда, с учетом того, что
площадь единичного квадрата и сектора соответственно равны Aq = 1 и As = ,
значение π можно оценить следующим образом:
В листинге 4.33 показана реализация описанного выше процесса оценивания средствами библиотеки UPC++. Как объяснено в предыдущем разделе, программа UPC++ начинается с вызова метода upcxx::init(), который
устанавливает среду исполнения UPC++. Затем каждый ранг, идентификатор
которого задается методом upcxx::rank_me(), инициализирует локальный генератор случайных чисел, который используется для вычисления координат разных попыток. Каждый ранг исполняется параллельно и выполняет
Инструменты программирования на основе разделенного глобального адресного 187
y
100 000 попыток, генерируя случайную точку в единичном квадрате с координатами (x, y), x ∼ U[0, 1], y ∼ U[0, 1], подсчитывая количество точек, которые
попадают в сектор окружности единичного радиуса. Это значение, то есть
количество попаданий, каждый ранг хранит в своей локальной переменной
my_hits. После того как каждый ранг произвел свои попытки, все результаты собираются рангом 0 (подробнее об этом позже), который определяет
суммарное количество попаданий методом reduce_to_rank0 и сохраняет их
в переменной hits. Наконец, с учетом суммарного количества рангов, полученного посредством upcxx::rank_n(), можно вычислить совокупное коли
чество сгенерированных точек (попыток) и получить оценку π, выводимую
как 4 ∗ попадания / попытки.
A = π/4
x
Рис. 4.22 Оценивание π методом Монте-Карло
Листинг 4.33 Оценивание π методом Монте-Карло в UPC++
#include
#include
#include
#include
<iostream>
<cstdlib>
<random>
<upcxx/upcxx.hpp>
using namespace std;
int64_t hit()
{
// сгнерировать случайные координаты точки
double x = static_cast<double>(rand()) / RAND_MAX;
double y = static_cast<double>(rand()) / RAND_MAX;
// выполнить проверку на попадание точки внутрь сектора окружности
if (x*x+y*y <= 1.0) return 1;
else return 0;
}
188 Инструменты обработки больших данных
int main(int argc, char **argv)
{
upcxx::init();
// каждый ранг получает свою собственную копию локальных переменных
int64_t my_hits = 0;
// количество попыток, выполняемых в каждом ранге
int my_trials = 100000;
// инициализировать генератор случайных чисел для каждого ранга
srand(upcxx::rank_me());
// локальное вычисление
for (int i = 0; i < my_trials; i++) {
my_hits += hit();
}
// ранг 0 собирает и суммирует попадания всех рангов
int64_t hits = reduce_to_rank0(my_hits);
// ранг 0 печатает окончательную оценку
if (upcxx::rank_me() == 0) {
int64_t trials = upcxx::rank_n() * my_trials;
cout << "pi estimate: " << 4.0 * hits / trials << endl;
}
upcxx::finalize();
return 0;
}
Осталось выяснить, как локальные результаты каждого ранга используются рангом 0 для определения совокупного количества попаданий, из которого выводится окончательная оценка π. Библиотека UPC++ предлагает разные
способы выполнения этой операции, среди которых наиболее простым является использование метода allreduce, как показано ниже:
upcxx::allreduce(my hits, plus<int>()).wait()
В этом случае коллективная редукция вычисляется путем применения
функции plus (то есть суммы) ко всем локальным значениям переменной
my_hits. Эта операция является асинхронной и возвращает будущее типа
int, поэтому окончательный результат необходимо ожидать посредством
метода wait.
Другой способ определения совокупного количества попаданий показан
в листинге 4.34, в котором используются глобальные указатели UPC++, основанные на асинхронной модели коллективной памяти APGAS.
Листинг 4.34 Определение суммарного количества попаданий с помощью
глобальных указателей
int64_t reduce_to_rank0(int64_t my_hits)
{
// ранг 0 создает массив, чтобы хранить в нем все поступающие значения
upcxx::global_ptr<int64_t> all_hits_ptr = nullptr;
if (upcxx::rank_me() == 0) {
all_hits_ptr = upcxx::new_array<int64_t>(upcxx::rank_n());
}
Инструменты программирования на основе разделенного глобального адресного 189
// ранг 0 широковещательно транслирует всем рангам глобальный указатель на массив
all_hits_ptr = upcxx::broadcast(all_hits_ptr, 0).wait();
// все ранги смещают указатель массива на свой ранговый ИД
upcxx::global_ptr<int64_t> my_hits_ptr = all_hits_ptr +
upcxx::rank_me();
// все ранги помещают свое локальное значение попаданий в коллективный массив
upcxx::rput(my_hits, my_hits_ptr).wait();
// ожидать завершения всех вставок
upcxx::barrier();
// ранг 0 выполняет операцию редукции по сумме
int64_t hits = 0;
if (upcxx::rank_me() == 0) {
// получить локальный указатель на коллективный массив
int64_t *local_hits_ptrs = all_hits_ptr.local();
// просуммировать все значения, хранящиеся в массиве
for (int i = 0; i < upcxx::rank_n(); i++) {
hits += local_hits_ptrs[i];
}
upcxx::delete_array(all_hits_ptr);
}
return hits;
}
В качестве первого шага ранг 0 выделяет глобальный указатель all_hits_ptr
на целочисленный массив посредством функции upcxx::new_array. Размер массива задается равным количеству рангов, полученному как upcxx::rank_n(),
так как он будет использоваться для хранения всех значений попаданий
из дистанционных рангов. Затем глобальный указатель широковещательно
транслируется всем рангам, которые будут обновлять конкретную позицию
массива, основываясь на своем ранговом идентификаторе, полученном как
upcxx::rank_me(). В частности, ранговый идентификатор используется для
смещения глобального указателя таким образом, чтобы i-й ранг вставлял
значение своей локальной переменной my_hits в i-ю позицию коллективного массива. Вставка значения локальной переменной my_hits в глобальный
коллективный массив выполняется каждым рангом с помощью функции
upcxx::rput (от англ. remote put, то есть дистанционная вставка), которая
инициирует одностороннюю коммуникацию для передачи дистанционному
процессу в асинхронной манере, чтобы не требовалась координация с процессом-адресатом. После прохождения барьера upcxx::barrier, который действует как точка синхронизации, чтобы обеспечивать завершение всех дистанционных передач, ранг 0 использует функцию upcxx::global ptr<T>::local,
дабы получить локальную версию глобального указателя и просуммировать
все значения, вставленные дистанционными рангами. Наконец, он высвобождает коллективный массив с помощью функции upcxx::delete_array
и возвращает редуцированный результат, хранящийся в переменной hits.
Глава
5
Сравнение инструментов
программирования
В этой главе рассматриваются и сравниваются инструменты программирования, представленные в предыдущей главе. Для сравнения фреймворков используется набор функциональных и нефункциональных свойств. Это сравнение будет применено для того, чтобы объяснить, каким образом каждый
инструмент мог бы помочь разработчикам в программировании приложений
по обработке больших данных.
5.1. Перед проведением анализа
инструментов
Организации и изыскатели в области больших данных ищут все более эффективные инструменты обработки и добычи информации из крупных объемов
данных. Сегодня предлагается широкий спектр программных инструментов,
разработанных для аналитики больших данных, чтобы удовлетворять самые
разные потребности. Цель этой главы состоит в том, чтобы предъявить всестороннее сравнение разных инструментов программирования, представленных в предыдущей главе. В целях сужения рамок сравнения и придания
ему большей содержательности мы также проводим всесторонний анализ
на примерах приложений, сосредоточиваясь на четырех преобладающих
классах приложений: пакетной обработке, потоковой обработке, выполнении запросов к данным и графовой обработке. Цель такого сравнения на
примерах – помочь читателю получить глубокое понимание относительных
достоинств и недостатков преобладающих инструментов для конкретных
вариантов использования.
Сравнительный анализ характеристик систем 191
Глава организована следующим образом. В разделе 5.2 резюмированы
и классифицированы главные характеристики рассмотренных инструментов программирования, включая их распространенность, поддержку сообществом, преимущества и недостатки. Он призван помочь разработчикам
выбрать один из этих инструментов, основываясь на нескольких других
факторах, таких как бюджет, тип параллелизма, уровень абстракции, многословность при написании исходного кода и главные классы приложений.
В разделе 5.3 проводится сравнение различных инструментов обработки
больших данных путем оценивания их сильных и слабых сторон на примерах
приложений. При сравнении также учитываются дополнительные факторы,
такие как производительность, масштабируемость, удобство использования
и пригодность.
5.2. Сравнительный анализ
характеристик систем
В этом разделе резюмированы и классифицированы главные характеристики
всех рассмотренных инструментов программирования, их распространенность, преимущества и недостатки. Некоторые из этих систем имеют общие
характеристики, что затрудняет программистам выбор между ними. Выбор
может зависеть от нескольких факторов, таких как бюджет (например, нередко высокоуровневые услуги отличаются простотой использования, но
они дороже низкоуровневых решений), тип параллелизма, формат, источник и объем данных, а также производительность. Следует признать, что
конкретное задание по анализу больших данных может быть реализовано
с использованием разных моделей и систем программирования.
Проведенный в этом разделе сравнительный анализ поможет исследователям данных и разработчикам выбрать оптимальную систему, исходя из
своих навыков программирования, параллельной модели, бюджета, области
применения и поддержки, оказываемой сообществом пользователей и разработчиков.
5.2.1. Характеристики систем
В табл. 5.1 приведены характеристики систем в соответствии с их моделями
программирования, типом параллелизма, уровнем абстракции, многословностью исходного кода и главными классами приложений.
Под уровнем абстракции понимается способность системы скрывать низкоуровневые детали программирования, что позволяет разработчикам сосредоточиваться на логике задачи. Высокий уровень абстракции позволяет
легко строить приложения, но затрудняет их компиляцию в эффективный
Workflow
BSP (API Pregel)
SQL-подобная (Spark SQL)
Рабочие потоки
Рабочие потоки
Передача сообщений
SQL-подобная
SQL-подобная
На основе PGAS
Spark
Storm
Airflow
MPI
Hive
Pig
UPC++
Система Модель
программирования
Hadoop
MapReduce
Таблица 5.1. Характеристики систем
Параллелизм
данных/заданий
Параллелизм
данных/заданий
Параллелизм
данных
Параллелизм
данных
Параллелизм
данных/заданий
Параллелизм
данных
Тип
параллелизма
Параллелизм
данных
Параллелизм
данных/заданий
Низкий
Средний
Высокий
Низкий
Высокий
Средний
Средний
Высокая
Низкая
Низкая
Высокая
Низкая
Средняя
Низкая
Уровень
Многословность
абстракции
Низкий
Высокая
Итеративные параллельные
приложения
Выполнение запросов к данным
и генерирование отчетов
Выполнение запросов к данным
и анализ данных
Итеративные параллельные
приложения
Приложения на основе конвейеров
Пакетная и потоковая обработка,
анализ графов, выполнение запросов
к данным, итеративные параллельные
приложения
Потоковая обработка
Пакетная обработка
Главные классы приложений
192 Сравнение инструментов программирования
Сравнительный анализ характеристик систем 193
исходный код. С другой стороны, низкий уровень абстракции затрудняет
создание приложений, но облегчает их эффективную реализацию (Skillicorn
и Talia, 1998). Для целей сравнения мы выделяем три уровня абстракции:
низкий: фреймворки этой категории предоставляют мощные API и примитивы, требующие навыков распределенного программирования, что
влечет за собой большие затраты на разработку. Они также нуждаются в низкоуровневом понимании системы, включая работу с файлами
в распределенных средах (Wadkar и соавт., 2014). Однако эффективность исходного кода высока, поскольку его можно полностью перенастраивать. Примерами таких фреймворков являются Hadoop, MPI
и UPC++;
средний: такие системы позволяют разработчикам реализовывать
параллельные и распределенные приложения, используя ограниченное количество конструкций. Они нуждаются в определенных навыках программирования, но трудоемкость разработки у них ниже, чем
у фреймворков с низким уровнем абстракции. В качестве примера
можно привести фреймворки Spark, Storm и Pig;
высокий: к этому классу относятся системы, требующие ограниченных
навыков программирования и позволяющие разработчикам быстро
собирать приложения по анализу данных с помощью простых визуальных интерфейсов или высокоуровневых скриптов. Хотя усилия по разработке программ на этом уровне невелики, эффективность исходного
кода во время его исполнения также невелика, так как генерировать
исполняемые файлы сложнее, а соотнесение исходного кода не является прямым. К этой категории относятся фреймворки Hive и Airflow.
Тип параллелизма описывает принцип, на основе которого модель программирования или система выражает параллельные операции и способ,
каким ее среда исполнения поддерживает выполнение параллельных операций на нескольких узлах или процессорах. Для целей сравнения выделяется
два типа параллелизма:
параллелизм данных: он достигается, когда одинаковый исходный код
исполняется параллельно на разных элементах данных. Параллелизм
данных также относится к архитектурам «одна инструкция, несколько
элементов данных» (SIMD). Он является одним из классов в таксономии классификации параллельных вычислений Флинна (Flynn, 1972).
К этой категории относятся фреймворки Hadoop, Spark, Storm, MPI,
UPC++, Hive и Pig. Такие системы предназначены для автоматического управления крупными входными данными, их разбиения на куски
и параллельной обработки на разных вычислительных узлах;
параллелизм заданий: он достигается, когда входящие в состав приложения разные задания исполняются параллельно. Преимущества
этого вида параллелизма могут ограничиваться наличием зависимостей от данных. Такой параллелизм можно задавать двумя способами:
явным, когда программист определяет зависимости между заданиями
194 Сравнение инструментов программирования
посредством явных инструкций, и неявным, когда система анализирует данные на входе в задания / выходе из них, чтобы понимать зависимости между ними. Эта форма параллелизма используется во
фреймворках Spark, Storm, Pig и Airflow. Такие системы позволяют
параллельно исполнять независимые задания без какой-либо зависимости от данных.
По степени многословности системы можно классифицировать следующим образом с учетом рассмотренных примеров программирования:
высокая: системы этой категории требуют большого количества строк
исходного кода, и при сборке даже простого приложения используется
большое количество инструкций/вызовов, в результате чего написание
приложений с использованием этих систем бывает сложным и отнимает много времени (Verma и соавт., 2016). Например, в эту категорию
входит фреймворкх Hadoop, так как приложение MapReduce в Hadoop
нуждается в определении преобразователя, редуктора и вычислительной заявки;
средняя: сюда входят системы, требующие реализации специфических интерфейсов и методов для программирования приложения или
использования специфических конструкций. Например, фреймворк
Storm нуждается в реализации интерфейсов для выпускных кранов
и поперечных задвижек и переопределении таких методов, как nextTuple и declareOutputFields;
низкая: в эту категорию входят такие фреймворки, как Spark, Airflow,
Hive и Pig. Исходный код в этих системах обычно имеет компактный
вид, так как программистам не приходится использовать специфические конструкции, а при необходимости требуется всего несколько
строк исходного кода. Они обычно предоставляют простой в использовании стиль программирования (например, как в языках HiveQL
и Pig Latin) или позволяют строить рабочие потоки непосредственно
из набора скриптов (например, Airflow).
Наконец, системы могут поддерживать разные классы приложений,
а именно:
пакетную обработку: приложения, предназначенные для обработки
и анализа крупных объемов данных в неинтерактивном режиме;
потоковую обработку: приложения, предназначенные для обработки
и анализа данных, собираемых в реальном времени;
графовую обработку: приложения, предназначенные для обработки
и анализа данных, связанных между собой в сложные сети или графовые структуры;
выполнение запросов к данным: приложения, предназначенные для
обеспечения быстрого и эффективного доступа к крупным объемам
данных с помощью языков запросов и инструментов поиска.
Кроме того, иногда рассматриваются следующие дополнительные классы
приложений:
Сравнительный анализ характеристик систем 195
итеративные параллельные: приложения, состоящие из повторяющихся заданий, работающих параллельно:
конвейерные: приложения, состоящие из последовательности стадий,
в которых результат на выходе из одной стадии подается на вход следующей стадии.
Программисты могут использовать как общецелевые системы, такие как
Hadoop, Spark, MPI и UPC++, так и системы, разработанные для конкретных
областей применения. Например, доступная в Spark библиотека GraphX представляет собой версию системы графовой обработки Google Pregel, которую
можно эффективно использовать для разработки приложений по графовой
обработке. Кроме того, фреймворки Hive и Pig можно использовать для выполнения запросов к данным, а Storm – для реально-временной потоковой
обработки.
5.2.2. Распространенность систем
В табл. 5.2 приведены данные о распространенности и популярности каждой
системы с точки зрения пользователей и разработчиков.
Таблица 5.2. Распространенность и популярность систем
Система Размер
Звезды Поддержка
сообщества
GitHub API
пользователей
(еженедельный)
Hadoop
Большой: 44.2k
13.3k
Java, C, C++,
(16)
Ruby, Groovy,
Perl, Python
Spark
Очень большой: 35.2k
Scala, Python,
79.6k (139)
Java, R
Storm
Малый: 2.5k (–)
6.4k
Clojure, Java,
Python, Ruby
Airflow
Средний: 9.5k
29.4k
Python
(50)
MPI
Средний: 6.8k
1.7k
Java, Fortran,
(11)
C, C++, Perl,
Python
Hive
Большой: 21.7k
4.7k
HiveQL
(27)
Pig
Малый: 5.2k (–)
656
Pig Latin
Количество
коммитов
на GitHub
Внедренцы
26.5k
Yahoo!, IBM,
Amazon
36k
UPC++
–
eBay, Amazon,
Alibaba, CERN
Twitter, Groupon,
Spotify
Adobe,
Onefootball
Amazon WS,
AMD, Cisco,
Facebook
Facebook, Netflix,
Yahoo!, AirBnB
LinkedIn, PayPal,
Mendeley
NERSC, LANL
Очень малый:
23 (–)
–
C++
10.5k
19k
33.6k
16.6k
3.7k
Источник: данные получены из Stack Overflow и GitHub в марте 2023 года.
В том, что касается распространенности, системы классифицированы
с учетом следующих ниже параметров.
196 Сравнение инструментов программирования
Сообщество пользователей, которое относится к распространенности
системы с точки зрения количества людей, активно ее использующих.
При проведении сравнения было проинспектировано количество соответствующих вопросов, заданных на веб-сайте Stack Overflow. В частности, в качестве главного показателя заинтересованности пользователей использовалось суммарное количество вопросов, а с целью более
точного отражения последних трендов в освоении системы пользователями бралось среднее количество вопросов в неделю. На основании
этого было обнаружено, что система Spark имеет самое большое сообщество пользователей; за ним следуют Hadoop и Hive, а затем MPI,
Airflow, Pig и Storm.
Звезды GitHub – показатель популярности и возможности повторного использования систем. Между популярностью системы и воспринимаемым пользователями качеством этой системы существует, по
сути дела, тесная связь и, следовательно, ее повторное использование.
В частности, в работе Папамикаила (Papamichail и соавт., 2016) продемонстрирована сильная положительная корреляция между коли
чеством звезд и количеством копий (веток) после проведения анализа
100 самых популярных репозиториев Java на GitHub. Самой популярной и многократно используемой системой является Spark, за ней следуют Airflow, Hadoop, Storm, Hive, MPI и Pig.
Поддержка API, которая указывает на набор языков программирования,
доступных для разработки приложений с помощью данной системы.
Такие системы, как Hadoop, Spark, Storm, Airflow и MPI, рассчитаны на
разработчиков с самыми разными навыками программирования благодаря наличию API-интерфейсов к популярным языкам (в частности,
языкам Python и Java, согласно индексу PYPL1 от марта 2023 года). И наоборот, для работы с такими фреймворками, как Pig или Hive, возможно, потребуется освоить новые языки, скажем Pig Latin или HiveQL.
Количество коммитов на GitHub как показатель размера сообщества
разработчиков, участвующих в разработке и техническом сопровождении исходного кода. Было решено обратиться к количеству коммитов
в официальных репозиториях GitHub, для того чтобы понять заинтересованность разработчиков в исправлении ошибок/дефектов и внед
рении новых функциональностей. Как видно по распространенности
среди пользователей, система Spark продемонстрировала наибольшее
участие со стороны вкладчиков в систему, за ней следует фреймворк
MPI, который интересовал программистов, работающих над широким
спектром приложений, и фреймворк Hadoop. Далее следуют системы
Airflow, Hive, Storm и Pig.
Главные внедренцы, под которыми понимаются крупные компании и исследовательские институты, которые активно работают с той или иной
системой, встраивая ее в свою операционную, научную и инженер1
См. https://pypl.github.io/PYPL.html.
Сравнительный анализ характеристик систем 197
но-изыскательскую деятельность. Такое внедрение служит свидетельством надежности, функциональности и потенциала для широкого использования системы. Некоторые из обсуждаемых фреймворков, такие
как Hadoop, Spark и MPI, широко используются крупными компаниями
сферы ИТ, такими как IBM, Amazon, Twitter, Facebook, Netflix и PayPal,
что подтверждает актуальность и влияние указанных систем в данной
отрасли. Факты внедрения этими компаниями не только подчеркивают
способности фреймворков, но и их важность как инструментов достижения организационных целей. И наоборот, другие фреймворки, такие
как UPC++, используются в основном научным и инженерно-изыскательским сообществом.
5.2.3. Преимущества и недостатки
В табл. 5.3 приведены главные преимущества и недостатки описанных систем при их использовании для программирования приложений по анализу
больших данных. Плюсы и минусы описываются, начиная с характеристик,
выявленных в ходе разбора каждой системы, и рассматриваются в контексте
приложений и представленных далее фрагментов исходного кода.
Таблица 5.3. Преимущества и недостатки систем
Система Преимущества
Hadoop
Отказоустойчивость, низкая
стоимость, очень большое сообщество
разработчиков открытого исходного
кода
Spark
Storm
Airflow
MPI
Hive
Недостатки
Многословность, ограничен
пакетной обработкой, трудности
при обработке малых файлов,
неэффективность в итеративных
приложениях
Резидентные вычисления, простота
Отсутствие процесса
использования и гибкость,
автоматической оптимизации,
библиотеки для графовой аналитики, трудности при обработке малых
масштабируемая поддержка
файлов, большое потребление
машинного обучения
памяти
Поддержка нескольких языков, низкое Упорядочивание сообщений не
время отклика
гарантируется
Поддержка оркестровки скриптов
Отсутствует версионирование
Python, гибкость рабочих потоков
рабочих потоков, отсутствует
и контроль над ними, расширяемость обширная документация
и интегрируемость
Эффективность, переносимость,
Трудно отлаживать, узкое место
коллективная или распределенная
в сетевом взаимодействии
память
Поддерживает только онлайновую
Выполнение запросов к крупным
аналитическую обработку (OLAP),
распределенным наборам
реально-временной доступ
данных, SQL-подобный язык,
к данным не поддерживается,
пользовательские функции для
трудности в написании сложных
поведения продвинутой аналики
функций по аналитике данных
данных
198 Сравнение инструментов программирования
Таблица 5.3 (окончание)
Система Преимущества
Pig
Высокоуровневый процедурный
язык, пользовательские функции
для проведения продвинутого
анализа данных, прост в усвоении
и разработке
UPC++
Высокая эффективность
и масштабируемость, поддержка
программирования асинхронной
модели разделенного глобального
адресного пространства памяти
(APGAS)
Недостатки
Малое сообщество, трудно
настраивать производительность
Трудно отлаживать, широкое
использование коммуникаций
Преимущества каждой системы связаны с конкретными характерис
тиками, которые она предлагает по сравнению со смежными системами
с точки зрения функциональности, поддержки разных библиотек и интег
рации с другими фреймворками. Например, фреймворк Spark является
единственным, в котором используется модель резидентных вычислений,
что позволяет конструировать эффективные приложения, интенсивные по
привлечению данных. Кроме того, как станет ясно из сравнения со специа
лизированными фреймворками, такими как Storm и Pig, в последующих
разделах, фреймворк Spark обладает высокой гибкостью и может использоваться в широком спектре областей применения, так как он предлагает библиотеки для потоковой и графовой обработки, машинного обучения
и анализа структурированных данных. Среди других фреймворков, основанных на рабочих потоках, наиболее удобным в использовании является
Airflow, позволяя любому человеку с достаточным знанием языка Python
развертывать, отслеживать, планировать и управлять рабочим потоком через веб-приложение.
С другой стороны, недостатки системы в основном связаны с нехваткой, слабостями, затратами и ограничениями в использовании данной
системы. Например, фреймворк Hadoop плохо подходит для итеративных приложений, но отлично подходит для пакетной обработки. Главным недостатком использования фреймворка Storm для вычислений на
реально-временных потоках данных является отсутствие гарантий упорядочивания сообщений. Фреймворки MPI и UPC++ в целом демонстрируют эффективность, но в них бывает трудно проводить отладку из-за
низк оу ровн ев ых моделей программирования. Фреймворк Hive хорошо
подходит для выполнения запросов к крупным распределенным данным;
однако он не поддерживает операции онлайновой обработки транзакций
(OLTP). Наконец, фреймворк Pig предлагает простой в использовании интерфейс программирования для приложений по анализу данных, но их
отладка отличается сложностью.
Сравнительный анализ на примерах приложений 199
5.3. Сравнительный анализ
на примерах приложений
В этом разделе представлен сравнительный анализ разных систем для работы с большими данными, а также рассмотрены их сильные стороны и ограничения через призму примеров приложений. Мы стремимся дать глубокое понимание производительности, масштабируемости, удобства использования
и пригодности разных фреймворков, разработанных для решения сложных
задач, связанных с обработкой больших данных.
5.3.1. Пакетное приложение: Apache Spark
в сопоставлении с Apache Hadoop
Ниже представлено приложение по автоматическому обнаружению шаблонов мобильности пользователей по геотегированным постам Flickr, сгенерированным в городе Рим. Говоря конкретнее, приложение призвано обнаруживать наиболее часто встречающиеся траектории движения пользователей
по конкретным местам или территориям, представляющим интерес для нашего анализа, которые принято называть точками заинтересованности (PoI,
от англ. points of interest). В частности, точка заинтересованности – это место,
которое считается полезным или интересным, например туристическая достопримечательность или место, интересное с точки зрения предпринимательской деятельности. Поскольку информация о точке заинтересованности обычно ограничивается адресом или GPS-координатами, сопоставлять
траектории с точками заинтересованности очень трудно. По этой причине
нередко бывает полезно определять так называемые области заинтересованности (RoI, от англ. region of interest), которые представляют собой границы
территорий точек заинтересованности (Belcastro и соавт., 2018). В силу этого
траектория может быть определена как последовательность областей заинтересованности, представляющая шаблон передвижения во временной динамике. Часто встречающаяся траектория – это последовательность областей
заинтересованности, часто посещаемых пользователями.
Как показано на рис. 5.1, рабочий процесс предлагаемого приложения
состоит из разных шагов. В частности, после извлечения набора геотегированных постов из Flickr применяется предобработка, чтобы отфильтровать
геотегированные посты и соотнести каждый геотегированный пост с об
ластью заинтересованности.
Наконец, мы приступаем к добыче траекторий, чтобы извлечь часто встречающиеся шаблоны мобильности в траекториях передвижения пользователей по областям заинтересованности, стремясь лучше понять, как люди
перемещаются по городу Риму.
200 Сравнение инструментов программирования
Фильтрация
Соотнесение с областью
заинтересованности
Извлечение
траектории
Элементы данных
социальной сети
Траектории
Рис. 5.1 Рабочий процесс приложения по добыче траекторий
Сбор данных для целей анализа траекторий обычно предусматривает использование датчиков и устройств GPS, так как они регулярно обновляют
информацию о местоположении устройства. Однако для этой цели также
подойдут и данные социальной сети (Cesario и соавт., 2017). Посты в социальной сети нередко имеют геотеги, то есть содержат информацию о мес
тоположении поста или другие метаданные, которые можно использовать
для определения местоположения пользователя в момент создания поста.
В частности, элемент данных социальной сети может содержать следующие
ниже поля (Belcastro и соавт., 2021a):
текстовое описание;
набор ключевых слов, связанных с постом;
пара широта/долгота, представляющая координаты места создания
поста;
ИД, идентифицирующий пользователя, создавшего пост;
временная метка, указывающая на дату создания поста.
5.3.1.1. Реализация с использованием фреймворка Spark
Сначала мы обсудим реализацию приложения в рамках Apache Spark на языке Scala. В листинге 5.1 определены два класса, а именно SingleTrajectory
и UserTrajectory, которые представляют соответственно траекторию (то есть
пары ⟨PoI, timestamp⟩) и пользователя (то есть пары user ⟨id, timestamp⟩). Эти
классы определяют методы equals и hashCode, которые будут использоваться
последующими программами при сравнении траекторий и пользователей.
Листинг 5.1 Классы SingleTrajectory и UserTrajectory для представления
соответственно траектории и пользователя
class SingleTrajectory(var poi: String = "",
var daytime: String = "") {
override def hashCode(): Int = 31 * poi.## + daytime.##
override def equals(o: Any): Boolean = {
if (o == null) return false
if (getClass ne o.getClass) return false
val other = o.asInstanceOf[SingleTrajectory]
if(poi.equals(other.poi) && daytime.equals(other.daytime))
return true
false
}
}
Сравнительный анализ на примерах приложений 201
class UserTrajectory (var username: String = "",
var daytime: String = ""){
override def hashCode(): Int = 31 * username.## + daytime.##
override def equals(o: Any): Boolean = {
if (o == null) return false
if (getClass ne o.getClass) return false
val other = o.asInstanceOf[UserTrajectory]
if(username.equals(other.username) && daytime.equals(other.daytime))
return true
false
}
}
Обозначение .## эквивалентно функции .hashCode, за исключением числовых типов и null. В случае числовых значений возвращаемое оператором
.## значение хеша соответствует эквивалентности, то есть если сравнение
двух экземпляров дает истину, то оператор .## выдаст одинаковое значение
хеша. Однако при появлении значения null оператор .## возвращает хешкод, а null.hashCode генерирует исключение NullPointerException.
Затем, в листинге 5.2, определяется метод main, который включает в себя
шаги, описанные на рис. 5.1.
Листинг 5.2 Метод main
def main(args: Array[String]): Unit = {
val dataset = "FlickrRomeSample.json"
val kmlPath = "rome.kml"
val spark = SparkSession
.builder
.appName("TrajectoryMining")
.master("local[*]")
.getOrCreate()
val stringShapeMap = KMLUtils.lookupFromKml(kmlPath).asScala
var df = spark.read.json(dataset)
df = filterFlickrDataframe(df)
df = df.filter(r => filterIsGPSValid(r) && filterIsInRome(r))
val trajectories = computeTrajectoryUsingFPGrowth(df, stringShapeMap)
trajectories._1.foreach(println)
trajectories._2.foreach(println)
spark.close()
}
Метод main читает входной набор данных JSON в кадр данных Spark и применяет ряд функций, чтобы отфильтровать точки данных, не имеющие отношения к анализу, например точки с неверными GPS-координатами или
точки, расположенные не в Риме. Далее в исходном коде используется класс
KMLUtils, чтобы прочитать KML-файл с разметкой геопространственных данных (от англ. Keyhole Markup Language), содержащий координаты некоторых
точек заинтересованности в Риме, и конвертировать его в ассоциативный
массив (словарь) Scala, ключами которого являются названия точек заин-
202 Сравнение инструментов программирования
тересованности (например, Колизей, музеи Ватикана и мавзолей Адриана),
а ассоциированными значениями – координаты, задающие границы этой
территории в виде многоугольника. Затем программа вычисляет пользовательские траектории с помощью алгоритма FPGrowth и возвращает часто
встречающиеся наборы элементов и ассоциативные правила, которые представляют добытые траектории. Класс KMLUtils определяет служебный метод,
а именно lookupFromKml, который конвертирует KML-файл точек заинтересованности в Риме в описанный ранее ассоциативный массив, используя API
Java для формата KML, чтобы обеспечить удобное и простое использование
KML-файлов в Java-средах.
Исходный код шага фильтрации описан в листинге 5.3. В частности, в нем
задаются три фильтра:
фильтр для отбора только тех столбцов из набора данных Flickr, которые представляют интерес для анализа;
фильтр, оставляющий только те данные, которые имеют действительные GPS-координаты (то есть правильно определенные долготы и широты);
фильтр, оставляющий только те посты, которые были опубликованы
в Риме.
Листинг 5.3 Методы фильтрации
def filterFlickrDataframe(dataframe: DataFrame): DataFrame = {
dataframe.select("geoData.latitude", "geoData.longitude",
"geoData.accuracy", "owner.id", "dateTaken")
}
def filterIsGPSValid(r: Row): Boolean = {
r.getAs[Double]("longitude") > 0 && r.getAs[Double]("latitude")>0
}
def filterIsInRome(r: Row): Boolean = {
val p = GeoUtils.getPoint(r.getAs[Double]("longitude"),
r.getAs[Double]("latitude"))
GeoUtils.isContained(p, romeShape)
}
В функции filterIsInRome используется служебный класс, а именно Geo
Utils, который предоставляет набор методов для взаимодействия с геопространственными данными, таких как преобразование пары долгота–широта
в точку или проведение проверки на принадлежность точки заинтересованности другой точке. Класс основан на общецелевой геопространственной
библиотеке Java Spatial4j, предоставляющей распространенные контуры,
реализуемые в евклидовой и геодезической (поверхность сферы) моделях
мира, позволяющей вычислять расстояния, а также читать и писать контуры
в таких форматах, как WKT и GeoJSON.
После фильтрации входного набора данных можно извлечь ассоциативные правила, используя алгоритм добычи часто встречающихся шаблонов.
Сравнительный анализ на примерах приложений 203
В частности, был применен популярный алгоритм FPGrowth добычи часто
встречающихся наборов элементов в транзакционных базах данных, предложенный в работе Хана (Han и соавт., 2000). Для представления транзакционной базы данных в алгоритме FPGrowth используется сжатая структура
данных, именуемая деревом часто встречающихся шаблонов (FP-дерево, от
англ. frequent pattern tree), которая позволяет эффективно обнаруживать час
то встречающиеся наборы элементов путем сканирования дерева и использования рекурсивного процесса построения условных шаблонов. Указанный
алгоритм состоит из двух фаз. Первая фаза включает в себя однократное
сканирование транзакционной базы данных и построение FP-дерева путем
выявления часто встречающихся элементов в транзакциях и их организации
в древовидную структуру на основе их частоты. Вторая фаза предусматривает рекурсивное извлечение часто встречающихся наборов элементов из
дерева путем их выявления на каждом уровне в направлении снизу вверх
и построения базы условных шаблонов по каждому часто встречающемуся
набору элементов до тех пор, пока не будут извлечены все такие наборы.
С целью адаптации алгоритма добычи траекторий был определен метод
mapCreateTrajectory, который с учетом строки входного набора данных и набора точек заинтересованности, извлеченных из KML-файла, возвращает
кортеж, состоящий из ИД пользователя и посещенной им точки заинтересованности в форме предварительно определенной пары ⟨UserTrajectory,
SingleTrajectory⟩. Соответствующий исходный код показан в листинге 5.4.
Листинг 5.4 Метод извлечения пользователя и посещенных им точек
заинтересованности
def mapCreateTrajectory(row: Row, shapeMap: mutable.Map
[String, String]): (UserTrajectory,
Set[SingleTrajectory]) = {
val lat = row.getDouble(0)
val lon = row.getDouble(1)
val username = row.getString(3)
val d = row.getAs[String](4).substring(0, 12)
val point = GeoUtils.getPoint(lon, lat)
var arr: Array [SingleTrajectory] = Array[SingleTrajectory]()
for ((place, polygon) <- shapeMap) {
val pol = GeoUtils.getPolygonFromString(polygon)
if (GeoUtils.isContained(point, pol)) {
val trajectory = new SingleTrajectory(place, d)
arr = arr :+ trajectory
}
}
val ut = new UserTrajectory(username, d)
(ut, arr.toSet)
}
Первый шаг метода computeTrajectoryUsingFPGrowth, реализация которого
показана в листинге 5.5, состоит в подготовке транзакционных данных в форме RDD[Array[String]] путем создания траекторий с помощью определенного
204 Сравнение инструментов программирования
выше метода mapCreateTrajectory. Результирующие траектории фильтруются
с целью удаления пустых, а потом редуцируются по ключу, чтобы сгруппировать траектории, относящиеся к одному и тому же пользователю. Затем
они преобразовываются во множество уникальных наборов элементов с помощью функции distinct. Далее они передаются на вход алгоритму FPGrowth,
импортированному из библиотеки MLlib, который создает модель на основе
часто встречающихся наборов элементов и генерирует ассоциативные правила с помощью двух параметров: поддержки minSupport и уверенности minConfidence. Показатели поддержки и уверенности широко применяются в добыче
ассоциативных правил с целью измерения силы и значимости взаимосвязи
между двумя элементами в наборе данных. Поддержка служит мерой частоты появления набора элементов в наборе данных и рассчитывается как доля
транзакций в наборе данных, содержащих заданный набор элементов. С другой стороны, уверенность служит мерой условной вероятности того или иного
следствия при наличии той или иной предпосылки (условия) в ассоциативном
правиле. Она рассчитывается как поддержка набора элементов, содержащего
как предпосылку, так и следствие, деленная на поддержку только предпосылки. Наконец, указанный метод возвращает часто встречающиеся наборы
элементов и сгенерированные ассоциативные правила.
Листинг 5.5 Вычисление траектории с помощью алгоритма FPGrowth
def computeTrajectoryUsingFPGrowth(df: DataFrame,
stringShapeMap: mutable.Map[String, String],
minSupport: Double = 0.01, minConfidence:Double = 0.2) = {
val prepareTransaction = df
.rdd
.map(x=>mapCreateTrajectory(x,stringShapeMap))
.filter(x=>x._2.nonEmpty)
.reduceByKey((x,y)=> x ++ y)
val transactions = prepareTransaction
.map{x=>var arr: Array[String] = Array[String]()
x._2.foreach(x=>{
arr = arr :+ x.poi})
arr}
.map(x => x.distinct)
val fpg = new FPGrowth()
.setMinSupport(minSupport)
val model = fpg.run(transactions)
(model.freqItemsets.collect(),model.
generateAssociationRules(minConfidence).collect())
}
Ниже представлена выдержка из ассоциативных правил, произведенных
в результате этой процедуры, где по каждому правилу, добытому с помощью
алгоритма FPGrowth, указаны баллы уверенности и подъема.
Сравнительный анализ на примерах приложений 205
{romanforum,stpeterbasilica}⇒{colosseum}: (confidence: 0.75; lift: 3.84)
{colosseum, stpeterbasilica}⇒{romanforum}: (confidence: 0.375; lift: 3.48)
{colosseum,stpeterbasilica}⇒{pantheon}: (confidence: 0.375; lift: 3.24)
{piazzadelpopolo}⇒{piazzadispagna}: (confidence: 0.25; lift: 2.98)
Подъем – это еще одна распространенная мера степени ассоциации двух
элементов в наборе данных по отношению к ожидаемой степени ассоциации,
если эти два элемента были независимы друг от друга. Значение подъема,
превышающее 1, указывает на положительную ассоциацию между двумя
элементами, тогда как значение подъема, недотягивающее до 1, указывает
на отрицательную ассоциацию между двумя элементами. Воспроизведение
приведенного выше результата достигается путем установки параметра minSupport равным 0,01. В данной реализации алгоритма на базе библиотеки
MLlib минимальный уровень поддержки часто встречающего шаблона задается таким образом, что будет выводиться любой шаблон, который встречается более (minSupport ∗ size_of_the_dataset) раз.
5.3.1.2. Реализация с использованием фреймворка Hadoop
То же самое приложение, описанное с использованием фреймворка Spark,
можно реализовать с помощью парадигмы MapReduce в рамках фреймворка
Apache Hadoop. В частности, мы определяем:
преобразователь, а именно DataMapperFilter, который выполняет шаги
фильтрации;
редуктор, а именно DataReducerByDay, который извлекает области заинтересованности для добычи траекторий;
класс Main, который комбинирует преобразователь и редуктор, а затем
применяет алгоритм FPGrowth из библиотеки Apache Mahout, чтобы
добыть траектории из областей заинтересованности, извлеченных редуктором.
Мы использовали ту же реализацию классов GeoUtils и KMLUtils на языке Java, которая была описана для приложения Spark. В служебных целях
был определен класс, который будет представлять данные Flickr и работать
с ними, а именно Flickr. Он содержит методы-получатели полей user_id, longitude, latitude и др. Более того, этот класс позволяет импортировать данные
Flickr из строкового литерала или объекта JSON, чтобы собирать элемент
данных Flickr и экспортировать данные в виде строкового литерала, как показано в листинге 5.6.
Листинг 5.6 Класс Flickr
public class Flickr {
private final static DateTimeFormatter dateStringFormat =
DateTimeFormat.forPattern("MMM dd, yyyy h:mm:ss a").
withLocale(Locale.ENGLISH);
private String userId;
206 Сравнение инструментов программирования
private
private
private
private
double longitude;
double latitude;
LocalDateTime date;
String roi;
public void importFromString(String s) {
JSONObject json = new JSONObject(s);
this.userId = json.getString("userId");
this.username = json.getString("username");
this.latitude = json.getDouble("latitude");
this.longitude = json.getDouble("longitude");
this.date = dateStringFormat.parseDateTime(json.
getString("date")).toLocalDateTime();
this.roi = json.getString("roi");
}
public void importFromFlickrJSON(String s) {
try {
JSONObject jsonObject = new JSONObject(s);
this.userId = jsonObject.getJSONObject("owner").
getString("id");
this.username = jsonObject.getJSONObject("owner").
getString("username");
if (jsonObject.has("geoData")) {
JSONObject geo = jsonObject.getJSONObject
("geoData");
this.longitude = geo.getDouble("longitude");
this.latitude = geo.getDouble("latitude");
}
this.date = dateStringFormat.parseDateTime
(jsonObject.getString("dateTaken")).
toLocalDateTime();
this.roi = "";
} catch (Exception e) {
}
}
public String export() {
JSONObject ret = new JSONObject();
ret.put("userId", userId);
ret.put("username", username);
ret.put("longitude", longitude);
ret.put("latitude", latitude);
ret.put("date", dateStringFormat.print(date));
ret.put("roi", roi);
return ret.toString();
}
}
Описанный в листинге 5.7 класс DataMapperFilter расширяет класс Mapper
из библиотеки Hadoop MapReduce и содержит несколько экземплярных переменных, таких как объект Shape, представляющий геопространственный
многоугольник (например, географические границы города Рима), объект
Сравнительный анализ на примерах приложений 207
Map, соотносящий названия местоположений с многоугольниками, и объекты
Text, представляющие выходные пары ключ–значение.
Метод setup вызывается перед исполнением метода map и инициализирует
переменную romeShape многоугольником, основанным на координатах Рима,
и переменную shapeMap путем вызова служебных методов в классах GeoUtils
и KMLUtils.
Метод map является главной обрабатывающей функцией преобразователя.
Класс Flickr используется в ней для структурного разбора входных данных
JSON и извлечения информации о GPS-координатах поста, ИД пользователя
и других метаданных. Указанный метод применяет две функции фильтрации, filterIsGPSValid и filterIsInRome, чтобы выполнить проверку критериев фильтрации, то есть на наличие действительных GPS-координат и на
нахождение в окрестностях Рима. Если пост проходит фильтры, то метод
использует класс GeoUtils, чтобы создать объект Shape, который представляет местоположение поста, и выполняет проверку на его принадлежность
одному из многоугольников, определенных в объекте shapeMap, извлеченном
из KML-файла точек заинтересованности в Риме. Если совпадение найдено,
то для элемента данных Flickr устанавливается точка заинтересованности.
И наконец, ИД пользователя и сериализованный объект Flickr, обогащенный
информацией об извлеченной области заинтересованности, записываются
в выходной контекст.
Листинг 5.7 Класс DataMapperFilter
public class DataMapperFilter extends Mapper<LongWritable,
Text, Text, Text> {
private Shape romeShape;
private Map<String, String> shapeMap;
private final String kmlPath = "rome.kml";
private Text outputKey = new Text();
private Text outputValue = new Text();
private final double LAT = 12.492373;
private final double LNG = 41.890251;
private final int RADIUS = 10000;
@Override
protected void setup(Mapper<LongWritable, Text, Text,
Text>.Context context)
throws IOException, InterruptedException {
romeShape = GeoUtils.getCircle(GeoUtils.getPoint
(LAT, LNG), RADIUS);
shapeMap = KMLUtils.lookupFromKml(kmlPath);
}
public void map(LongWritable key, Text value, Context context)
throws InterruptedException, IOException {
Flickr f = new Flickr();
f.importFromFlickrJSON(value.toString());
if (!(filterIsGPSValid(f) && filterIsInRome(f)))
208 Сравнение инструментов программирования
return;
Shape point = GeoUtils.getPoint(f.getLongitude(),
f.getLatitude());
boolean found = false;
for (Entry<String, String> entry : shapeMap.entrySet())
{
String place = entry.getKey();
String polygon = entry.getValue();
try {
Shape pol = GeoUtils.getPolygonFromString(polygon);
if (GeoUtils.isContained(point, pol)) {
f.setRoi(place);
found = true;
}
} catch (IOException | ParseException e) {
e.printStackTrace();
}
}
if (found) {
outputKey.set(f.getUserId());
outputValue.set(f.export());
context.write(outputKey, outputValue);
}
}
private boolean filterIsGPSValid(Flickr f) {
return f.getLongitude() > 0 && f.getLatitude() > 0;
}
private boolean filterIsInRome(Flickr f) {
Point p = (Point) GeoUtils.getPoint(f.getLongitude(),
f.getLatitude());
return GeoUtils.isContained(p, romeShape);
}
}
На этом этапе, как описано в листинге 5.8, каждый редуктор получает
набор постов каждого пользователя социальной сети Flickr, которые были
предварительно обогащены информацией об области заинтересованности.
Цель редуктора состоит в вычислении траекторий как последовательности
областей заинтересованности. Ядром фазы редукции является метод concatenateLocationsByDay, который строит отсортированную последовательность
областей заинтересованности, посещенных в каждый день. В указанном
методе используется компаратор с целью сортировки элементов данных
Flickr по дате, а затем по каждому дню строится строковый литерал с посещенными областями заинтересованности и добавляется в список, который
будет возвращен в метод reduce. Метод reduce выводит, в распределенном
контексте, разделенный пробелом список областей заинтересованности по
каждому дню.
Сравнительный анализ на примерах приложений 209
Листинг 5.8 Класс DataReducerByDay
public class DataReducerByDay extends Reducer<Text, Text,
NullWritable, Text> {
private Text outputValue = new Text();
@Override
public void reduce(Text key, Iterable<Text> values, Context
context) throws java.io.IOException,
InterruptedException {
List<String> res = concatenateLocationsByDay(values);
for (String s: res) {
outputValue.set(s);
context.write(NullWritable.get(), outputValue);
}
}
private static List<String> concatenateLocationsByDay
(Iterable<Text> listItems) {
LocalDateTime oldTimestamp = new LocalDateTime(0);
LocalDateTime currTimestamp = null;
List<String> ret = new LinkedList<String>();
Set<String> s = null;
String oldLocation = null;
String currentLocation = null;
List<Flickr> lf = new LinkedList<Flickr>();
for (Text value: listItems) {
Flickr item = new Flickr();
item.importFromString(value.toString());
lf.add(item);
}
lf.sort(new Comparator<Flickr>() {
@Override
public int compare(Flickr f1, Flickr f2) {
return (f1.getDateWithoutTime().compareTo(f2.
getDateWithoutTime()));
}
});
for (Flickr f : lf) {
currTimestamp = f.getDateWithoutTime();
if (Days.daysBetween(oldTimestamp, currTimestamp).
getDays() > 0) {
if (s != null)
ret.add(s.toString().replaceAll("\\[", "").
replaceAll("\\]", "").replaceAll(", ", "
").trim());
s = new HashSet<String>();
oldLocation = null;
oldTimestamp = currTimestamp;
210 Сравнение инструментов программирования
}
currentLocation = f.getRoi();
if (!currentLocation.equals(oldLocation)) {
s.add(currentLocation);
oldLocation = currentLocation;
}
}
if (s != null)
ret.add(s.toString().replaceAll("\\[", "").
replaceAll("\\]", "").replaceAll(", ", " ").trim());
return ret;
}
}
Ниже приводится пример результата, производимого редуктором.
stpeterbasilica
piazzadispagna
mausoleumofhadrian
stpeterbasilica
capitolinehill
trevifontain
pantheon piazzadelpopolo piazzadispagna trevifontain piazzanavona
colosseum mausoleumofhadrian
И наконец, можно применить алгоритм FPGrowth, чтобы добыть часто
встречающиеся траектории. Использованная здесь параллельная реализация алгоритма основана на работе Ли (Li и соавт., 2008), в которой был
предложен подход на основе модели MapReduce к параллельному алгоритму
FPGrowth (PFP). Указанный алгоритм использует три фазы MapReduce и пять
шагов.
1. Сегментация (шардирование): данный шаг заключается в разбивке
данных на сегменты (шарды) и их распределении по разным узлам.
2. Параллельный подсчет: на этом шаге используется подход MapReduce
для подсчета значений поддержки по всем элементам во входном наборе данных и сохраняется результат в списке часто встречающихся
элементов, отсортированных по частоте в убывающем порядке, именуемом F-List.
3. Группировка элементов: этот шаг заключается в группировке элементов
списка F-List в группы на основе их значений поддержки.
4. Параллельный алгоритм FPGrowth: это ключевой шаг PFP, на котором
используется итерация по MapReduce, чтобы выполнить параллельный
алгоритм FPGrowth на группозависимых сегментах (шардах).
5. Агрегирование: этот шаг заключается в агрегировании результатов, сгенерированных на предыдущем шаге, чтобы произвести окончательный результат.
В методе main из листинга 5.9 в конечном итоге комбинируются преобразователь, редуктор и алгоритм FPGrowth.
Сравнительный анализ на примерах приложений 211
Листинг 5.9 Класс main
public class Main extends Configured implements Tool {
private static String inputPath = "FlickrRome.json";
private static String trajOutputPath = "outputMR/";
private static String fpOutputPath = "fpOutput/";
@Override
public int run(String[] args) throws Exception {
int minimumSupport = Integer.parseInt(args[0]);
int maxPatterns = Integer.parseInt(args[1]);
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "file:///");
/** Конфигурация заявки на вычисление траекторий */
Job traJob = Job.getInstance(conf, "TrajectoryJob");
/* Настройка файловой системы */
FileInputFormat.addInputPaths(traJob, inputPath);
FileOutputFormat.setOutputPath(traJob, new Path
(trajOutputPath));
FileSystem fs = FileSystem.get(conf);
if (fs.exists(new Path(trajOutputPath)))
fs.delete(new Path(trajOutputPath), true);
traJob.setMapperClass(DataMapperFilter.class);
// Задать выходной класс ключей и значений
// для преобразователя
traJob.setMapOutputKeyClass(Text.class);
traJob.setMapOutputValueClass(Text.class);
traJob.setReducerClass(DataReducerByDay.class);
// Задать выходной класс ключей и значений
// для редуктора
traJob.setOutputKeyClass(Text.class);
traJob.setOutputValueClass(Text.class);
boolean completed;
completed = traJob.waitForCompletion(true);
if (!completed)
return 1;
/** Выполнить алгоритм FPGrowth **/
if (fs.exists(new Path(fpOutputPath)))
fs.delete(new Path(fpOutputPath), true);
FpGrowth.runFpGrowth(trajOutputPath, fpOutputPath,
minimumSupport, maxPatterns);
return 1;
}
public static void main(String[] args) throws Exception {
int res = ToolRunner.run(new Configuration(),
212 Сравнение инструментов программирования
new Main(), args);
System.exit(res);
}
}
При установке параметра minSupport равным 3 и высокого значения для
параметра maxPatterns (например, 500) описанная выше реализация в рамках
Hadoop генерирует те же правила, что и приложение в рамках фреймворка
Spark.
5.3.2. Потоковое приложение: Apache Storm
в сопоставлении с Apache Spark Streaming
В предлагаемом приложении реализуется система обнаружения вторжений
в сеть (NIDS, от англ. network intrusion detection system), ориентированная
на раннее обнаружение сетевых вторжений и вредоносных действий. Обнаружение сетевых вторжений является важной частью управления сетью с целью повышения безопасности и обеспечения качества обслуживания. В таких
системах, как правило, используются техники глубокой переработки данных
либо машинного обучения, помогающие автоматически обнаруживать атаки
на вычислительные сети и системы.
5.3.2.1. Реализация с использованием фреймворка Storm
Приложение Storm нуждается в определении трех сущностей: выпускных
кранов, поперечных задвижек и топологии. Предлагаемое приложение реа
лизовано с помощью топологии Storm, состоящей из одного крана и двух
задвижек:
ConnectionSpout: этот класс-кран является источником данных в топологии. Он передает поток подключений, которые поступают от брандмауэра или хранятся в журнале, и каждая запись перенаправляется
в виде кортежа следующей далее задвижке. В этом примере подключение обозначается набором из 41 признака, которые описывают характеристики сетевого подключения (например, продолжительность,
тип протокола и службу);
DataPreprocessingBolt: этот класс-задвижка получает кортежи из выпускного крана и выполняет несколько шагов по предобработке данных. В частности, фаза предобработки включает преобразование категориальных признаков в числовые и стандартизацию этих признаков
для модели машинного обучения;
ModelBolt: этот класс-задвижка занимается классифицированием посредством модели на основе случайного дерева, которая тренируется
в офлайновом режиме и используется в реальном времени, чтобы отслеживать подключения с целью обнаружения потенциальных вредоносных действий.
Сравнительный анализ на примерах приложений 213
Как уже было сказано ранее, фаза тренировки выполняется в офлайновом
режиме с использованием библиотеки Python scikit-learn, так как фреймворк Storm не предоставляет никаких собственных библиотек машинного
обучения. Все натренированные модели (то есть стандартный шкалировщик
для числовых признаков, кодировщик меток для категориальных признаков
и модель, обучающаяся на основе случайного леса) сбрасываются в файлы с помощью Python’овского модуля pickle. Таким образом, мультиязычный протокол Storm можно использовать для интеграции натренированных
моделей в топологию, реализованную на языке виртуальной машины Java
(JVM). На рис. 5.2 показана вся архитектура предлагаемого приложения.
Офлайновая тренировка в Python
Тренировочный
набор
Предобработка
Шкалировщик
Кодировщик
Тренировка модели
Модель
случайного
леса
1. smurf
2. phf
3. normal
4. buffer overflow
5. guess passwd
6. load module…
Топология Apache Storm
Выпускной кран
подключений
Поперечная
задвижка
предобработки
данных
Поперечная
задвижка
в виде модели
Тип атаки
Рис. 5.2 Архитектура предлагаемого приложения Storm
Топология Storm, реализованная с использованием языка Java, показана
в листинге 5.10. Исходный код начинается с создания конфигурационного
объекта, который используется для указания различных параметров топологии, таких как количество узлов-работников в кластере. Следующим шагом является создание самой топологии с помощью класса TopologyBuilder.
Топология представляется в виде ориентированного ациклического графа
вычислительных компонентов, в котором данные перетекают из выпускных
кранов к поперечным задвижкам. Затем в исходном коде в топологию добавляются три определенных выше компонента, а именно кран и две задвижки.
Метод shuffleGrouping между ConnectionSpout, DataPreprocessingBolt и ModelBolt
указывает на то, что перед отправкой данных в следующий компонент они
должны быть случайно перемешаны, что позволяет распределять их более
равномерно. После того как топология полностью определена, она передается в кластер с помощью метода StormSubmitter.submitTopology, который
214 Сравнение инструментов программирования
принимает три аргумента: имя топологии, конфигурационный объект и саму
топологию, созданную с помощью метода createTopology(). Запущенная
в кластере топология будет обрабатывать данные по мере их поступления.
Листинг 5.10 Топология Storm
public class IntrusionTopology {
public static void main(String[] args) {
// Собрать и передать топологию в кластер на обработку
Config conf = new Config();
conf.setNumWorkers(20);
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("spout", new ConnectionSpout());
builder.setBolt("process", new DataPreprocessingBolt())
.shuffleGrouping("spout");
builder.setBolt("model", new ModelBolt())
.shuffleGrouping("process");
StormSubmitter.submitTopology("IntrusionDetection",
conf, builder.createTopology());
}
}
Каждый узел-кран должен указывать коллектору, используемому для эмитирования кортежей (метод open), как эмитировать очередной кортеж (метод
nextTuple) и объявлять выходные поля для эмитируемых кортежей (метод
declareOutputFields), как показано в листинге 5.11. Эмитируемый выпускным
краном кортеж представляет собой описывающее подключение множество
из 41 признака (например, продолжительность, тип протокола). Каждому
полю кортежа назначается имя, ранее сохраненное в массиве строковых литералов под названием field_names.
Листинг 5.11 Выпускной кран ConnectionSpout в Storm
public class ConnectionSpout implements IRichSpout {
private SpoutOutputCollector collector;
// Задать имя каждого столбца в тренировочных данных
private String[] field_names = new String[]{"duration",
"protocol_type", "service", ...};
@Override
public void open(Map conf, TopologyContext context,
SpoutOutputCollector collector) {
// Задать коллектор, подлежащий использованию
// для эмитирования кортежей
this.collector = collector;
}
@Override
public void nextTuple() {
// Прочитать из журнального файла
Сравнительный анализ на примерах приложений 215
while ((str = reader.readLine()) != null) {
String[] fields = str.split(",");
// Эмитировать кортеж из входного файла
this.collector.emit(new Values(fields[0], ... ,
fields[40]));
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer dec) {
// Объявить имя каждого поля кортежа
dec.declare(new Fields(field_names[0], ...,
field_names[40]));
}
}
Каждый кортеж эмитируется выпускным краном и обрабатывается последующими задвижками, как объявлено в топологии. В данном случае класс
DataPreprocessingBolt из листинга 5.12 представляет задвижку Python, определенную в листинге 5.13. Python’овский скрипт этой задвижки обрабатывает кортеж, применяя преобразования набора загруженных с диска моделей
(например, кодировщики для категориальных признаков и шкалировщики для числовых признаков), которые были предварительно натренированы в офлайновом режиме на тренировочном наборе данных нормальных
и вредоносных подключений. Мультиязычный протокол требует указания
в задвижке лишь одного скрипта, подлежащего исполнению путем вызова
метода super (см. листинг 5.12), тогда как вся логика приложения содержится
в скрипте Python.
Листинг 5.12 Поперечная задвижка DataPreprocessingBolt в Storm
public class DataPreprocessingBolt extends ShellBolt
implements IRichBolt {
public DataPreprocessingBolt() {
// Применить многоязычный протокол, чтобы выполнить
// скрипт на языке Python
super("python3", "preprocessingBolt.py");
}
}
Листинг 5.13 Класс DataPreprocessingBolt на языке Python
class preprocessingBolt(storm.BasicBolt):
# Загрузить кодировщики на типе протокола,
# службе и флаге
label_enc_prot = pickle.load(open("label_enc_prot", 'rb'))
label_enc_serv = pickle.load(open("label_enc_serv", 'rb'))
label_enc_flag = pickle.load(open("label_enc_flag", 'rb'))
# Загрузить стандартный шкалировщик для числовых признаков
standard_scaler = pickle.load(open("standard_scaler", 'rb'))
216 Сравнение инструментов программирования
# Пометить номинальные, двоичные и числовые признаки
nominal_idx, binary_idx = [1, 2, 3], [6, 11, 13, 14, 20, 21]
numerical_idx = list(set(range(41)).difference
(nominal_idx).difference(binary_idx))
def process(self, tuple):
# Закодировать категориальные признаки
# protocol type = nominal_idx[0]
prot_type = label_enc_prot.transform([tuple.values
[nominal_idx[0]]])[0]
# service = nominal_idx[1]
serv = label_enc_serv.transform([tuple.values
[nominal_idx[1]]])[0]
# flag = nominal_idx[2]
flag = label_enc_flag.transform([tuple.values
[nominal_idx[2]]])[0]
# Прошкалировать числовые признаки
scaled_features = standard_scaler.transform
(np.reshape([float(tuple.values[i]) for i in
numerical_idx], (1, -1)))[0]
# Эмитировать кортеж после обработки
storm.emit([scaled_features[0], prot_type, serv,
flag, ... ])
preprocessingBolt().run()
Наконец, задвижка ModelBolt из листинга 5.14 действует аналогично задвижке DataPreprocessingBolt, определенной в листинге 5.12. В частности,
в ней применяется модель с использованием алгоритма случайного леса,
натренированная в офлайновом режиме с помощью скрипта Python, описанного в листинге 5.15. Предсказанный тип подключения из набора 23 типов,
включая смурф-атаки, переполнение буфера и угадывание пароля, позволяет
инфраструктуре обеспечения сетевой безопасности реагировать и смягчать
возможные угрозы.
Листинг 5.14 Поперечная задвижка ModelBolt в Storm
public class ModelBolt extends ShellBolt implements
IRichBolt {
public ModelBolt() {
// Применить многоязыковой протокол, чтобы выполнить
// скрипт на языке Python
super("python3", "modelBolt.py");
}
}
Листинг 5.15 Поперечная задвижка ModelBolt на языке Python
class modelBolt(storm.BasicBolt):
# Загрузить модель случайного леса с диска
model = pickle.load(open("rf_model", 'rb'))
Сравнительный анализ на примерах приложений 217
def process(self, tuple):
# Предсказать тип подключения из набора 23 типов
prediction = model.predict(np.reshape([tuple.
values[0], tuple.values[1],..., tuple.values
[40]], (1, -1)))[0]
# Эмитировать предсказанный тип подключения
storm.emit([prediction])
modelBolt().run()
5.3.2.2. Реализация с использованием библиотеки
Spark Streaming
В продолжение нашего обсуждения реализации системы обнаружения вторжений с помощью фреймворка Storm теперь будет проиллюстрировано то же
самое реально-временное приложение, разработанное с помощью библиотеки Spark Streaming, предлагаемой во фреймворке Apache Spark для масштабируемой, высокопроизводительной и отказоустойчивой потоковой обработки.
Указанная библиотека поддерживает многочисленные источники внесения
данных, включая Apache Kafka, Amazon Kinesis и сокеты TCP, и задействует
мощь распределенной обработки фреймворка Apache Spark и его встроенных
библиотек, таких как Spark SQL и Spark MLlib, с целью обеспечения сквозного
решения по потоковой обработке. После внесения данных они могут быть
преобразованы и обработаны с помощью продвинутых алгоритмов (например, машинного обучения и графовой обработки), чтобы затем генерировать
содержательные сведения и предсказания. Обработанные данные можно
сохранять в самых разных системах хранения, таких как Apache Cassandra,
Apache HBase или Amazon S3, для дальнейшего анализа и принятия решений.
Библиотека Spark Streaming предоставляет высокоуровневую абстракцию
для непрерывных потоков данных, именуемую дискретизированным потоком (DStream). DStream – это непрерывная последовательность RDD-наборов
данных, где каждый RDD-набор собирает данные за определенный промежуток времени, а все операции, выполняемые на дискретизированном потоке, перенаправляются в базисные RDD-наборы. Такая абстракция позволяет
разработчикам выполнять преобразования и действия на входящих данных
в реальном времени, без необходимости хранить все данные в памяти.
Первая часть приложения включает в себя офлайновую тренировку модели
машинного обучения, в частности классификатора на основе алгоритма случайного леса, на тренировочных данных. А именно выполняются следующие
ниже шаги:
1) загрузить CSV-файл в кадр данных с использованием источника CSVданных Spark и схемы для назначения имени каждому столбцу;
2) случайно разбить данные на тренировочный и тестовый наборы
с целью тренировки и дальнейшего оценивания модели перед ее использованием в условиях реального времени;
218 Сравнение инструментов программирования
3) определить шаги предобработки:
– использовать класс VectorAssembler для комбинирования нескольких
столбцов в один столбец признаков;
– использовать класс StringIndexer для конвертации целевого столбца
категориальных меток в столбец числовых меток;
– использовать класс StandardScaler для шкалирования признаков
с целью придания им нулевого среднего значения и единичной дисперсии;
4) определить класс RandomForestClassifier в качестве модели машинного
обучения, подлежащей тренировке;
5) создать конвейер, чтобы скомбинировать шаги предобработки в нужном порядке, и затем натренировать модель на тренировочных данных;
6) применить натренированный конвейер, чтобы сгенерировать предсказания на тестовых данных;
7) оценить точность модели на тестовых данных с помощью класса MulticlassClassificationEvaluator;
8) сохранить натренированный конвейер и натренированную модель RandomForestClassifier на диске.
В листинге 5.16 показано, как с помощью библиотеки Spark MLlib тренируется, оценивается и сохраняется на диске конвейер предобработки и классификатор на основе случайного леса для дальнейшего реиспользования.
Листинг 5.16 Офлайновая тренировка классификатора на основе случайного
леса с использованием библиотеки Spark MLlib с целью
обнаружения вторжений
val df = spark.read.format("csv").option("header",
"false").schema(connectionSchema).load(path_df)
val Array(trainingData, testData) = df.randomSplit(Array(0.7, 0.3))
// Определить шаги предобработки
val assembler = new VectorAssembler()
.setInputCols(Array("duration", "protocol_type", "service", "..."))
.setOutputCol("features")
val indexer = new StringIndexer().setInputCol
("attack_type").setOutputCol("indexed_label")
val scaler = new StandardScaler().setInputCol
("features").setOutputCol("scaledFeatures").
setWithStd(true).setWithMean(false)
val rf = new RandomForestClassifier().setFeaturesCol
("scaledFeatures").setLabelCol("indexed_label")
// Поместить шаги предобработки в конвейер
val pipeline = new Pipeline().setStages(Array(assembler,
indexer, scaler, rf))
// Натренировать конвейер на тренировочных данных
Сравнительный анализ на примерах приложений 219
val pipelineModel = pipeline.fit(trainingData)
val predictions = pipelineModel.transform(testData)
// Сохранить конвейер и модель случайного леса
// на диске для реиспользования
pipelineModel.write.overwrite().save("pipeline_model")
pipelineModel.stages(2).asInstanceOf
[RandomForestClassifier].write.overwrite().save("rf_model")
// Оценить точность на тестовых данных
val evaluator = new MulticlassClassificationEvaluator()
.setLabelCol("indexed_label")
.setPredictionCol("prediction")
.setMetricName("accuracy")
val accuracy = evaluator.evaluate(predictions)
spark.stop()
После тренировки и тестирования модели машинного обучения ее можно
использовать для реально-временного обнаружения вредоносных подключений. Для этого необходимо загрузить модель, которая ранее была сохранена диске, и использовать ее совместно с потоковыми API-интерфейсами
библиотеки Spark Streaming, которые применяются для обработки входного потока. В листинге 5.17 показана реально-временная часть приложения
Spark Streaming.
Листинг 5.17 Обнаружение вторжений в сеть с помощью Spark Streaming
val conf = new SparkConf().setMaster("...")
// Потоковый контекст с пакетным интервалом в 1 секунду
val ssc = new StreamingContext(conf, Seconds(1))
// Создать поток DStream, который подключится к hostname:port
val data: ReceiverInputDStream[String] = ssc.
socketTextStream(hostname, port)
// Обработать данные в окне длиной windowLength и с
// итервалом скольжения slideInterval
val dataInWindow = data.window(Seconds(30), Seconds(10)).map
{line =>
val col = line.split(",")
ConnectionTest(col(0).toDouble, col(1).toInt, ...,
col(40).toInt) }
// Загрузить натренированную модель и шкалировщик
val pipelineLoaded = PipelineModel.load("pipeline_model")
// Применить преобразования к каждому подлежащему RDD-набору
// и получить предсказания
dataInWindow.foreachRDD { rdd =>
pipelineLoaded.transform(rdd.toDF()).show()
}
220 Сравнение инструментов программирования
// Запустить вычисление и дожидаться его завершения
ssc.start()
ssc.awaitTermination()
Первым делом в исходном коде создается объект SparkConf и устанавливается узел-мастер для кластера Spark. Затем создается объект StreamingContext с пакетным интервалом в 1 секунду – он является точкой входа для
всех потоковых операций. После определения контекста последующие шаги
таковы: (i) создание входных потоков типа DStream для указания источников
входных данных; (ii) применение операций преобразования и вывода к потокам DStream для определения потоковых операций; (iii) запуск получения
и обработки данных путем вызова операции start() на потоковом контексте
и (iv) ожидание остановки обработки (вручную либо из-за ошибки) посредством операции awaitTermination(). В дополнение к этому обработку можно
останавливать вручную с помощью операции streamingContext.stop().
Объект StreamingContext используется для получения и сбора входящих
данных с указанного хоста и порта через сокет-соединение в форме потока
ReceiverInputDStream строковых литералов. Данные также можно получать из
файлов, но файловая система должна быть совместима с API HDFS (например, HDFS, S3 и NFS). Для создания потока DStream в этом сценарии можно
использовать инструкцию streamingContext.textFileStream(dataDirectory).
Благодаря ей Spark Streaming имеет возможность отслеживать указанный
каталог и обрабатывать любые файлы, созданные в этом каталоге. Все файлы,
расположенные непосредственно по этому пути, будут обрабатываться по
мере их обнаружения, но обновления файлов в текущем окне будут игнорироваться. Важно помнить, что чем больше файлов в каталоге, тем дольше
будет длиться сканирование на предмет изменений, даже если ни один из
файлов не был изменен. Наконец, стоит отметить, что в поток будут включены только те файлы, время модификации которых попадает в текущее окно.
Полученные данные обрабатываются с целью извлечения релевантных
признаков и организовываются в окна длиной 30 с интервалом скольжения
10 секунд. Каждая строка входящих данных разбивается на столбцы с помощью метода split, а затем конвертируется в объект ConnectionTest, который представляет собой соединение со всеми описанными ранее полями.
Натренированный конвейер загружается с диска и применяется к каждому
RDD-набору в потоке DStream. Метод transform шкалировщика используется
для нормализации признаков, а метод predict модели RandomForestClassifier – для предсказания обработанных данных. Еще одним важным моментом
является то, что вычисления в потоке инициируются только при вызове оператора вывода; в противном случае без оператора вывода на потоке DStream
никаких вычислений не вызывается, подобно действиям на RDD-наборах
данных. В настоящее время можно вызывать следующие операторы вывода:
print, saveAsTextFiles, saveAsObjectFiles, saveAsHadoopFiles и foreachRDD. Оператор foreachRDD является наиболее обобщенным оператором вывода и используется для применения функции к каждому RDD-набору, созданному
потоком. Переданная на вход оператору функция отвечает за отправку дан-
Сравнительный анализ на примерах приложений 221
ных из каждого RDD-набора во внешнюю систему, например сохранение их
в файлы или передачу по сети в базу данных.
Одной из особенностей библиотеки Spark Streaming является возможность
выполнения оконных операций, аналогичных тем, что предлагает фреймворк
Apache Storm. Оконные операции позволяют обрабатывать скользящее окно
данных, комбинировать все RDD-наборы, которые помещаются в окно, и их
обрабатывать, чтобы генерировать оконный поток DStream. Для того чтобы
выполнять оконную операцию, программисты должны указывать длительность окна и интервал, через который такая операция выполняется. Одним
из главных преимуществ библиотеки Spark Streaming перед фреймворком
Apache Storm является полная интеграция с библиотекой MLlib, что позволяет использовать широкий спектр алгоритмов для офлайнового обучения
без необходимости использования внешних библиотек.
Вдобавок библиотека Spark Streaming поддерживает потоковые алгоритмы
машинного обучения, которые могут одновременно учиться и предсказывать
на основе потока данных (например, потоковую линейную регрессию и потоковый алгоритм K-средних). Указанная библиотека также поддерживает
язык программирования Scala, что приводит к созданию более компактного
и удобочитаемого исходного кода. Однако стоит отметить, что в библиотеке Spark Streaming принята микропакетная обработка, в отличие от чисто
потоковой обработки, как во фреймворке Apache Storm. По этой причине
необходимо задавать интервал пакетной обработки, который в приложении
Spark Streaming является одним из критически важных гиперпараметров.
В целях обеспечения стабильности приложения оно должно обрабатывать
данные с той же скоростью, с какой они поступают, то есть пакеты должны
обрабатываться с тем же темпом, с каким они генерируются. В связи с этим
на используемый интервал между пакетами сильно влияет скорость передачи данных.
5.3.3. Приложение на основе SQL: Apache Hive
в сопоставлении с Apache Spark SQL
Одним из ведущих трендов в исследованиях в области социальных сетей является анализ геотегированных данных с целью определения посещаемости
пользователями интересных мест, обычно именуемых точками заинтересованности (PoI), таких как туристические достопримечательности, торговые
центры, площади и парки. Как было показано в разделе 5.3.1, для соотнесения траекторий передвижения пользователей с точками заинтересованности
нередко бывает полезно определять так называемую область заинтересованности (RoI), то есть границы территории точек заинтересованности.
Ниже будет рассмотрено приложение по анализу данных, позволяющее
извлекать подходящую область заинтересованности для той или иной точки
путем анализа крупного геотегированного набора постов, собранных из со-
222 Сравнение инструментов программирования
циальной сети. Пост g считается геотегированным, если он содержит пару
координат (широту, долготу), идентифицирующих место создания поста g.
Более того, пост g может быть ассоциирован с точкой заинтересованности p,
если его текст или теги относятся к точке p. Например, пользователи социальных сетей обозначают Колизей (Coliseum) в своих постах разными ключевыми словами, такими как Coliseum, Coliseo или Colise, и синонимами, такими
как Flavian Amphitheatre или Amphitheatrum Flavium.
После извлечения всех координат постов социальной сети, относящихся
к конкретной точке заинтересованности (PoI), применяется алгоритм пространственной кластеризации (например, DBSCAN), чтобы получить область
заинтересованности (RoI). Такая область представляет собой многоугольник,
охватывающий наибольший кластер точек, обнаруженных алгоритмом.
5.3.3.1. Реализация с использованием фреймворка Hive
Как говорилось в разделе 4.6.1, фреймворк Hive позволяет внедрять продвинутую обработку данных в запрос путем определения разных типов функций
(то есть чисто пользовательских, пользовательских агрегатных либо пользовательских функций генерации таблиц). В частности, разработчики могут
определять два типа пользовательских агрегатных функций: простые и обобщенные. Простые агрегатные функции довольно просто писать, но они могут
иметь проблемы с производительностью из-за использования рефлексии
Java и не позволяют использовать такие средства, как списки аргументов
переменной длины. С другой стороны, обобщенные пользовательские агрегатные функции позволяют применять все эти средства, но их написание,
возможно, не будет столь же интуитивно понятным, как написание простых
агрегатных функций.
С целью реализации приложения нативно в рамках Hive были созданы две
конкретно-прикладные функции:
GeoDataUDF, которая, получив текст поста, выполняет проверку на наличие в нем хотя бы одного тега, ссылающегося на точку заинтересованности;
DbscanUDAF, которая с учетом набора действительных координат, извлеченных из постов, использует алгоритм DBSCAN, чтобы извлечь
кластер, представляющий область точек заинтересованности.
Пользовательская функция GeoDataUDF показана в листинге 5.18. В частности, она написана путем расширения абстрактного класса GenericUDF,
который, в отличие от простейшего класса пользовательских функций, использует концепцию ObjectInspector. Экземпляр класса ObjectInspector для
конкретного типа, такого как нативный объект Java (например, String, Double
и Collection), позволяет программистам напрямую обращаться к внутренним
полям и методам этого типа. Следовательно, все взаимодействия с данными, передаваемыми в пользовательские функции, осуществляются через
объекты ObjectInspector, которые позволяют читать входные значения из
параметра пользовательской функции и писать выходные значения. Глядя
Сравнительный анализ на примерах приложений 223
внимательно на исходный код листинга 5.18, следует отметить, что в нем
переопределяются следующие ниже методы из класса GenericUDF:
initialize: инициализирует объект GenericUDF. В частности, этот метод
вызывается один и только один раз для каждого экземпляра GenericUDF.
Он отвечает за проверку входных аргументов (типов и длины) и возвращает ObjectInspector для возвращаемого значения;
evaluate: реализует логику работы функции GenericUDF. При вызове он
применяет функцию с аргументами к входным объектам, которые могут быть проверены объектом ObjectInspector, переданным в методе
initialize;
getDisplayString: определяет строковый литерал, подлежащий выводу
на экран при вызове команды EXPLAIN в Hive. Команда Hive EXPLAIN используется для показа плана исполнения, который механизм запросов
Hive генерирует и использует при исполнении любого запроса. Такая
команда очень полезна на практике для понимания потока исполнения
запроса при попытке его оптимизировать.
Листинг 5.18 Функция GeoDataUDF для обработки постов в социальных сетях
public class GeoDataUDF extends GenericUDF {
private StringObjectInspector input;
private StringObjectInspector input2;
@Override
public ObjectInspector initialize(ObjectInspector[]
args) throws UDFArgumentException {
// выполнить проверку на наличие всего 1 аргумента
if (args.length != 2)
throw new UDFArgumentException("input must have length 2");
// задать объекты экземпляра ObjectInspector из входных аргументов, прежде
// проверив, что они оба являются строковыми литералами
if (!(args[0] instanceof StringObjectInspector))
throw new UDFArgumentException("input 1 must be
a string object");
if (!(args[1] instanceof StringObjectInspector))
throw new UDFArgumentException("input 2 must be
a string object");
this.input = (StringObjectInspector) input;
this.input2 = (StringObjectInspector) input2;
// вернуть объект ObjectInspector, подлежащий
// использованию в качестве возвращаемого значения
return PrimitiveObjectInspectorFactory.
javaStringObjectInspector;
}
@Override
public Object evaluate(DeferredObject[] args) throws
HiveException {
// Проверить правильность количества и значения входных аргументов
224 Сравнение инструментов программирования
if (input == null || input2 == null || args.length
!= 2 || args[0].get() == null || args[1].get() == null)
return null;
// Получить текстовое содержимое поста социальной сети из args[0]
String postText = input.getPrimitiveJavaObject(args
[0].get()).toString().toLowerCase();
// Получить список ключевых слов,
// идентифицирующих точку заинтересованности из args[1]
String[] forwardKeywordsArray = input2.
getPrimitiveJavaObject(args[1].get()).toString().split(",");
// Проверить, содержится ли в тексте поста хотя бы одно ключевое слово
for (String k : forwardKeywordsArray) {
if (postText.contains(k.toLowerCase())) {
// вернуть первое ключевое слово, идентифицирующее точку заинтересованности
return forwardKeywordsArray[0];
}
}
// Вернуть null, если пост невозможно связать с точкой заинтересованности
return null;
}
@Override
public String getDisplayString(String[] strings) {
return getStandardDisplayString("geoData", strings);
}
}
В листинге 5.19 показана реализация функции DbscanUDAF как простой
пользовательской агрегатной функции. Внутри класса DbscanUDAF определен
статический класс-оценщик DbscanUDAFEvaluator, который реализует интерфейс UDAFEvaluator. Фреймворк Hive выполняет данную агрегатную функцию,
вызывая последовательно следующие ниже методы класса-оценщика:
init, который инициализирует оценщик пользовательской агрегатной
функции, устанавливая пустой частичный результат (то есть состояние
частичной агрегации), указывающий на то, что никакие значения еще
не были агрегированы;
iterate, который вызывается при каждом появлении нового значения,
подлежащего агрегированию;
terminatePartial, который вызывается для возврата частично агрегированного результата;
merge, который вызывается, когда фреймворку Hive нужно скомбинировать одну частичную агрегацию с другой;
terminate, который вызывается, когда необходимо вычислить окончательный результат агрегирования.
В частности, во время исполнения метода iterate функция агрегирует доступные значения, сохраняя координаты (широты, долготы) и параметры
(eps и minPts) внутри статического объекта (DbscanGeoDataset, который используется для хранения состояния частичного агрегирования). Затем Hive
агрегирует другие локальные агрегации, вызывая метод merge. Наконец, Hive
Сравнительный анализ на примерах приложений 225
вызывает метод terminate, чтобы сгенерировать окончательный результат
путем исполнения экземпляра DBSCAN. В частности, здесь использована
реализация алгоритма DBSCAN для геопространственной кластеризации,
которая входит в состав фреймворка ELKI1.
Листинг 5.19 Функция GeoDataUDF для обработки постов из социальных сетей
@Description(name = "dbscan",
value = "_FUNC_(lat, lng, eps, minPts) – Возвращает
самый большой кластер, найденный
алгоритмом dbscan в списке геоточек"
)
public class DbscanUDAF extends UDAF {
public static class DbscanUDAFEvaluator implements
UDAFEvaluator {
// статический класс, используемый для хранения состояния агрегации
public static class DbscanGeoDataset {
private List<String> points = new
LinkedList<>();
private double eps = 0;
private int minPts = 0;
}
// статический объект, хранящий состояние локальной агрегации
private DbscanGeoDataset dataset = null;
// Конструктор оценщика
public DbscanUDAFEvaluator() {
super(); init();
}
// Инициализировать состояние агрегации как пустое
// (ни одно значение еще не было агрегировано)
@Override
public void init() {
dataset = new DbscanGeoDataset();
}
// Вызывается для агрегирования каждой новой координаты. Параметры
// DBSCAN поддерживаются в агрегированном состоянии
public boolean iterate(double latitude, double
longitude,
double eps, int minPts) throws HiveException {
if (dataset == null)
throw new HiveException("DBSCAN dataset is
not initialized");
if (latitude > 0 && longitude > 0) {
// Добавить действительные координаты и
1
См. https://elki-project.github.io/.
226 Сравнение инструментов программирования
// параметры в локальное состояние
dataset.points.add(longitude + "," + latitude);
dataset.eps = eps;
dataset.minPts = minPts;
return true;
} else
return false;
}
// Вернуть частично агрегированные результаты
public DbscanGeoDataset terminatePartial() {
return dataset;
}
// Объединить два частичных результата (то есть объединить точки,
// найденные в одном наборе данных)
public boolean merge(DbscanGeoDataset other) throws
HiveException {
if (other != null) {
this.dataset.points.addAll(other.points);
this.dataset.minPts = other.minPts;
this.dataset.eps = other.eps;
}
return true;
}
// Вычислить окончательный результат, исполнив экземпляр DBSCAN
public String terminate() throws HiveException {
// Подготовить экземпляр DBSCAN с заданными параметрами и полностью
// агрегированным списком точек
ElkiDBSCAN dbscan = new ElkiDBSCAN(
this.dataset.points.stream().map(p -> {
String[] data = p.split(",");
return new GeoPoint(Double.
parseDouble(data[0]),
Double.parseDouble(data[1]));
}).collect(Collectors.toList()),
this.dataset.eps, this.dataset.minPts);
// Выполнить алгоритм DBSCAN
dbscan.cluster();
// Найти самый крупный кластер
List<GeoCluster> clusters = dbscan.
getAllGeoClusters(false);
int max = -1;
GeoCluster maxCluster = null;
for (GeoCluster c : clusters) {
if (c.getPoints().size() > max) {
maxCluster = c;
max = c.getPoints().size();
}
Сравнительный анализ на примерах приложений 227
}
// Конвертировать кластер в многоугольник в формате KML
if (maxCluster != null) {
try {
Shape clusterShape = GeoUtils.convexHull
(maxCluster.getPoints());
return KMLUtils.serialize(clusterShape);
} catch (IOException e) {
return "ERROR_NOT_FOUND";
}
} else return "NOT_FOUND";
}
}
}
5.3.3.2. Реализация с использованием модуля Spark SQL
Spark SQL – это модуль, служащий для управления структурированными данными и их обработки. Он отличается от API RDD тем, что расширяет фреймворк Spark опциями, основанными на информации о структуре данных и выполняемых вычислениях. Разработчики могут взаимодействовать с модулем
Spark SQL посредством запросов на языке SQL и API Dataset, используя один
и тот же механизм исполнения. Помимо исполнения SQL-запросов, модуль
Spark SQL можно применять для чтения данных из существующей среды
Hive. Модуль Spark SQL предлагает две высокоуровневые абстракции, а именно Dataset и DataFrame, как подробно описано в разделе 4.3.1.1. Типы данных
выявляются автоматически, но разработчики могут предоставлять схему
в явной форме через StructType, соответствующую структуре DataFrame в именованных столбцах. Spark SQL поддерживает подавляющее большинство
функций Hive, таких как пользовательские функции и операции на кадрах
данных. В листинге 5.20 показана реализация приложения по добыче областей заинтересованности, которое было рассмотрено в предыдущем разделе, с помощью API Dataset модуля Spark SQL на языке программирования
Python.
Листинг 5.20 Приложение по добыче областей заинтересованности в рамках
Hive с помощью Spark SQL
import findspark
findspark.init('/opt/spark')
from pyspark.sql import SparkSession
# Инициализировать сеанс Spark с поддержкой Hive
spark = SparkSession.builder.enableHiveSupport().getOrCreate()
# Загрузить посты Flickr из HDFS
flickrData=spark.read.json("hdfs://master/user/custom/allFlickrRome.json")
# Создать внутреннюю таблицу Hive
flickrData.write.mode("overwrite").saveAsTable("posts")
228 Сравнение инструментов программирования
# Создать пользовательскую функцию и такую же агрегатную функцию из JAR-файла,
# хранящегося в HDFS
spark.sql("ADD JAR hdfs://master/user/custom/HiveUDF-1.0-\
SNAPSHOT-jar-with-dependencies.jar")
spark.sql("CREATE FUNCTION GeoData AS 'it.unical.dimes.
scalab.hive.udf.GeoDataUDF'");
spark.sql("CREATE FUNCTION GeoDbscan AS 'it.unical.dimes.\
scalab.hive.udf.DbscanUDAF'");
# Задать список ключевых слов, используемых пользователями социальных сетей для
# идентификации Колизея
keywords = "colosseum, colosseo, colis, collis, amphitheatrum flavium, \
colasaem, coliseo, coliseu, coliseum, coliseus, ..."
# Найти области заинтересованности для Колизея
spark.sql("SELECT GeoDbscan(longitude, latitude, 150, 50) AS
cluster FROM posts WHERE
GeoData(description,'"+keywords+"') IS NOT NULL
AND latitude > 0 AND longitude > 0").show(truncate=False)
На рис. 5.3 показан пример области заинтересованности (RoI), полученной
в результате применения исходного кода Hive к набору геотегированных
постов, собранных в социальной сети Flickr.
Рис. 5.3 Пример области заинтересованности, обнаруженной для Колизея
с помощью функции DbscanUDAF на наборе постов из социальной сети Flickr.
Источник: снимок из OpenStreetMap, https://openstreetmap.org/copyright
Стоит отметить, что при исполнении запроса модуль Spark SQL всегда использует один и тот же механизм, независимо от того, какой API применяется
(то есть API SQL либо API Dataset). Подобное единообразие дает большое
преимущество разработчикам, так как они могут легко переключаться между
двумя API в зависимости от того, какой из них больше подходит для выражения определенного преобразования.
Простота, с которой фреймворк Spark позволяет писать функции обработки данных, также говорит о том, что он гораздо практичнее в использова-
Сравнительный анализ на примерах приложений 229
нии, чем фреймворк Hive. Следует признать, что по сравнению с фреймворком Spark использование пользовательских и агрегатных пользовательских
функций во фреймворке Hive в качестве сложных функций анализа данных сопряжено с трудностями. Этот факт подчеркивает истинную природу фреймворка Hive, который представляет собой не что иное, как систему
хранения для чтения, записи и управления структурированными данными
объемом до петабайта, но не предназначен для сложных операций по глубокой переработке данных или аналитике данных. Хотя фреймворк Hive
предлагает крупный набор функций агрегации и преобразования данных,
которые можно интегрировать непосредственно в запросы на языке SQL,
они не охватывают случаи, когда необходимо выполнять сложные алгоритмы
обработки и глубокой переработки данных: в этих случаях гораздо удобнее
использовать фреймворк Spark с модулем Spark SQL.
5.3.4. Графовое приложение: MPI в сопоставлении
с Apache Spark GraphX
В этом разделе будет рассмотрена реализация приложения по экстрактивному резюмированию, сравнив два инструмента программирования, эффективно используемых для производительных графовых вычислений, а именно
фреймворк MPI и библиотеку GraphX, доступную в рамках фреймворка Spark.
Экстрактивное резюмирование (также квазиреферирование)1 – это тип
техники резюмирования текста, который предусматривает выявление и извлечение наиболее важной информации из документа и ее представление
в краткой сводке. В частности, сводка создается путем упорядочивания наиболее важных предложений или фраз исходного текста. Подобные техники резюмирования, как правило, более легковесны по сравнению с теми,
которые генерируют новый текст, то есть техниками абстрактивного резюмирования, которые предусматривают понимание смысла и контекста
изначального текста и создание сводки наподобие сводки, написанной человеком, с помощью более продвинутых техник обработки естественного
языка, таких как самоконтролируемые большие языковые модели. Экстрактивное резюмирование имеет целый ряд областей применения. В частности,
оно используется при резюмировании новостных статей, когда необходимо
быстро и точно резюмировать крупный объем текста для читателей, а также
в академических исследованиях, чтобы помогать научным исследователям
оперативно понимать ключевые выводы и предложения, содержащиеся
в статье.
В этом разделе мы сосредоточимся на технике резюмирования TextRank
(Mihalcea и Tarau, 2004), которая представляет текст в виде графа семанти1
Экстрактивное резюмирование (extractive summarization) – это создание сводки,
содержащей подмножество предложений исходного текста после выделения в нем
важных предложений. – Прим. перев.
230 Сравнение инструментов программирования
чески связанных предложений и извлекает сводку, определяя верхние k наиболее представительных предложений по рангу PageRank. В общем случае,
если задан подлежащий резюмированию текст T, алгоритм создает граф G =
<S, E>, где S = s1, ..., sn – это n предложений, присутствующих в T. Множество E,
содержащее ребра графа, создается путем соединения каждой пары предложений в S, где каждое соединение ассоциируется с весом wi,j, представляющим текстовое сходство между предложениями si и sj. Сходство между двумя
предложениями si и sj определяется по следующей ниже формуле:
Согласно этой формуле, сходство между двумя предложениями прямо пропорционально количеству общих слов (то есть их пересечению), но обратно
пропорционально их длине, исходя из допущения, что очень длинные фразы
могут иметь высокую степень пересечения друг с другом, что не обязательно
влечет за собой большое сходство. Таким образом, множество ребер будет
представлять собой множество троек E =< si, sj, wi, j > ∀si, sj ∈ S × S, что приводит к неориентированному, полному, взвешенному графу семантически
связанных предложений.
Имея такой граф, можно задействовать алгоритм PageRank (Brin и Page,
1998) (подробно рассмотренный в разделе 4.5.1.4), чтобы извлечь наиболее
важные вершины, то есть наиболее представительные предложения изначального текста. Наконец, эти предложения конкатенируются и возвращаются на выходе в виде добытой сводки. Далее будут продемонстрированы
в сравнении две разные реализации описанного выше алгоритма, которые
были разработаны с использованием фреймворка MPI и API Pregel библиотеки GraphX. Реализации подробно описаны, а сравнение сосредоточено на
главных преимуществах и недостатках, возникающих при использовании
обоих инструментов разработки.
5.3.4.1. Реализация с использованием библиотеки GraphX
Сначала мы обсудим реализацию приложения в рамках системы Apache
Spark с использованием языка Scala и библиотеки GraphX для графопараллельных вычислений на основе абстракции передачи сообщений с массовым синхронным параллелизмом (BSP). Мы применили Pregel-подобные
API-интерфейсы, предоставляемые библиотекой GraphX, чтобы реализовать
взвешенную версию алгоритма PageRank, в которой при вычислении балла
каждого узла учитывается сила связи между двумя узлами. Главные части,
которые составляют разработанную реализацию, описаны ниже.
Прежде всего создается контекст Spark и определяется простой метод на
языке Scala, служащий для измерения текстового сходства между двумя заданными предложениями, полученными по представленной выше формуле
(см. листинг 5.21). Здесь кардинальность пересечения в сущности делится на
Сравнительный анализ на примерах приложений 231
сумму логарифмических длин двух входных фраз. Знаменатель корректируется во избежание логарифмического нуля или деления на ноль.
Листинг 5.21 Определить контекст Spark и сходство предложений
// Создать сеанс Spark
val spark = SparkSession
.builder.master("local")
.appName("Spark-GraphX-TextRank")
.getOrCreate()
val sc: SparkContext = spark.sparkContext
// Определить балл сходства предложений
def sentenceSimilarity(s1: String, s2: String): Double = {
val words1 = s1.split("[\\s,.;:?!]+").map(_.toLowerCase)
.toSet
val words2 = s2.split("[\\s,.;:?!]+").map(_.toLowerCase)
.toSet
val commonWords = words1.intersect(words2).size
val den = log(words1.size + 1) + log(words2.size + 1) + 1
commonWords.toDouble / den
}
После этого, как показано в листинге 5.22, из входного файла создается
полносвязный граф предложений. Этот граф строится путем чтения входного
текстового файла и извлечения содержащихся в нем разных предложений (то
есть множества вершин графа предложений). Затем каждому предложению
назначается индекс, и все возможные пары предложений определяются как
декартово самопроизведение множества извлеченных предложений. Каждая
полученная пара образует ребро полносвязного графа предложений. Наконец, с помощью метода, представленного в листинге 5.21, вычисляется вес
каждого ребра, и на выходе в качестве результата возвращается граф.
Листинг 5.22 Построение графа предложений, начиная с входного
текстового файла
def buildGraph(input_path: String): Graph[String, Double] =
{
// получить предложение из текстового файла, подлежащего резюмированию
val input_sentences = sc.textFile(input_path)
.flatMap(line =>line.split('.'))
// каждое предложение является вершиной графа
val vertices = input_sentences.zipWithIndex.map {
case (sentence, index) => (index, sentence)}
// каждая пара предложений является ребром графа,
// взвешенным на попарное сходство
val pairs = vertices.cartesian(vertices)
.filter { case ((i1, _), (i2, _)) => i1 != i2 }
.map { case ((i1, s1), (i2, s2)) => (i1, i2,
sentenceSimilarity(s1, s2)) }
232 Сравнение инструментов программирования
.map { case (i1, i2, sim) => Edge(i1, i2, sim) }
Graph(vertices, pairs)
}
После создания граф предложений подготавливается к вычислению алгоритма PageRank, как показано в листинге 5.23. В частности, веса ребер нормализуются таким образом, чтобы сумма весов исходящих ребер каждой вершины была равна 1. Это достигается созданием словаря, содержащего сумму
весов исходящих ребер каждой вершины, полученную путем применения
метода aggregateMessages, предоставляемого библиотекой GraphX. В данном
случае метод aggregateMessages позволяет обрабатывать каждое ребро графа,
отправляя вес ребра его вершине-источнику, который агрегирует сообщения,
полученные всеми его исходящими ребрами, используя в качестве функции
редукции сумму: (a, b) => a + b. Затем применяется функция map, которая
нормализует каждое ребро на основе его вершины-источника и соответствующего значения, хранящегося в словаре. Наконец, посредством операции
mapVertices каждой вершине назначается первоначальная догадка.
Листинг 5.23 Подготовка графа к вычислению алгоритма PageRank
// нормализовать реберные веса и установить первоначальную догадку
def prepareGraph(graph: Graph[String, Double]):
Graph[Double, Double] = {
// вычислить словарь коэффициентов нормализации
val outgoingEdgesSum = graph.aggregateMessages[Double](
ctx => ctx.sendToSrc(ctx.attr),
(a, b) => a + b,
TripletFields.EdgeOnly
).collect.toMap
// нормализовать ребра
val normEdges = graph.edges.map(e => {
val srcSum = outgoingEdgesSum.getOrElse(e.srcId, 1.0)
val newWeight = e.attr / srcSum
Edge(e.srcId, e.dstId, newWeight)
})
val initialGuess = 1.0
val vertices = graph.vertices
// назначить каждой вершине первоначальную догадку
Graph(vertices, normEdges).mapVertices((_, attr) =>
initialGuess)
}
Как было описано ранее, в технике резюмирования TextRank задействуется алгоритм PageRank, который служит для извлечения сводки, собираемой
в виде помещенных бок о бок верхних k наиболее значимых предложений
в графе. Поскольку при этом учитывается сила связи между парой предложений, пропорциональная их сходству, нужна модифицированная версия алгоритма PageRank, которая может работать со взвешенным графом
предложений. Как показано в листинге 5.24, алгоритм PageRank реализован
Сравнительный анализ на примерах приложений 233
с помощью API-интерфейсов Pregel, предлагаемых библиотекой GraphX, как
обсуждалось в подразделе «Графовая абстракция» раздела 4.5.1. Единственное отличие здесь заключается в том, как граф подготавливается к вычислениям: в стандартном случае вес каждого ребра нормализуется по отношению
к исходящей степени вершины-источника, тогда как в данном случае он
нормализуется по сумме весов всех исходящих ребер вершины-источника.
Более того, здесь на алгоритм не влияет наличие вершин-стоков, так как
он вычисляет ранги PageRank на полносвязном графе предложений. После
вычисления рангов PageRank путем выполнения оператора Pregel в течение фиксированного числа итераций получается граф rankGraph, в каждой
вершине которого хранится соответствующее ранговое значение PageRank.
Этот граф используется в методе buildSummary (см. листинг 5.25) вместе с изначальным графом предложений с целью извлечения сводки, которая распечатывается в консоли.
Листинг 5.24 Метод main: определение пользовательских функций PageRank
и запуск Pregel
def main(args: Array[String]) = {
// собрать граф предложений
val path = "src/main/scala/graphX_apps/ANN.txt"
val sentenceGraph = buildGraph(path)
// подготовить граф для вычисления рангов PageRank
val preparedGraph = prepareGraph(sentenceGraph)
// определить пользовательские функции Pregel для алгоритма PageRank
val d = 0.85
val numVertices = preparedGraph.numVertices
def vertexProgram(id: VertexId, attr: Double, msgSum:
Double): Double = (1 – d) / numVertices + d * msgSum
def sendMessage(edge: EdgeTriplet[Double, Double]):
Iterator[(VertexId, Double)] = Iterator((edge.dstId,
edge.srcAttr * edge.attr))
def messageCombiner(a: Double, b: Double): Double = a + b
// Исполнить оператор Pregel фиксированное число итераций.
val rankGraph = Pregel(graph = preparedGraph, initialMsg
= 0.0, maxIterations = 50)(vprog = vertexProgram,
sendMsg = sendMessage, mergeMsg = messageCombiner)
// Извлечь сводку
val top_k = 3
val summary = buildSummary(sentenceGraph, rankGraph, top_k)
println("Summary:\n" + summary)
}
В листинге 5.25 показано, как строится сводка, начиная с изначального
графа предложений rankGraph и целого числа k, обозначающего количество
предложений, которые будут вставлены в сводку. Таким образом, сводка будет состоять из верхних k предложений по рангу PageRank, упорядоченных
в соответствии с их появлением в изначальном тексте, причем этот порядок
сохраняется благодаря использованию ИД вершины в графе предложений.
234 Сравнение инструментов программирования
Листинг 5.25 Извлечение резюме, начиная с графа рангов и изначального
графа предложений
def buildSummary(sentenceGraph: Graph[String, Double],
rankGraph: Graph[Double, Double], k: Int): String ={
// отобрать верхние k предложений по рангу PageRank,
// упорядоченных согласно порядку их появления в изначальном тексте
sentenceGraph.vertices.join(rankGraph.vertices)
.map { case (id, (sent, rank)) => (id, sent, rank) }
.top(k)(Ordering.by(_._3)).sortBy(_._1)
.map { case (_, sent, _) => sent }
.mkString(".\n") + "."
}
Далее приводятся результаты работы алгоритма, начиная с текстового
файла, содержащего краткий отрывок из Википедии, описывающий искусственные нейронные сети.
Входные данные (ANN.txt)
Artificial neural networks (ANNs), usually simply called neural networks (NNs) or neural
nets, are computing systems inspired by the biological neural networks that constitute animal
brains. An ANN is based on a collection of connected units or nodes called artificial neurons,
which loosely model the neurons in a biological brain. Each connection, like the synapses in
a biological brain, can transmit a signal to other neurons. An artificial neuron receives signals then processes them and can signal neurons connected to it. The signal at a connection
is a real number, and the output of each neuron is computed by some nonlinear function of
the sum of its inputs. The connections are called edges. Neurons and edges typically have
a weight that adjusts as learning proceeds. The weight increases or decreases the strength of
the signal at a connection. Neurons may have a threshold such that a signal is sent only if the
aggregate signal crosses that threshold. Typically, neurons are aggregated into layers. Signals
travel from the first layer (the input layer), to the last layer (the output layer), possibly after
traversing the layers multiple times. The training of a neural network from a given example is
usually conducted by determining the difference between the processed output of the network
(often a prediction) and a target output. This difference is the error. The network then adjusts
its weighted associations according to a learning rule and using this error value. Successive
adjustments will cause the neural network to produce output that is increasingly similar to the
target output. Such systems “learn” to perform tasks by considering examples, generally without
being programmed with task-specific rules.
Результат (сводка)
An ANN is based on a collection of connected units or nodes called artificial neurons, which
loosely model the neurons in a biological brain. The signal at a connection is a real number,
and the output of each neuron is computed by some nonlinear function of the sum of its inputs.
The training of a neural network from a given example is usually conducted by determining the
difference between the processed output of the network (often a prediction) and a target output.
5.3.4.2. Реализация с использованием фреймворка MPI
В этом разделе будет рассмотрена реализация описанного выше алгоритма
TextRank с применением фреймворка MPI. Мы использовали привязку к Java,
Сравнительный анализ на примерах приложений 235
предлагаемую библиотекой Open MPI, представляющей собой реализацию
MPI с открытым исходным кодом.
Прежде всего, как и в описанной ранее реализации, для заданной пары
предложений определяется метод вычисления текстового сходства (см. лис
тинг 5.26).
Листинг 5.26 Сходство предложений
public static double sentenceSimilarity(String sent1, String sent2) {
Set<String> words1 = new HashSet<>(Arrays.asList(sent1.
toLowerCase().split("[\\s,.;:?!]+")));
Set<String> words2 = new HashSet<>(Arrays.asList(sent2.
toLowerCase().split("[\\s,.;:?!]+")));
int commonWords = (int) words1.stream().filter(words2::
contains).count();
double den = Math.log(words1.size() + 1) + Math.log(
words2.size() + 1) + 1;
return (double) commonWords / den;
}
Листинг 5.27 читает входной файл и возвращает список всех содержащихся
в нем предложений.
Листинг 5.27 Извлечение всех предложений из входного текстового файла
private static List<String> readInputFile(String fileName) {
List<String> allSentences = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new
FileReader(fileName))) {
String line;
while ((line = br.readLine()) != null) {
String[] sentences = line.split("\\.");
for (String s : sentences) {
allSentences.add(s.strip());
}
}
} catch (IOException e) {
System.err.format("IOException: %s%n", e);
}
return allSentences;
}
Начиная со списка, содержащего все предложения, исходный код в лис
тинге 5.28 используется для построения полносвязного графа предложений.
В частности, он представлен в виде словаря, в котором ключом является ИД
вершины (vertexId) каждого предложения, а значением – множество входящих и исходящих соседей. Ближайшие соседи каждой вершины в словаре
представлены вложенным словарем, содержащим два ключа, in_neigh и out_
neigh, соответствующие значения которых являются списками идентификаторов соответственно входящих и исходящих соседей.
236 Сравнение инструментов программирования
Листинг 5.28 Построение графа предложений начиная с входного
текстового файла
private static Map<Integer, Map<String, ArrayList<Integer>>>
buildGraph(List<String> allSentences) {
// сохранить граф в виде словаря, используя ИД вершины в
// качестве ключа; значениями являются подсловари,
// хранящие входящие и выходящие окрестности
Map<Integer, Map<String, ArrayList<Integer>>> graph =
new HashMap<>();
for (int i = 0; i < allSentences.size(); i++) {
for (int j = 0; j < allSentences.size(); j++) {
if (i != j) {
if (!graph.containsKey(i)) {
// инициализировать окрестности вершины i
graph.put(i, new HashMap<String,
ArrayList<Integer>>());
graph.get(i).put("in_neigh", new
ArrayList<Integer>());
graph.get(i).put("out_neigh", new
ArrayList<Integer>());
}
if (!graph.containsKey(j)) {
// инициализировать окрестности вершины j
graph.put(j, new HashMap<String,
ArrayList<Integer>>());
graph.get(j).put("in_neigh", new
ArrayList<Integer>());
graph.get(j).put("out_neigh", new
ArrayList<Integer>());
}
// добавить соседей
graph.get(i).get("out_neigh").add(j);
graph.get(j).get("in_neigh").add(i);
}
}
}
return graph;
}
После того как описанные выше методы были определены, был задействован фреймворк MPI, чтобы реализовать алгоритм PageRank, который
является стержневым компонентом техники резюмирования TextRank. Далее
приводятся и обсуждаются главные части разработанной реализации с акцентом на использование фреймворка MPI.
Сначала инициализируется среда исполнения MPI. Это делается путем
вызова метода MPI.Init(args). Затем извлекается размер (то есть количество
процессов) и ранг (то есть ИД) текущего процесса, как показано ниже:
int size = MPI.COMM_WORLD.getSize();
int rank = MPI.COMM_WORLD.getRank();
Сравнительный анализ на примерах приложений 237
После этого из входного файла вычисляется список всех предложений,
который используется для создания графа предложений (см. листинг 5.29).
Определяется количество вершин N, каждой из которых назначается первоначальная догадка, равная . Ранговое значение PageRank вершины с индексом i будет храниться в i-й позиции массива pageranks.
Листинг 5.29 Создание графа предложений из входного файла
List<String> allSentences = readInputFile("ANN.txt");
Map<Integer, Map<String, ArrayList<Integer>>> graph =
buildGraph(allSentences);
int numVertices = graph.keySet().size();
double[] pageranks = new double[numVertices];
Arrays.fill(pageranks, 1.0 / numVertices);
Затем вычисляется количество вершин, с которыми каждый процесс будет
работать, и остаток в случае, когда количество вершин не кратно количеству
процессов. Начиная с этого, все множество вершин делится на количество
разделов, равное переменной size, которая задает количество процессов,
и каждый раздел назначается отдельному процессу. В частности, вычисляется диапазон индексов, указывающих на вершины, для которых каждый
процесс будет вычислять ранговое значение PageRank, таким образом распределяя вычисление рангов PageRank всего множества вершин по разным
рангам в стиле параллельности данных. Стоит отметить, что в случае, когда
остаток больше 0, оставшиеся вершины назначаются последнему процессу
(см. листинг 5.30).
Листинг 5.30 Разделить данные, назначив каждому рангу множество вершин
для работы. При наличии остатка он обрабатывается последним
рангом
// Вычислить количество вершин, которое каждый процесс должен обрабатывать
int verticesPerProcess = numVertices / size;
int remainder = numVertices % size;
// Вычислить начальный и конечный индексы у процесса
int startIndex = rank * verticesPerProcess;
int endIndex = startIndex + verticesPerProcess;
if (remainder > 0 && rank == size – 1) {
endIndex += remainder;
verticesPerProcess += remainder;
}
В листингах 5.31 и 5.32 иллюстрируется вычисление итерации алгоритма
PageRank для заданного ранга. В частности, как показано в листинге 5.31,
если схождение еще не достигнуто, то процесс инициализирует массив, в котором будет храниться локальное ранговое значение PageRank назначенных
ему вершин. Затем процесс выполняет следующие ниже операции по каждой
назначенной вершине v:
238 Сравнение инструментов программирования
для каждого соседа i из v вычисляется сходство между v и i, то есть вес
ребра, соединяющего v-ю вершину и i-е предложение в списке allSentences;
затем вес ребра, соединяющего v и i, нормализуется относительно суммы весов всех исходящих из i вершин;
вклад вершины i в локальное значение PageRank вершины v суммируется в массиве localPageRanks;
совокупный вклад вершины v, хранящийся в массиве localPageRanks,
конвертируется в реальное ранговое значение PageRank путем применения формулы, рассмотренной в подразделе «Пример программирования» раздела 4.5.1.
Листинг 5.31 Итерация алгоритма PageRank с вычислением локальных
значений
// сохраняет глобальные ранговые значения PageRank каждой вершины графа
double[] globalPageranks = new double[numVertices];
// прокручивать в цикле вплоть до схождения
boolean converged = false;
while (!converged) {
double[] localPageranks = new double[verticesPerProcess];
double sum_out_sim = 0.0;
double in_weight = 0.0;
for (int i = startIndex; i < endIndex; i++) {
for (int in_neighbor : graph.get(i).get("in_neigh"))
{
in_weight = sentenceSimilarity(allSentences.get(
in_neighbor), allSentences.get(i));
// нормализовать веса входящих ребер
sum_out_sim = 0.0;
for (int out_neighbor : graph.get(in_neighbor).
get("out_neigh")) {
sum_out_sim += sentenceSimilarity(
allSentences.get(in_neighbor),
allSentences.get(out_neighbor));
}
in_weight /= sum_out_sim;
localPageranks[i – startIndex] += pageranks[
in_neighbor] * in_weight;
}
// вычислить локальный ранг PageRank для текущей назначенной вершины
localPageranks[i – startIndex] = DAMPING_FACTOR *
localPageranks[i – startIndex] + (1 DAMPING_FACTOR) / numVertices;
}
...
Сравнительный анализ на примерах приложений 239
В листинге 5.32 глобальные ранги PageRank собираются для каждого процесса путем сбора локальных ранговых значений PageRank, вычисленных
всеми другими процессами. Эта операция обычно выполняется с помощью
метода allGather, который обеспечивает коммуникационный шаблон «многие ко многим». В частности, при заданном множестве элементов, распределенных по всем процессам, метод allGather будет собирать все элементы
для всех процессов. Такое поведение можно рассматривать как исполнение
операции gather для каждого процесса, дополненной операцией broadcast.
Однако в данном случае возможное наличие остатка не позволяет использовать операцию gather, которая накладывает ограничение, заключающееся
в том, что каждый ранг передает одинаковое количество данных. Для устранения этой проблемы можно использовать метод allGatherv, который является обобщенной версией метода allGather, в котором каждый ранг может
передавать переменное количество данных. Для выполнения этой операции
задаются следующие ниже параметры:
localPageranks: это локальный массив текущего ранга, содержащий отправляемые значения;
verticesPerProcess: содержит количество вершин, назначенных текущему рангу. В данной реализации все ранги обрабатывают одинаковое
количество вершин, за исключением последнего (то есть процесс, ранг
которого равен size – 1), который также будет обрабатывать остаток;
MPI.DOUBLE: обозначает тип данных каждого элемента в буфере отправки;
globalPageranks: это глобальный массив, в котором будут собираться
и храниться полученные значения;
rcvCount: это массив, содержащий количество данных (то есть число
ранговых значений PageRank), отправленных i-м рангом и полученных
текущим рангом;
displs: это массив, задающий позицию смещения в глобальном массиве, начиная с которой будут храниться данные, полученные i-м рангом;
MPI.DOUBLE: обозначает тип данных каждого элемента в буфере получения.
После сбора глобальных ранговых значений PageRank каждый ранг вычисляет текущее значение схождения как абсолютную разницу (то есть дельту)
между текущим вычисленным ранговым значением PageRank и значением,
полученным на предыдущей итерации, и передает эту дельту всем остальным рангам посредством метода allGather. Стоит отметить, что в данном
случае допустимо использовать метод allGather, так как каждый ранг отправляет остальным ровно одно значение (то есть дельту), независимо от
количества обрабатываемых вершин. После того как текущий ранг получил
240 Сравнение инструментов программирования
значения дельты от всех остальных рангов, он может выполнить проверку
на схождение. В частности, схождение достигается, когда все дельты будут
меньше заданного порога схождения. Наконец, ранговые значения PageRank
обновляются глобальным результатом текущей итерации, и последующий
шаг выполняется при условии, что схождение не было достигнуто.
Листинг 5.32 Сбор всех локальных ранговых значений PageRank и проверка
схождения
...
// Собрать локальные ранговые значения PageRank из всех процессов,
// обрабатывающих варьирующиеся количества вершин в расчете на ранг
globalPageranks = new double[numVertices];
int[] rcvCount = new int[size];
int[] displs = new int[size];
for (int i = 0; i < size; i++) {
rcvCount[i] = numVertices / size;
displs[i] = rcvCount[i] * i;
}
rcvCount[size – 1] += remainder;
MPI.COMM_WORLD.allGatherv(localPageranks,
verticesPerProcess, MPI.DOUBLE, globalPageranks,
rcvCount, displs, MPI.DOUBLE);
// Вычилить глобальную вариацию PageRank для итерации
double delta = 0.0;
for (int i = startIndex; i < endIndex; i++) {
delta += Math.abs(globalPageranks[i] – pageranks[i]);
}
double[] deltas = new double[size];
MPI.COMM_WORLD.allGather(new double[] { delta }, 1, MPI.
DOUBLE, deltas, 1, MPI.DOUBLE);
// Выполнить проверку на схождение
converged = true;
for (double d : deltas) {
if (d > CONVERGENCE_THRESHOLD) {
converged = false;
break;
}
}
// Обновить ранговые значения PageRank для выполнения следующей итерации
pageranks = globalPageranks;
}
Интересно отметить, что вычисление схождения можно было бы выполнить с помощью одного ранга, заменив метод allGather примитивом gather.
Однако сведение коммуникации «многие к многим» к коммуникации «многие к одному» потребовало бы передачи результата вычисления схождения
Сравнительный анализ на примерах приложений 241
(то есть флага схождения) обратно всем рангам. Кроме того, перед выполнением следующей итерации каждый ранг должен будет дожидаться прихода
флага схождения, что приведет к снижению уровня параллелизма за счет
синхронизации.
Наконец, в листинге 5.33 процесс с рангом 0 вычисляет сводку из трех
верхних предложений по рангу PageRank с помощью метода extractSummary
(см. листинг 5.34), распечатывает его в консоли и завершает работу среды
исполнения MPI, вызывая метод MPI.Finalize().
Листинг 5.33 Процесс с рангом 0 вычисляет сводку из трех верхних
предложений по рангу PageRank
if (rank == 0) {
// ранг 0 отвечает за извлечение и распечатку сводки
int topK = 3;
String summary = extractSummary(globalPageranks,
topK, allSentences);
System.out.println("SUMMARY:\n" + summary);
}
MPI.Finalize();
В листинге 5.34 показана процедура извлечения сводки, начиная с массива, содержащего окончательные ранговые значения PageRank каждого
предложения, списка всех предложений, содержащихся во входном тексте,
и целого числа k, обозначающего количество предложений, которые должны
быть включены в извлеченную сводку. Производимый этой версией результат будет точно таким же, как и в реализации на основе библиотеки GraphX
(см. раздел 5.3.4).
Листинг 5.34 Извлечение сводки начиная с массива pagerank и списка
предложений
public static String extractSummary(double[] globalPageranks,
int k, List<String> allSentences) {
Map<Double, Integer> map = new HashMap<>();
for (int i = 0; i < globalPageranks.length; i++) {
map.put(globalPageranks[i], i);
}
// получить верхние k предложений по рангу PageRank
List<Double> values = new ArrayList<>(map.keySet());
Collections.sort(values, Collections.reverseOrder());
List<Integer> topKIndexes = new ArrayList<>();
for (int i = 0; i < k && i < values.size(); i++) {
topKIndexes.add(map.get(values.get(i)));
}
// упорядочить верхние k предложений в соответствии
// с их появлением в изначальном тексте
Collections.sort(topKIndexes);
242 Сравнение инструментов программирования
String summary = topKIndexes.stream().map(allSentences::
get).collect(Collectors.joining(".\n")) + ".";
return summary;
}
Сравнивая две представленные выше версии алгоритма TextRank, легко заметить большую многословность реализации на базе фреймворка
MPI по сравнению с рассмотренной ранее реализацией на базе GraphX,
(см. раздел 5.3.4.1). Это связано как с большей лаконичностью языка Scala,
обусловленной использованием функционального программирования, так
и с поддержкой графоориентированных структур данных и примитивов. Отсутствие такой поддержки в MPI приводит к необходимости хранить самую
разную информацию, связанную с вершинами и окрестностями, в нативных
структурах данных, что приводит к снижению удобочитаемости и увеличению многословности исходного кода. Все это делает библиотеку GraphX
более подходящей для разработки сложных приложений с параллельностью
графов.
Глава
6
Выбор правильного
фреймворка
для приручения
больших данных
На протяжении всех глав этой книги мы рассматривали и анализировали
наиболее важные фреймворки программирования, которые были разработаны для реализации масштабируемых распределенных и/или параллельных
приложений, интенсивных по привлечению данных. При описании и сравнении этих фреймворков мы сосредоточивались на их характеристиках,
которые позволяют разработчикам реализовывать приложения по анализу
больших данных и машинному обучению, а также анализировали способы,
посредством которых обсуждаемые инструменты программирования можно
использовать для реализации параллельных операций в алгоритмах и приложениях, интенсивных по привлечению данных. Этой главой мы завершаем
книгу и обсуждаем ряд факторов, которые разработчикам следует учитывать
при выборе подходящего фреймворка для конструирования и реализации
своих приложений по обработке больших данных. Из анализа рассмотренных
в книге разных инструментов программирования можно извлечь выводы,
которые позволяют предположить, что при выборе наилучшего (или наиболее подходящего) фреймворка программирования разработчики должны
руководствоваться характеристиками данных, целями приложений и вычислительными инфраструктурами. Этот подход будет полезен в ориентировании конструкторов и разработчиков по пути правильного выбора одного или
нескольких из рассмотренных фреймворков программирования, когда им
нужно реализовывать то или иное приложение по обработке больших данных
244 Выбор правильного фреймворка для приручения больших данных
или конкретную технику машинного обучения, которая должна обеспечивать
доступ к крупным наборам данных и их обработку, гарантируя масштабируемость и высокую производительность.
Как уже говорилось ранее, при реализации приложений, интенсивных по
привлечению данных, необходимо учитывать главные факторы, которые
включают следующее:
характеристики входных данных: сюда входят такие важные элементы, как объем данных, как с точки зрения размера, так и размерности
входного набора данных, быстрота данных и разнообразие данных,
которые необходимо всегда учитывать;
класс приложения: это тип подлежащего реализации приложения и его
цели; это может быть пакетная, потоковая, графовая и запросная обработка данных либо другой тип приложений по обработке данных;
программно-аппаратная инфраструктура: это инфраструктура хранения и вычислений, которая будет использоваться для работы разработанного приложения; например, платформа высокопроизводительных
вычислений, облачная инфраструктура (публичная или частная) или
кластер на базе сервера, с учетом трудностей, связанных с локальностью и доступностью данных.
Наряду с перечисленными главными факторами следует учитывать и другие, которые обычно влияют на выбор разработчиков-исполнителей и жизненного цикла приложений. К ним относятся:
навыки программирования конструкторов и разработчиков;
характеристики экосистемы выбранного фреймворка программирования;
размер и степень активности сообщества разработчиков;
требования к конфиденциальности данных, определяющие доступ
к ним и их обработку;
стоимость используемой аппаратно-программной инфраструктуры;
наличие библиотек для анализа данных и/или машинного обучения,
которые могут использоваться в модели программирования;
предлагаемый фреймворком уровень абстракции модели программирования.
В последующих разделах главы обсуждаются и анализируются перечисленные факторы, начиная с трех главных.
6.1. Входные данные
При преодолении трудностей, порождаемых большими данными, выбор подходящего фреймворка программирования является ключом к эффективной
работе с объемом, скоростью, разнообразием и другими характеристиками
подлежащих анализу данных.
Входные данные 245
Объем, который относится к количеству данных, генерируемых или собираемых приложениями, существенно влияет на выбор модели и системы программирования с разных точек зрения. Во-первых, объем влияет на
предъявляемые к приложению требования по хранению данных, поскольку
хранение больших объемов данных нуждается в распределенных технологических решениях по хранению, обеспечивающих репликацию данных, отказоустойчивость и масштабируемость. Для этих целей предназначены распределенные файловые системы, такие как HDFS (от англ. Hadoop Distributed
File System), которые служат для хранения и управления данными в кластере
машин, обеспечивая отказоустойчивость и масштабируемость. Во-вторых,
объем данных также влияет на требования к приложению по обработке, так
как для обработки крупных объемов данных требуются распределенные вычислительные системы, способные горизонтально масштабироваться в клас
тере машин, такие как фреймворк Hadoop, который обычно используется
для распределенной и параллельной обработки больших данных. С другой
стороны, фреймворк Apache Spark предлагает возможности резидентной обработки, что делает его неплохим кандидатом для итеративных алгоритмов
и интерактивного анализа данных.
Еще одна проблема обработки крупных объемов данных связана с их размерностью и свойствами. Во многих случаях высокоразмерные данные могут
нуждаться в использовании техник редукции размерности, таких как анализ
главных компонент (PCA, от англ. principal component analysis) или разложение на сингулярные значения (SVD, от англ. singular value decomposition)1.
Однако эти методы поддерживаются лишь некоторыми фреймворками для
работы с большими данными, например системой Spark посредством биб
лиотеки MLlib, которая упрощает анализ и выведение глубинных сведений
из высокоразмерных данных. Более того, объем данных также влияет на
производительность приложений. И действительно, с увеличением объема
данных увеличивается время, необходимое для их обработки, что затрудняет удовлетворение требований к приложениям по производительности.
В таких случаях для обеспечения эффективной обработки больших данных
целесообразно выбирать систему программирования, поддерживающую параллелизм данных и заданий, например, Spark.
Быстрота, означающая скорость генерирования данных, является еще
одной важной характеристикой больших данных, которая требует разработки программных моделей и систем, способных собирать, обрабатывать
и анализировать данные (почти) в реальном времени. Быстрота данных несомненно влияет на требования к приложениям по приобретению и обработке
данных. В сценариях с высокоскоростными данными они должны приобретаться и обрабатываться в реальном времени, чтобы обеспечивать возможность своевременного принятия решений. Хотя в некоторых сценариях
1
Также сингулярное разложение, то есть метод представления матрицы в виде серии линейных приближений, раскрывающих глубинную смысловую структуру мат
рицы. – Прим. перев.
246 Выбор правильного фреймворка для приручения больших данных
могут использоваться микропакетные потоковые системы, такие как Spark
Streaming, для обработки подобных потоков данных в реальном времени
обычно используются фреймворки потоковой обработки, такие как Apache
Storm, позволяющие приложениям реагировать на события по мере их возникновения. Скорость данных также влияет на способности приложения
по обработке и анализу, так как для улавливания релевантной информации
из потока данных, генерируемого с высокой быстротой, требуются такие
методы, как оконная обработка и агрегация по времени. Кроме того, для
обработки данных в реальном времени необходимы способности обработки
с низкой задержкой, такие как резидентная обработка, чтобы обеспечивать
отзывчивость приложения по работе с большими данными.
Разнообразие, означающее неоднородность типов, форматов и источников
данных, генерируемых или собираемых приложением, нуждается в интег
рации, преобразовании и анализе данных в гибком и адаптируемом стиле.
Данные из множественных источников и форматов могут предобрабатываться перед анализом с помощью фреймворков, поддерживающих операции извлечения, преобразования и загрузки (ETL), таких как Apache Hive
и Apache Pig. В дополнение к этому для разных типов данных требуются
разные техники анализа. Например, структурированные данные можно
анализировать с помощью традиционных техник работы с реляционными
СУБД, таких как Hive и Pig, а неструктурированные данные (например, текст)
требуют техник специального назначения (например, техники обработки
естественного языка (NLP) для текстовых данных), которые предлагаются
некоторыми фреймворками, включая Apache Spark. Поэтому для управления
разнообразием данных в приложениях по обработке больших данных решающее значение имеет выбор системы программирования, поддерживающей
множественные типы данных и техники анализа. Среди рассматриваемых
в этой книге фреймворков система Spark является наиболее универсальным
инструментом обработки неоднородных данных, так как она предлагает APIинтерфейсы для пакетной, потоковой и графовой обработки, машинного
обучения, кадры данных для работы с разными типами данных, такими как
CSV-файлы, JSON-данные и таблицы баз данных.
6.2. Класс приложения
Выбор правильного программного решения для разработки приложения по
анализу больших данных существенно зависит от характеристик планируемого к реализации приложения и требований к нему. Некоторые программные решения являются более общими и потому могут выгодно использоваться для разных классов приложений, тогда как другие могут эффективно
использоваться в конкретной области (например, потоковой обработке данных, выполнении запросов к данным и глубоком обучении). Общецелевые
системы, такие как Hadoop и Spark, находят широкое применение, по сути
Класс приложения 247
дела, благодаря своей гибкости и способности обрабатывать крупные наборы
данных, тогда как более специализированные системы могут иметь преимущества в производительности и простоте использования. Приложения
по обработке больших данных используются во многих контекстах и служат
разным целям. Их можно разделить на четыре главных класса: пакетные,
потоковые, графовые и запросные.
Пакетные приложения предназначены для обработки и анализа крупных
объемов данных в пакетном режиме. Такой подход предусматривает обработку крупного количества данных, которые собираются и анализируются
вместе, как правило, в непиковые часы, когда спрос на обработку со стороны
системы невелик. Примерами приложений по пакетной обработке больших
данных являются хранилища данных, глубокая переработка данных и пакетная обработка крупных наборов данных. Пакетные приложения, интенсивные по привлечению данных, особенно полезны для анализа исторических данных, генерирования отчетов и выполнения комплексной аналитики,
требующих значительных вычислительных мощностей и ресурсов. В пакетной обработке данных широко используются фреймворки Apache Spark
и Apache Hadoop благодаря их способностям по распределенному хранению
и обработке данных. Фреймворк Hadoop включает в себя отказоустойчивое
хранилище и структуру для распределенной обработки, а Spark предлагает
быстрый и гибкий механизм обработки данных с высокоуровневыми APIинтерфейсами, машинное обучение и многое другое. Вдобавок для разработки и мониторинга пакетно-ориентированных приложений, основанных
на рабочих потоках, можно использовать систему Apache Airflow. По сути
дела, она может использоваться для оркестровки и автоматизации рабочих
потоков пакетной обработки, позволяя конечным пользователям сосредоточиваться не на управлении инфраструктурой обработки, а на получении
содержательных сведений из своих данных.
Потоковые приложения предназначены для обработки и анализа данных,
собираемых в режиме реального времени. Такой подход предусматривает обработку данных по мере их поступления, без необходимости хранить
их в централизованных хранилищах. Приложения по потоковой обработке
больших данных особенно полезны в областях, где требуется реально-временной анализ данных, таких как финансы, телекоммуникации и транспорт.
Примерами приложений по потоковой обработке больших данных являются
реально-временной анализ данных, выявление мошенничества и реальновременной мониторинг датчиков и IoT-устройств. Для потоковой обработки
данных предназначены фреймворки с открытым исходным кодом Apache
Storm и Apache Spark. Storm обеспечивает низкую задержку, масштабируемость и отказоустойчивость реально-временной обработки данных, а Spark –
микропакетную потоковую обработку с помощью специализированных API.
Оба фреймворка подходят для обработки крупных объемов данных в режиме
реального времени, что делает их идеальными кандидатами для приложений
по потоковой обработке данных, нуждающихся в быстром и эффективном
анализе данных.
248 Выбор правильного фреймворка для приручения больших данных
Графовые приложения предназначены для обработки и анализа данных,
связанных между собой в сложные сети или графовые структуры. Этот подход предусматривает анализ взаимосвязей между разными узлами данных
в графе с целью выявления закономерностей и действенной информации,
которые могут быть неочевидны при использовании традиционных методов
анализа. Примерами приложений по графовой обработке больших данных
являются анализ социальных сетей, рекомендательные системы и обнаружение мошенничества. Приложения по графовой обработке больших данных особенно полезны в тех случаях, когда данные сильно взаимосвязаны
и взаимосвязи между точками данных имеют решающее значение для анализа. Для графовой обработки больших данных подходят фреймворки MPI
и Apache Spark. MPI обеспечивает низкоуровневый контроль над параллелизмом и коммуникациями, а Spark предоставляет специализированные
высокоуровневые API (например, GraphX) для эффективной и масштабируемой графовой обработки. Оба фреймворка подходят для анализа сложных
взаимосвязей между узлами и обработки крупномасштабных графов в распределенной и эффективной манере.
Запросные приложения предназначены для обеспечения быстрого и эффективного доступа к крупным объемам данных с помощью языков запросов и инструментов поиска. Этот подход предусматривает хранение данных
в распределенной системе и использование языков запросов, таких как SQL,
с целью извлечения данных из системы. Приложения по запросной обработке больших данных особенно полезны в тех случаях, когда для извлечения
действенной информации из крупных наборов данных требуются быстрые
спонтанные запросы. Примерами приложений по запросной обработке больших данных являются управленческая аналитика, разведывательный анализ
данных и спонтанный анализ данных. Для запросной обработки крупных
наборов данных подходят фреймворки с открытым исходным кодом Apache
Hive, Pig и Spark. Hive предоставляет SQL-подобный интерфейс, Pig – прос
той скриптовый язык, а Spark SQL позволяет делать запросы к данным и их
анализировать, используя синтаксис языка SQL. Эти фреймворки предназначены для быстрого и эффективного доступа к крупным наборам данных и идеально подходят для выполнения спонтанных запросов и проведения спонтанного анализа, например разведывательного анализа данных
и управленческой аналитики.
6.3. Инфраструктура
Появление технологий и приложений по обработке больших данных привело
к разработке комплексных инфраструктур, обеспечивающих эффективную
обработку, хранение и анализ огромных объемов данных. Выбор правильной
инфраструктуры анализа больших данных имеет решающее значение для
предприятий и организаций, позволяя им эффективно использовать свои
Инфраструктура 249
данные и извлекать действенную информацию с целью принятия решений.
Однако выбрать правильный фреймворк, который соответствовал бы требованиям инфраструктуры, довольно сложно, так как конкретный выбор
зависит от нескольких факторов, включая масштаб инфраструктуры, варианты использования больших данных, распоряжение данными и безопасность, стоимость и лицензирование, а также требования к масштабируемости. В этом разделе мы обсудим, как с учетом этих факторов можно выбрать
фреймворк, наиболее подходящий для работы с большими данными в зависимости от конкретного типа используемой инфраструктуры. В частности, мы обсудим рекомендации по главным типам инфраструктур, а именно
оконечной, облачной и гибридной.
Под оконечной инфраструктурой подразумевается развертывание аппаратного и программного обеспечения на территории организации, что не
требует переноса крупных объемов данных в дистанционные места. Такая
инфраструктура идеально подходит для организаций, которые предъявляют
строгие требования к обеспечению безопасности данных и хотят получать
полный контроль над своими данными. Установка, конфигурирование и техническое сопровождение инфраструктуры такого типа требуют высокого
уровня квалификации и значительных экономических вложений. При использовании оконечной инфраструктуры данные обрабатываются и хранятся в собственном центре обработки данных, что обеспечивает более высокий
уровень безопасности и позволяет легче соблюдать строгие требования к доступу к данным и конфиденциальности. Оконечные инфраструктуры, в особенности в малых и средних организациях с ограниченным ИТ-бюджетом,
нередко состоят из взаимосвязанных машин, оснащенных товарным аппаратным обеспечением. В таких случаях для обработки крупных наборов
данных с меньшими затратами на разнородном оборудовании, используя
для обработки данных любой тип дискового хранилища, можно эффективно
использовать фреймворк Apache Hadoop. Более того, HDFS способна распределять данные по разным машинам под управлением разных операционных систем, не требуя специальных драйверов. Для компаний сферы ИТ
с большим бюджетом эффективным решением для быстрой резидентной обработки крупных объемов данных является фреймворк Apache Spark. Однако
его применение требует больших затрат, так как для раскрутки узлов нужен
большой объем оперативной памяти.
Под облачной инфраструктурой подразумевается использование облачных ресурсов для хранения и обработки данных. Облачные технологические
решения масштабируемы и экономически эффективны, требуют меньше
технического сопровождения и предлагают разные типы услуг, включая инф
раструктуру как услугу (IaaS), платформу как услугу (PaaS) и программное
обеспечение как услугу (SaaS). Облачные вычислительные услуги принято
внедрять по причине их масштабируемости и гибкости, позволяющих им
добавлять и удалять ресурсы в зависимости от потребностей приложения.
Подобные услуги обычно предлагаются такими поставщиками, как Amazon
Web Services (AWS), Microsoft Azure и Google Cloud Platform (GCP), и включают
250 Выбор правильного фреймворка для приручения больших данных
в себя специальные услуги по обработке больших данных, например Amazon EMR, Azure HDInsight, Google Cloud Dataproc, и полностью управляемые
фреймворки для работы большими данными, такие как Hadoop, Spark и Flink,
которые оптимизированы под облака, обеспечивая масштабируемость, прос
тоту использования и экономичность решений без необходимости управления частной инфраструктурой. Благодаря этому организации могут сосредоточиваться на своих стержневых процессах и операциях, снимая с себя бремя
содержания современной инфраструктуры, которую необходимо постоянно
обновлять и поддерживать в рабочем состоянии. Однако использование облачных инфраструктур порождает многочисленные трудности, связанные
с конфиденциальностью и управлением данными, включая вопросы соблюдения безопасности, нормативных требований, юрисдикционных ограничений и контроля доступа к данным. В частности, поскольку нормативные
акты в отношении данных, такие как Общий регламент по защите данных
(GDPR, от англ. General Data Protection Regulation) и Калифорнийский закон
о конфиденциальности потребителей (CCPA, от англ. California Consumer
Privacy Act), требуют от организаций принятия мер по защите своих данных
и данных своих заказчиков, очень важно, чтобы инфраструктура публичного
облака соответствовала этим нормам во избежание юридических последствий. Более того, в разных странах и регионах правила защиты данных и законы о конфиденциальности могут существенно различаться. Поэтому при
использовании публичной облачной инфраструктуры важно понимать, где
хранятся ваши данные и как они обрабатываются. Также важно обеспечить
изолированность ваших данных от других пользователей и убеждаться в наличии у поставщика надежных средств контроля над доступом во избежание
утечки данных и несанкционированного доступа.
Гибридная инфраструктура является сочетанием вычислений, хранения
и услуг в разных средах, включая оконечные и облачные. Такая инфраструктура позволяет управлять рабочей нагрузкой между самыми разными средами, что дает возможность разрабатывать более универсальные и гибкие
решения. Она также обеспечивает поддержание конфиденциальности – за
счет локального хранения и обработки конфиденциальных данных – и способности адаптировать к потребностям организации посредством облачных
услуг с моделью ценообразования «оплата по мере продвижения проекта»,
которые позволяют эффективно масштабировать, настраивать под свои потребности и резервировать. В гибридных инфраструктурах, в которых используется сочетание оконечных и облачных компонентов, подходящим
вариантом выбора могут стать несколько фреймворков, поддерживающих
гибридную обработку данных. Apache Spark поддерживает чтение и запись
данных из нескольких источников, включая HDFS, сетевые файловые системы (NFS) и многие облачные услуги хранения объектов (например, Amazon S3, Azure Blob Storage и Google Cloud Storage). Благодаря этому можно
конфигурировать комлексные конвейеры данных, в которых данные могут
читаться из оконечных источников, а затем загружаться, обрабатываться
и сохраняться в облаке. Во многих других случаях для интеграции данных
Другие факторы 251
можно задействовать специальные платформы. Например, распределенная
платформа потоковой обработки данных Apache Kafka позволяет собирать
конвейеры реально-временных данных между оконечными и облачными
средами и часто используется в качестве слоя интеграции данных между
этими средами. То же самое касается и фреймворка Apache Airflow, который
можно использовать для внесения и обработки данных, обеспечивая бесшовный переток данных между оконечными и облачными средами.
6.4. Другие факторы
После рассмотренных выше главных факторов, подлежащих учету при выборе фреймворка программирования обработки больших данных для разработки приложений, интенсивных по привлечению данных, следует напомнить о других перечисленных ранее факторах, которые в той или иной
степени влияют на решения разработчиков о выборе подходящего фреймворка программирования. К дополнительным факторам, которые необходимо учитывать, относятся следующие:
навыки конструкторов и разработчиков: программисты обычно начинают со своих экспертных знаний в области программирования;
однако хотя инструменты высокоуровневого программирования и являются предпочтительными, хорошее знание механизмов низкоуровневого программирования помогает в достижении высокой производительности разрабатываемых приложений;
экосистема фреймворка программирования: приветствуется наличие
богатого комплекта инструментов, связанных с выбранным фреймворком; если фреймворк программирования является частью экосистемы инструментов, то продуктивность разработчика может возрастать
практически в разы;
размер сообщества: чем больше имеется разработчиков, технических
сопроводителей и пользователей среды программирования, которую
вы хотите использовать, тем легче получать поддержку в жизненном
цикле разработки;
требования к конфиденциальности данных: как мы уже упоминали ранее в разделе 6.1, посвященном входным данным, важную роль играют требования к конфиденциальности данных, так как они руководят
способами получения доступа к данным, проведения их анализа и их
обмена в разрабатываемом приложении;
затраты на программно-аппаратную инфраструктуру: при выборе
подходящего фреймворка программирования иногда также следует
учитывать стоимость аппаратной инфраструктуры, на которой будет
работать приложение после его внедрения;
наличие дополнительных библиотек: полезным элементом, который
следует учитывать при реализации сложных алгоритмов анализа или
252 Выбор правильного фреймворка для приручения больших данных
обучения, является легкость доступа к библиотекам анализа данных
и/или машинного обучения, интегрированным в выбранный фреймворк программирования;
уровень абстракции: хотя хорошее знание низкоуровневого программирования и помогает увеличивать производительность приложений,
выбор высокоуровневой модели программирования позволяет разработчикам абстрагироваться от деталей низкоуровневого параллелизма или распределения, при этом упрощая и ускоряя реализацию
приложений.
Сопутствующие
материалы
Дополнительные материалы включают:
лекционные слайды в формате PowerPoint;
лекционные слайды в формате PDF.
Онлайновый доступ предоставляется автоматически при приобретении
электронной копии данной книги через веб-сайт www.worldscientific.com.
Если вы приобрели печатную копию этой книги или электронную книгу
через другие каналы продаж, то рекомендуем следовать приведенным ниже
инструкциям по скачиванию файлов.
1. Перейдите на веб-сайт https://www.worldscientific.com/r/q0444-supp или
отсканируйте приведенный ниже QR-код.
2. Зарегистрируйте учетную запись/вход на веб-сайт.
3. Скачайте файлы, расположенные по адресу: https://www.worldscientific.
com/worldscibooks/10.1142/q0444#t=suppl.
При последующем доступе нужно просто регистрировать свой вход на вебсайт, используя те же регистрационные данные.
По всем вопросам обращайтесь по электронной почте: sales@wspc.com.sg.
Библиография
Abramova, V., Bernardino, J., and Furtado, P. (2014). Which NoSQL database? A performance overview (Какая СУБД NoSQL? Обзор производительности), Open Journal of
Databases (OJDB) 1 (2), 17–24.
Agrawal, D., Bernstein, P., Bertino, E., Davidson, S., Dayal, U., Franklin, M., Gehrke, J.,
Haas, L., Halevy, A., Han, J., et al. (2012). Challenges and Opportunities with Big Data.
A community white paper developed by leading researchers across the United States
(Вызовы и возможности больших данных. Технический документ сообщества,
разработанный ведущими исследователями США) (Computing Research Association, Washington).
Agrawal, R., and Shafer, J. (1996). Parallel mining of association rules (Параллельная
добыча ассоциативных правил), IEEE Transactions on Knowledge and Data Enginee
ring 8 (6), 962–969.
Barga, R., Gannon, D., and Reed, D. (2011). The client and the cloud: Democratizing research computing (Клиент и облако: демократизация научно-исследовательских
вычислений), IEEE Internet Computing 15 (1), 72–75.
Bauer, M., Treichler, S., Slaughter, E., and Aiken, A. (2012). Legion: Expressing locality and
independence with logical regions (Legion: выражение локальности и независимости
с помощью логических областей), in Proceedings of the International Conference on
High Performance Computing, Networking, Storage and Analysis, SC ’12 (IEEE Computer
Society Press, Washington, DC, USA). ISBN 9781467308045.
Bekkerman, R., Bilenko, M., and Langford, J. (2011). Scaling up Machine Learning: Parallel
and Distributed Approaches (Вертикальное масштабирование машинного обучения:
параллельный и распределенный подходы) (Cambridge University Press).
Belcastro, L., Marozzo, F., Talia, D., and Trunfio, P. (2017). Big data analysis on clouds
(Анализ больших данных в облаках), in A. Zomaya and S. Sakr (eds.), Handbook of Big
Data Technologies (Springer), pp. 101–142. ISBN: 978-3-319-49339-8.
Belcastro, L., Marozzo, F., Talia, D., and Trunfio, P. (2018). G-RoI: Automatic region-of-interest detection driven by geotagged social media data (Автоматическое обнаружение
областей заинтересованности на основе геотегированных данных социальной
сети), ACM Transactions on Knowledge Discovery from Data 12 (3), 27:1–27:22.
Belcastro, L., Marozzo, F., and Talia, D. (2019). Programming models and systems for big
data analysis (Модели и системы программирования для анализа больших данных),
International Journal of Parallel, Emergent and Distributed Systems 34, 632–652.
Belcastro, L., Cantini, R., Marozzo, F., Talia, D., and Trunfio, P. (2020). Learning political polarization on social media using neural networks (Усвоение политической
поляризации в социальных сетях с помощью нейронных сетей), IEEE Access 8 (1),
47177–47187.
Belcastro, L., Marozzo, F., and Perrella, E. (2021a). Automatic detection of user trajectories
from social media posts (Автоматическое обнаружение траекторий пользователей
на основе постов социальной сети), Expert Systems with Applications 186, 115733.
Библиография 255
Belcastro, L., Marozzo, F., Talia, D., and Trunfio, P. (2021b). Cloud computing for enabling
big data analysis (Облачные вычисления для обеспечения условия для анализа
больших данных), in The 10th International Conference on Cloud Computing and
Services Science (CLOSER 2020), pp. 84–109. ISBN 978-3-030-72369-9.
Belcastro, L., Cantini, R., Marozzo, F., Orsino, A., Talia, D., and Trunfio, P. (2022). Programming big data analysis: Principles and solutions (Программирование анализа
больших данных: принципы и решения), Journal of Big Data 9 (4), 1–50.
Bell, G., Hey, T., and Szalay, A. (2009). Beyond the data deluge (За пределами лавины
данных), Science 323 (5919), 1297–1298.
Bergman, K., Borkar, S., Campbell, D., Carlson, W., Dally, W., Denneau, M., Franzon, P., Harrod, W., Hill, K., Hiller, J., et al. (2008). Exascale computing study: Technology challenges
in achieving exascale systems (Исследование в области вычислений экзафлопсного
масштаба: технологические препятствия в разработке систем экзафлопсного
масштаба), Defense Advanced Research Projects Agency Information Processing Techniques Office (DARPA IPTO), Technical Report, Vol. 15, p. 181.
Beyer, M. A. and Laney, D. (2012). The importance of ‘big data’: A definition (Важность
«больших данных»: определение понятия) (Gartner, Stamford, CT), pp. 2014–2018.
Brin, S., and Page, L. (1998). The anatomy of a large-scale hypertextual web search engine
(Анатомия механизма крупномасштабного гипертекстового веб-поиска), Computer
Networks and ISDN Systems 30 (1–7), 107–117.
Brown, S. D., Francis, R. J., Rose, J., and Vranesic, Z. G. (1992). Field-Programmable Gate
Arrays (Полевые программируемые вентильные матрицы), Vol. 180 (Springer Science & Business Media).
Buyya, R. (1999). High Performance Cluster Computing (Высокопроизводительные клас
терные вычисления), Vol. 2 (New Jersey: Prentice).
Byun, C., Arcand, W., Bestor, D., Bergeron, B., Gadepally, V., Houle, M., Hubbell, M., Jananthan, H., Jones, M., Keville, K., Klein, A., Michaleas, P., Milechin, L., Morales, G., Mullen,
J., Prout, A., Reuther, A., Rosa, A., Samsi, S., Yee, C., and Kepner, J. (2022). pPython for
parallel python programming (pPython для параллельного программирования на Python), in 2022 IEEE High Performance Extreme Computing Conference (HPEC), pp. 1–6.
Cantini, R., Marozzo, F., Orsino, A., Talia, D., and Trunfio, P. (2021). Exploiting machine
learning for improving in-memory execution of data-intensive workflows on parallel machines (Задействование машинного обучения для улучшения резидентного
исполнения рабочих процессов, интенсивных по привлечению данных, на параллельных машинах), Future Internet 13 (5), 121.
Cao, L. (2017). Data science: A comprehensive overview (Наука о данных: всесторонний
обзор), ACM; Computing Surveys 50 (3–43), 1–42.
Cattell, R. (2011). Scalable SQL and NoSQL data stores (Масштабируемый SQL и хранилища
данных NoSQL), ACM SIGMOD Record 39 (4), 12–27.
Cesario, E., Marozzo, F., Talia, D., and Trunfio, P. (2017). SMA4TD: A social media analysis
methodology for trajectory discovery in large-scale events (SMA4TD: методика анализа
социальных сетей в области обнаружения траекторий среди крупномасштабных
событий), Online Social Networks and Media 3–4, 49–62.
Chang, F., Dean, J., Ghemawat, S., Hsieh, W. C., Wallach, D. A., Burrows, M., Chandra, T.,
Fikes, A., and Gruber, R. E. (2008). Bigtable: A distributed storage system for structured
data (Bigtable: система распределенного хранения для структурированных данных), ACM Transactions on Computer Systems (TOCS) 26 (2), 4.
256 Библиография
Chang, W. and Grady, N. (2015). NIST big data interoperability framework: Volume 1, big
data definitions (Фреймворк операционной совместимости больших данных NIST:
том 1, определения термина «большие данные»), Special Publication (NIST SP), National Institute of Standards and Technology, Gaithersburg, MD.
Charles, P., Grothoff, C., Saraswat, V., Donawa, C., Kielstra, A., Ebcioglu, K., Von Praun, C.,
and Sarkar, V. (2005). X10: An object-oriented approach to non-uniform cluster computing (X10: объектно ориентированный подход к неоднородным кластерным вы
числениям), ACM SIGPLAN Notices 40 (10), 519–538.
Da Costa, G., Fahringer, T., Rico-Gallego, J.-A., Grasso, I., Hristov, A., Karatza, H., Lastovetsky, A., Marozzo, F., Petcu, D., Stavrinides, G., Talia, D., Trunfio, P., and Astsatryan, H. (2015). Exascale machines require new programming paradigms and runtimes
(Машины экзафлопсного масштаба нуждаются в новых парадигмах программирования и средах исполнения), Supercomputing Frontiers and Innovations: International Journal 2 (2), 6–27.
De Mauro, A., Greco, M., and Grimaldi, M. (2015). What is big data? A consensual definition and a review of key research topics (Что такое большие данные? Консенсусное
определение и обзор ключевых тем исследований), in AIP Conference Proceedings,
Vol. 1644 (American Institute of Physics), pp. 97–104.
De Wael, M., Marr, S., De Fraine, B., Van Cutsem, T., and De Meuter, W. (2015). Partitioned
global address space languages (Языки разделенного глобального адресного прост
ранства), ACM Computing Surveys 47 (4), 1–27.
Dean, J. and Ghemawat, S. (2004). MapReduce: Simplified data processing on large clusters
(Упрощенная обработка данных в больших кластерах), in Proceedings of the 6th Conference on Symposium on Operating Systems Design & Implementation, OSDI’04, Vol. 6
(USENIX Association, USA), p. 10.
Deelman, E., Singh, G., Su, M.-H., Blythe, J., Gil, Y., Kesselman, C., Mehta, G., Vahi, K., Berriman, G. B., Good, J., et al. (2005). Pegasus: A framework for mapping complex scientific
workflows onto distributed systems (Pegasus: фреймворк для соотнесения сложных
научных рабочих процессов с распределенными системами), Scientific Programming 13 (3), 219–237.
Deitz, S. J., Chamberlain, B. L., and Hribar, M. B. (2006). Chapel: Cascade high-productivity
language: an overview of the chapel parallel programming model (Chapel: каскадный
высокопроизводительный язык: обзор модели параллельного программирования
на Chapel), Cray User Group.
Dijcks, J.-P. (2013). Oracle: Big Data for the Enterprise (Большие данные для предприятия)
(Oracle Corporation, USA).
Dongarra, J. J., Moler, C. B., Bunch, J. R., and Stewart, G. W. (1979). LINPACK Users’ Guide
(Руководство пользователя LINPACK) (SIAM).
Flynn, M. J. (1972). Some computer organizations and their effectiveness (Некоторые
компьютерные организации и их эффективность), IEEE Transactions on Computers
100 (9), 948–960.
Flynn, M. J. and Rudd, K. W. (1996). Parallel architectures (Параллельные архитектуры),
ACM Computing Surveys (CSUR) 28 (1), 67–70.
Fuerlinger, K., Fuchs, T., and Kowalewski, R. (2016). DASH: A C++ PGAS library for distribu
ted data structures and parallel algorithms (Библиотека PGAS на C++ для распре
деленных структур данных и параллельных алгоритмов), in 2016 IEEE 18th Interna-
Библиография 257
tional Conference on High Performance Computing and Communications (IEEE, Sydney,
Australia), pp. 983–990.
Gagliardi, F., Moreto, M., Olivieri, M., and Valero, M. (2019). The international race towards
exascale in Europe (Международная гонка за экзафлопсом в Европе), CCF Transactions on High Performance Computing 1 (1), 3–13.
Gajendran, S. K. (2012). A Survey on NoSQL Databases (Обзор СУБД NoSQL) (University
of Illinois).
Ganesh Chandra, D. (2015). Base analysis of NoSQL database (Базовый анализ СУБД
NoSQL), Future Generation Computer Systems 52, 13–21 (Special Section: Cloud Computing: Security, Privacy and Practice).
Gantz, J. and Reinsel, D. (2011). Extracting value from chaos (Добыча ценностей из хаоса),
IDC iView 1142, 1–12.
Gartner, Inc. (nd). What is big data? Gartner IT Glossary (Что такое большие данные?
ИТ-глоссарий Gartner), http://www.gartner.com/it-glossary/big-data.
Gates, A. F., Natkovich, O., Chopra, S., Kamath, P., Narayanamurthy, S. M., Olston, C., Reed,
B., Srinivasan, S., and Srivastava, U. (2009). Building a high-level dataflow system on top
of Map-Reduce: The Pig experience (Построение высокоуровневой системы потока
данных поверх Map-Reduce: опыт Pig), Proceedings of the VLDB Endowment 2 (2),
1414–1425.
Geist, A., Gropp, W., Huss-Lederman, S., Lumsdaine, A., Lusk, E., Saphir, W., Skjellum, T.,
and Snir, M. (1996). MPI-2: Extending the message-passing interface (Расширение
интерфейса передачи сообщений), in Euro-Par’96 Parallel Processing (Springer),
pp. 128–135.
Gilbert, S. and Lynch, N. (2002). Brewer’s conjecture and the feasibility of consistent,
available, partition-tolerant web services (Гипотеза Брюера и осуществимость состыкованных, доступных, устойчивых к разбиениям веб-сервисов), ACM SIGACT
News 33 (2), 51–59.
Grama, A., Gupta, A., Karypis, G., and Kumar, V. (2003). Introduction to Parallel Computing
(Введение в параллельные вычисления) (Addison Wesley, Harlow, England).
Gropp, W. and Snir, M. (2013). Programming for exascale computers (Программирование
компьютеров экзафлопсного масштаба), Computing in Science & Engineering 15 (6),
27–35.
Han, J., Pei, J., and Yin, Y. (2000). Mining frequent patterns without candidate generation
(Добыча часто встречающихся шаблонов без генерации кандидатов), SIGMOD Record 29 (2), 1–12.
Hashem, I. A. T., Yaqoob, I., Anuar, N. B., Mokhtar, S., Gani, A., and Khan, S. U. (2015). The
rise of “big data” on cloud computing: Review and open research issues (Возникновение
«больших данных» в облачных вычислениях: обзор и открытые проблемы исследования), Information Systems 47, 98–115.
Hey T., Tansley S., and Tolle, K. (2009). The Fourth Paradigm (Четвертая парадигма)
(Microsoft).
Huai, Y., Chauhan, A., Gates, A., Hagleitner, G., Hanson, E. N., O’Malley, O., Pandey, J.,
Yuan, Y., Lee, R., and Zhang, X. (2014). Major technical advancements in Apache Hive
(Самые важные технические усовершенствования в Apache Hive), in Proceedings of
the 2014 ACM SIGMOD International Conference on Management of Data, pp. 1235–
1246.
258 Библиография
Kalé, L. and Krishnan, S. (1993). CHARM++: A portable concurrent object oriented system
based on C++ (CHARM++: переносимая конкурентная объектно ориентированная
система на языке C++), in A. Paepcke (ed.), Proceedings of OOPSLA’93 (ACM Press),
pp. 91–108.
Kargupta, H., Park, B., Hershberger, D., and Johnson, E. (1999). Collective data mining:
A new perspective toward distributed data mining (Коллективная глубокая переработка данных: новый взгляд на распределенную глубокую переработку данных),
Advances in Distributed and Parallel Knowledge Discovery 2, 131–174.
Kornacker, M., Behm, A., Bittorf, V., Bobrovytsky, T., Ching, C., Choi, A., Erickson, J.,
Grund, M., Hecht, D., Jacobs, M., et al. (2015). Impala: A modern, open-source SQL engine for Hadoop (Impala: современный механизм SQL с открытым исходным кодом
для Hadoop), in CIDR, Vol. 1, p. 9.
Kumar, A. and Sebastian, T. M. (2012). Sentiment analysis on Twitter, International Journal
of Computer Science Issues (IJCSI) 9 (4), 372.
Laney, D. et al. (2001). 3d data management: Controlling data volume, velocity and variety
(Управление 3D-данными: управление объемом, быстротой и разнообразием дан
ных), META Group Research Note 6 (70), 1.
Li, A., Yang, X., Kandula, S., and Zhang, M. (2010). CloudCmp: Comparing public cloud
providers (CloudCmp: сравнение поставщиков публичных облаков), in Proceedings
of the 10th ACM SIGCOMM Conference on Internet Measurement, pp. 1–14.
Li, H., Wang, Y., Zhang, D., Zhang, M., and Chang, E. Y. (2008). PFP: Parallel FP-Growth for
query recommendation (PFP: параллельный алгоритм FP-Growth для рекомендации
запросов), in Proceedings of the 2008 ACM Conference on Recommender Systems, RecSys ’08 (Assoiation for Computing Machinery, New York, NY, USA), pp. 107–114. ISBN
9781605580937.
Lordan, F., Tejedor, E., Ejarque, J., Rafanell, R., Alvarez, J., Marozzo, F., Lezzi, D., Sirvent, R.,
Talia, D., and Badia, R. M. (2014). ServiceSs: An interoperable programming framework
for the cloud (ServiceSs: операционно-совместимая среда программирования для
облачных вычислений), Journal of Grid Computing 12, 67–91.
Louren¸co, J. R., Cabral, B., Carreiro, P., Vieira, M., and Bernardino, J. (2015). Choosing the
right NoSQL database for the job: A quality attribute evaluation (Выбор подходящей
СУБД NoSQL для работы: оценивание атрибутов качества), Journal of Big Data 2 (1),
1–26.
Lud¨ascher, B., Altintas, I., Berkley, C., Higgins, D., Jaeger, E., Jones, M., Lee, E. A., Tao, J., and
Zhao, Y. (2006). Scientific workflow management and the Kepler system (Управление
научным рабочим процессом и система Kepler), Concurrency and Computation: Practice and Experience 18 (10), 1039–1065.
Malewicz, G., Austern, M. H., Bik, A. J., Dehnert, J. C., Horn, I., Leiser, N., and Czajkowski, G. (2010). Pregel: A system for large-scale graph processing (Pregel: система для
крупномасштабной графовой обработки), in Proceedings of the 2010 ACM SIGMOD
International Conference on Management of Data (ACM), pp. 135–146.
Marozzo, F., Talia, D., and Trunfio, P. (2012). P2P-MapReduce: Parallel data processing
in dynamic cloud environments (P2P-MapReduce: параллельная обработка данных
в динамических облачных средах), Journal of Computer and System Sciences 78 (5),
1382–1402.
Marozzo, F., Talia, D., and Trunfio, P. (2018). A workflow management system for scalable
data mining on clouds (Система управления рабочими потоками для масштаби-
Библиография 259
руемой глубокой переработки данных в облаках), IEEE Transactions On Services
Computing 11 (3), 480–492. ISSN: 1939-1374.
Meijer, E. (2011). The World according to LINQ (Мир в понимании LINQ), Communications
of the ACM 54 (10), 45–51.
Mell, P., Grance, T., et al. (2011). The NIST definition of cloud computing (Определение
термина «облачные вычисления» по версии NIST), Special Publication (NIST SP),
National Institute of Standards and Technology, Gaithersburg, MD.
Mihalcea, R., and Tarau, P. (2004). TextRank: Bringing order into text (TextRank: привне
сение порядка в текст), in Proceedings of the 2004 Conference on Empirical Methods
in Natural Language Processing (Association for Computational Linguistics, Barcelona,
Spain), pp. 404–411.
Moniruzzaman, A. B. M. and Hossain, S. A. (2013). NoSQL database: New era of databases
for big data analytics – Classification, characteristics and comparison (СУБД NoSQL:
новая эра СУБД для аналитики больших данных – классификация, характеристики
и сравнение). CoRR abs/1307.0191.
Navarro, C. A., Hitschfeld-Kahler, N., and Mateu, L. (2014). A survey on parallel computing and its applications in data-parallel problems using GPU architectures (Обзор
параллельных вычислений и их применения в задачах с параллельностью данных
с использованием архитектур GPU), Communications in Computational Physics 15 (2),
285–329.
Nyce, C. and Cpcu, A. (2007). Predictive analytics white paper (Технический документ по
предсказательной аналитике) (American Institute for CPCU. Insurance Institute of
America), pp. 9–10.
Olston, C., Reed, B., Silberstein, A., and Srivastava, U. (2008a). Automatic optimization of
parallel dataflow programs (Автоматическая оптимизация параллельных программ
на основе модели потоков данных).
Olston, C., Reed, B., Srivastava, U., Kumar, R., and Tomkins, A. (2008b). Pig Latin: A notso-foreign language for data processing (Pig Latin: не совсем иностранный язык для
обработки данных), in Proceedings of the 2008 ACM SIGMOD international conference
on Management of data, pp. 1099–1110.
Otte, E. and Rousseau, R. (2002). Social network analysis: A powerful strategy, also for the
information sciences (Анализ социальных сетей: мощная стратегия, в том числе для
информационных наук), Journal of Information Science 28 (6), 441–453.
Owens, J. D., Houston, M., Luebke, D., Green, S., Stone, J. E., and Phillips, J. C. (2008). GPU
computing (Вычисления на базе GPU), Proceedings of the IEEE 96 (5), 879–899.
Papamichail, M., Diamantopoulos, T., and Symeonidis, A. (2016). User-perceived source code
quality estimation based on static analysis metrics (Оценивание качества исходного
кода по мнениям пользователей на основе метрик статического анализа), in 2016
IEEE International Conference on Software Quality, Reliability and Security (QRS),
pp. 100–107.
Prodromidis, A., Chan, P., Stolfo, S., et al. (2000). Meta-learning in distributed data mining
systems: Issues and approaches (Метаобучение в распределенных системах глубокой
переработки данных: Проблемы и подходы), Advances in Distributed and Parallel
Knowledge Discovery 3, 81–114.
Richardson, L. and Ruby, S. (2008). RESTful Web Services (Веб-службы RESTful) (O’Reilly
Media, Inc.).
260 Библиография
Rodriguez, M. A. and Neubauer, P. (2010). The graph traversal pattern (Схема обхода
графа), CoRR abs/1004.1001.
Salloum, S., Dautov, R., Chen, X., Peng, P. X., and Huang, J. Z. (2016). Big data analytics on
Apache Spark (Аналитика больших данных в Apache Spark), International Journal of
Data Science and Analytics 1 (3), 145–164.
Sarkar, A., Ghosh, A., and Nath, D. A. (2015). MapReduce: A comprehensive study on applications, scope and challenges (MapReduce: всестороннее исследование приложений,
сферы применения и проблем), Department of Computer Science, International Journal of Advance Research in Computer Science and Management Studies 3 (7).
Schroeck, M., Shockley, R., Smart, J., Romero-Morales, D., and Tufano, P. (2012). Analy
tics: The real-world use of big data (Аналитика: реально-практическое применение
больших данных), IBM Global Business Services, 1–20.
Singh, D. and Reddy, C. K. (2015). A survey on platforms for big data analytics (Обзор
платформ для аналитики больших данных), Journal of Big Data 2 (1), 1–20.
Skillicorn, D. B. and Talia, D. (1998). Models and languages for parallel computation (Мо
дели и языки для параллельных вычислений), ACM Computing Surveys (CSUR) 30 (2),
123–169.
Spezzano, G. and Talia, D. (1999). Calcolo parallelo, automi cellulari e modelli per sistemi
complessi (Параллельные вычисления, клеточные автоматы и модели сложных
систем), (FrancoAngeli).
Stonebraker, M. (2010). SQL databases v. NoSQL databases (СУБД SQL в сопоставлении
с СУБД NoSQL), Communications of the ACM 53 (4), 10–11.
Strohmaier, E., Meuer, H. W., Dongarra, J., and Simon, H. D. (2015). The TOP500 list and
progress in high-performance computing (Список TOP500 и прогресс в области
высокопроизводительных вычислений), Computer 48 (11), 42–49.
Talia, D. (2013). Workflow systems for science: Concepts and tools (Системы на основе
модели рабочих потоков для науки: концепции и инструменты), International
Scholarly Research Notices 2013.
Talia, D. (2019). A view of programming scalable data analysis: From clouds to exascale
(Взгляд на программирование масштабируемого анализа данных: от облаков до
экзафлопса), Journal of Cloud Computing 8 (1), 1–16.
Talia, D. and Trunfio, P. (2012). Service-Oriented Distributed Knowledge Discovery
(Сервис-ориентированное распределенное обнаружение знаний) (Chapman and
Hall/CRC).
Talia, D., Trunfio, P., and Marozzo, F. (2015). Data Analysis in the Cloud (Анализ данных
в облаке) (Elsevier). ISBN 978-0-12-802881-0.
Talia, D., Trunfio, P., Carrettero, J., and Garcia-Blas, J. (2022). Editorial research topic towards exascale solutions for big data computing (Редакционная тема исследования
в направлении экзафлопсных решений для вычислений с использованием больших
данных), Frontiers in Big Data, 4.
Talia, D., Trunfio, P., Marozzo, F., Belcastro, L., Garcia-Blas, J., Rio, D. D., Couvée, P., Go
ret, G., Vincent, L., Fernández-Pena, A., et al. (2019). A novel data-centric programming
model for large-scale parallel systems (Новая центрированная на данные модель
программирования для крупномасштабных параллельных систем), in European
Conference on Parallel Processing (Springer), pp. 452–463.
Библиография 261
Tan, P.-N., Steinbach, M., and Kumar, V. (2016). Introduction to Data Mining (Введение
в глубокую переработку данных) (Pearson Education India).
Tiskin, A. (1998). The bulk-synchronous parallel random access machine (Массовая
синхронная параллельная машина с произвольным доступом), Theoretical Computer Science 196 (1–2), 109–130.
UPC Consortium (2005). UPC language specifications (Спецификации языка UPC), v1. 2,
Lawrence Berkeley National Lab tech report lbnl-59208, Technical Report, Berkeley,
CA, USA.
Valiant, L. G. (1990). A bridging model for parallel computation (Мостовая модель для
параллельных вычислений), Communications of the ACM 33 (8), 103–111.
Van der Aalst, W. M. P., ter Hofstede, A. H. M., Kiepuszewski, B., and Barros, A. P. (2003).
Workflow patterns (Схемы рабочих потоков), Distributed and Parallel Databases 14
(1), 5–51.
Van der Aalst, W. M., and ter Hofstede, A. H. (2005). Yawl: Yet another workflow language
(Yawl: еще один язык для рабочих потоков), Information Systems 30 (4), 245–275.
Verma, A., Mansuri, A. H., and Jain, N. (2016). Big data management processing with Hadoop MapReduce and Spark technology: A comparison (Обработка больших данных
с помощью технологий Hadoop MapReduce и Spark: сравнение), in 2016 Symposium
on Colossal Data Analysis and Networking (CDAN) (IEEE), pp. 1–4.
Vukotic, A., Watt, N., Abedrabbo, T., Fox, D., and Partner, J. (2015). Neo4j in Action (Neo4j
в действии) (Manning).
Wadkar, S., Siddalingaiah, M., and Venner, J. (2014). Pro Apache Hadoop (Apache Hadoop
для профессионалов) (Apress).
Wu, D., Sakr, S., and Zhu, L. (2017). Big data programming models (Модели программирования для больших данных), in Handbook of Big Data Technologies (Springer),
pp. 31–63.
Zheng, Y., Kamil, A., Driscoll, M. B., Shan, H., and Yelick, K. (2014). UPC++: A PGAS extension for C++ (UPC++: расширение модели PGAS для языка C++), in 2014 IEEE 28th
International Parallel and Distributed Processing Symposium, pp. 1105–1114.
Zikopoulos, P., Eaton, C., deRoos, D., Deutch, T. and Lapis, G. (2011). Understanding Big
Data: Analytics for Enterprise Class Hadoop and Streaming Data (Понимание больших
данных: аналитика на основе Hadoop и потоковой обработки данных уровня пред
приятия), 1st edn. (McGraw-Hill Osborne Media). ISBN For Review / Sample Only
0071790535, 9780071790536.
Предметный указатель
A
C
ACID (атомарность, состыкованность,
изоляция и живучесть), 39, 96
aggregateMessages, 232
Airbnb, 170
Airflow, 121, 145, 151, 192, 195, 247, 251
Allegro, 37
allGather, 239
allGatherv, 239
allreduce, 185, 188
Amazon, 162
Amazon EMR, 250
Amazon Kinesis, 36
Amazon S3, 36, 122, 250
Amazon Web Services (AWS), 35, 56, 249
Ambari, 112, 122
Ansi-C, 31
Apache Derby, 173
Apache Spark, 121
API Dataset, 227
API Pregel, 163
API TaskFlow, 147, 150
AVG, 182
Avro, 34
AWS Lambda, 36
Azure Blob Storage, 250
Azure HDInsight, 250
C++, 108
Cassandra, 36
Cassandra Query Language (CQL), 31, 96,
98, 122
CFS, 31
Chapel, 107, 183
Charm++, 106
Clojure, 135
COGROUP, 177
Combiner, 115
CRCW, 94
CREW, 94
CSV, 246
Cypher, 38
B
EdgeTriplet, 162
Elastic Compute Cloud (EC2), 56
ELKI, 225
ERCW, 94
EREW, 94
bag, 177
BASE (базовая доступность,
мягкое состояние и окончательная
состыкованность), 39, 96
Berkeley Lab, 183
Bigtable, 31
BSON, 30
D
DASH, 183
DataFrame, 125, 128, 201, 217, 227
Dataset, 125
DBSCAN, 222, 225
DCEx, 107
DMCF (облачный фреймворк для
глубокой переработки данных), 121
DynamoDB, 31, 35, 56
E
F
Facebook, 170
Предметный указатель 263
finalize, 184
FLATTEN, 177, 181
Flink, 121, 161, 250
FOREACH, 177, 181
FPGrowth, 202, 203, 204, 205, 210
future, 185
G
GASNet, 184
gather, 239
Gelly, 161
GENERATE, 181
GFS, файловая система Google, 31
Giraph, 112, 161
GitHub, 196
Go, 31
Google, 74, 162, 166
Google App Engine, 52
Google Bigtable, 32
Google Cloud Dataflow, 56
Google Cloud Dataproc, 250
Google Cloud Platform (GCP), 55, 56, 249
Google Cloud Storage, 57, 250
Google Pregel, 161
GraphX, 122, 161, 164, 167, 197, 229, 230,
233, 242, 248
H
Hadoop, распределенная файловая
система (HDFS), 31, 33, 111, 112, 124,
172, 176, 179, 182, 245, 250
Hadoop, фреймворк, 33, 54, 74, 77, 98,
109, 110, 117, 161, 170, 176, 192, 205, 206,
212, 245, 249, 250
Hadoop Common, 112
Hadoop Query Language (HQL), 98
Hama, 161
HashtagCounter, 140, 142
HBase, 31, 33, 112
Hive, 98, 111, 170, 192, 195, 221, 246, 248
HiveQL, 170, 171, 173, 194
I
IaaS (инфраструктура как услуга), 52,
249
Impala, 169
InfiniteGraph, 37
init, 184
InputSplit, 115
J
Java, 31, 135, 140, 158, 195, 213, 234
Jinja, 145
JSON, 29, 114, 132, 149, 153, 246
JVM, 135, 213
K
Kafka, 251
KVM, 58
L
LAM/MPI, 154
Legion, 106
LinkedIn, 177
LINPACK, 60
LISP, 75
M
map, 177, 207, 232
Mapper, 115
MapReduce, 31, 74, 85, 96, 110, 114, 170,
176, 192, 197, 205
MapTask, 117
mapVertices, 232
MEM FS, 31
Mendeley, 177
Mesos, 122
messageCombiner, 168
Microsoft Azure, 52, 249
Microsoft SQL Server, 96, 98
Microsoft Visual Studio, 58
MLlib, 122, 204, 217, 245
MongoDB, 30, 96
MPI, 154
MPI_Barrier, 157
MPI_Bcast, 157
MPICH, 154
MPI_Gather, 157
MPI_Recv, 155
MPI_Reduce, 157
MPI_Scatter, 157
MPI_Send, 155
MySQL, 96, 98
264 Предметный указатель
N
Neo4j, 31, 37
Netflix, 162, 170
NoSQL, 122
O
Oozie, 112
OpenMP, 108
Open MPI, 154, 158, 235
OpenStack, 55, 58
OpenStack Swift, 122
Oracle, 96
OrientDB, 37
P
PaaS (платформа как услуга), 52, 249
PageRank, 162, 166, 230, 232, 236, 237
Partitioner, 115
PayPal, 177
Pig, 170, 176, 192, 246, 248
poke, 149
PostgreSQL, 96, 98
pPython, 183
Pregel, 161, 163, 165, 192, 197, 230, 233
Python, 31, 135, 145, 153, 171, 195, 213
R
R, 195
RecordReader, 115
RecordWriter, 115
Redis, 31, 34, 96
reduce, 207
Reducer, 115
ReducerTask, 119
reschedule, 149
Ruby, 31
S
SaaS (программное обечпечение как
услуга), 52, 249
Scala, 195, 200, 230
sendMessage, 168
SerDe, 173
Simple Storage Service (S3), 56
Spark, 85, 121, 161, 192, 200, 205, 212,
217, 229, 245
SparkContext, 128
Spark GraphX, 229
SparkSession, 128
Spark SQL, 121, 221, 227
Spark Standalone, 122
Spark Streaming, 122, 212, 217, 246
SplitHashtag, 140, 142
SQL, 96, 97, 169, 176, 217, 227, 248
SQL-подобный, 192, 197
Stack Overflow, 196
Stardog, 37
Storm, 112, 121, 134, 137, 192, 195, 212,
247
T
TaskFlow, 149
TextRank, 229, 232, 234, 236, 242
TOP500, список, 60
TopologyBuilder, 144
tuple, 177
TweetSpout, 140
Twitter, 140
U
UPC, 183
UPC++, 108, 183, 184, 186, 188, 192, 195,
197
V
vertexProgram, 168
Virtuoso, 37
VMware, 58
W
WSL2, 34
X
X10, 107, 183
X-информатика, 26
XCom, 149, 153
Y
Yahoo!, 33, 111, 170
YARN («еще один переговорщик по
ресурсам»), 112, 122
Предметный указатель 265
Z
ZooKeeper, 34, 112, 138
А
Абстракция, 73
Автоматизация, 56
Агрегирование, 210
Адрес глобально коллективный, 100
Аккумулятор, 126
Алгоритм распределенный, 68
Анализ
главных компонент, 245
данных, 177
масштабируемый, 80, 98
разведывательный, 25
рыночной корзины, 130
сетевой, 43
Анализатор настроений, 180
Аналитика
больших данных, 43, 121, 190
графовая, 43
данных, 25, 41, 170, 176, 229
высокопроизводительная
(HPDA), 59
предписательная, 43
предсказательная, 43
резидентная, 54, 104
текстовая, 43
управленческая, 171
Архитектура
«мастер–работник», 126
параллельного компьютера, 73
Атомарность, 39, 96
Б
База данных, 26, 28
графовая, 37, 41
документная, 40
ключей-значений, 40
столбцовая, 40
База метаданных, 146
Airflow, 149
Барьер, 92
Блок данных, 113
Блокировка, 88
живая, 93
мертвая (тупик), 92, 93
Бустинг, 69
Буферизация, 89
Бэггинг, 69
В
Валиант, 91
Веб-сервер, 146
Вершинно-центричность, 161, 165
Ветвление, 82
Взаимосвязь данных, 37
Визуализация данных, 43
Время
параллельного исполнения, 50
последовательного исполнения, 50
Вызов дистанционный процедур
(RPC), 108, 134, 157
Выполнение запросов к данным, 170,
177, 190, 192, 197
Вычисления
асинхронные, 185
высокопроизводительные (HPC),
технология, 23, 59, 81, 103, 105, 244
графовые, 161
конкурентные, 92
облачные, 51
параллельные, 45, 48
разнородные, 105
распределенные, 79, 85
с параллельностью графов, 230
Г
Генерирование отчетов, 170, 192
Голосование, 69, 150
Граф, 29, 161, 190
ориентированный ациклический
(DAG), 84, 121, 126, 145, 172, 213
ориентированный циклический
(DCG), 85
Графоориентированность, 242
Графопараллельность, 122, 161, 242
Группа
закрытая, 90
открытая, 90
Группировка перетасовкой, 144
Д
Данные
большие, 19, 23, 27, 72, 97, 190, 197,
243, 246
266 Предметный указатель
входные, 114
геопространственные, 201
промежуточные, 78
структурированные, 198, 227, 229
Даталогия, 22
Датчик, 147
Дерево
абстрактное синтаксическое, 172
решений, 64
часто встречающихся шаблонов
(FP-дерево), 203
Добыча
ассоциативных правил, 66, 204
траекторий, 199, 203, 205
Доля параллелизуемая, 50
Доступ
к данным параллельный, 104
к памяти
дистанционный, 184
неравномерный (NUMA), 101
Доступность, 38
в принципе, 39
Драйвер, 116, 171
Spark, 128
Е
Единица доступа к памяти (MAU), 94
Ж
Живучесть, 39, 96
З
Зависимость
от данных, 84
от управления, 84
Задвижка, 139
Задвижка поперечная, 134, 212
Задержка коммуникации, 91
Закон
Амдала, 50
калифорнийский
о конфиденциальности
потребителей, 250
Запрос
к данным, 177, 190, 197
множественный, 177
прямо на месте, 99
SQL-подобный, 169
Заявка вычислительная, 76
Зондирование, 149
И
Извлечение, преобразование и загрузка
(ETL), 98, 171, 177, 246
Изоляция, 39, 96
Индекс инвертированный, 76, 117
Институт стандартов и технологий
национальный (NIST), 21, 52
Инструкция параллельная, 74
Инструмент
программирования, 190
SQL-подобного
программирования, 169
Интенсивность по привлечению
данных, 22, 110, 198, 243
Интернет вещей (IoT), 19
Интерфейс
открытых облачных вычислений
(OCCI), 54
передачи сообщений (MPI), 74, 105,
108, 154, 192, 197, 229, 234, 239, 248
пользовательский, 171
Информация о преемственности, 123
Инфраструктура
высокопроизводительная, 154
гибридная, 250
крупномасштабная, 169
облачная, 249
оконечная, 249
Исполнение спекулятивное, 80
Исполнитель, 146
Исследователь данных, 25, 45, 68, 97, 170
Источник данных, 191
К
Кадр данных, 125, 217, 227
Каталог ориентированных
ациклических графов, 146
Квазиреферирование, 229
Кеширование, 125
Классифицирование, 64
Класс приложения, 194
Кластер
высокопроизводительный, 48
Storm, 137
Предметный указатель 267
Кластеризация, 65
Клиент-серверный, 48
Ключ–значение, 207
Коалиция по управлению рабочими
потоками, 81
Комбинатор, 78, 115, 117
Коммуникатор, 155
Коммуникация, 89, 92
глобальная, 92
коллективная, 157
локальная, 104
межпроцессная (IPC), 85
«многие к одному», 90
«многие ко многим», 90, 239, 240
многоадресная, 90
«один ко многим», 90
одноадресная, 89
сетевая в глобально-адресном
пространстве (GASNet), 184
точка–точка, 91, 154
Компилятор, 171, 179
Компонент
записывающий, 115
читающий, 115
Компонента связная, 44, 162
Компромиссы CAP, 40
Компьютер параллельный, 183
Конвейеризация, 192, 195
резидентная, 178
Конкурентность, 45
Контекст, 126
потоковый, 219
Конфиденциальность данных, 250
Концепции больших данных, 19
Кортеж, 135, 177, 212, 214
Кран выпускной, 134, 212
Кусок данных, 111, 123
Л
Лес случайный, 213, 216
Локальность
внутристоечная, 62
данных, 62, 79, 100, 104, 106, 108
межстоечная, 62
узловая, 62
М
Массив ассоциативный, 177
Масштабирование
вертикальное, 28, 48
горизонтальное, 28, 48
Масштабируемость, 75, 191
Масштаб экзафлопсный, 62, 102, 104
Матрица полевая программируемая
вентильная (FPGA), 48
Машина параллельная
массовая синхронная
с произвольным доступом
(BSPRAM), 94
с произвольным доступом (PRAM), 94
Метаобучение, 68
Метахранилище, 172
Метод Монте-Карло, 186
Метрика производительности, 50
Механизм
восстановления после ошибок, 123
запросов, 169
исполнения, 172, 179
Многословность, 191, 197
Многоядерный центральный
процессор, 48
Многоязычность, 215
Модель
асинхронная на основе разделенного
глобального адресного пространства
(APGAS), 183
вычисления, 73
данных вложенная, 177
масштабируемая, 73
программирования, 73
на основе рабочих потоков, 120
на основе разделенного
глобального адресного
пространства (PGAS), 182
распределения
блочная, 102
блочно-циклическая, 102
циклическая, 102
SQL-подобная, 96
Мультиграф, 162
Мультиядерный центральный
процессор, 48
Н
Набор
данных
крупномасштабный, 161
распределенный, 128
268 Предметный указатель
устойчивый распределенный
(RDD), 123, 161, 217, 227
Spark RDD, 162
заданий, 136
Надсмотрщик, 137
Накопление результатов работы всех
моделей (stacking), 68
Наука
вычислительная, 42
о данных, 22, 23, 24
электронная, 22
Неблокирующая передача
сообщений, 88
Несколько потоков инструкций,
несколько потоков данных (MIMD), 47
Несколько потоков инструкций, один
поток данных (MISD), 47
Нимб, 137
О
Облако
гибридное, 53
публичное, 53
частное, 53
Область заинтересованности (RoI), 199,
205, 221, 227
Обнаружение вторжения в сеть, 217
Обработка
графов, 195, 198
графовая, 190, 192, 197
естественного языка, 246
микропакетная, 221
онлайновая аналитическая
(OLAP), 171
пакетная, 190
потоковая, 190, 217, 247
реально-временная, 195
сообщений, 136
транзакций онлайновая (OLTP), 171
Обучение
ансамблевое, 68, 150
машинное, 25, 212
распределенное, 67
федеративное, 70
Объект
главный, 139
коллективный, 184
Объем данных, 245
Один поток инструкций, несколько
потоков данных (SIMD), 193
Одноранговая сеть, 48
Оператор, 145
Операции с плавающей запятой
в секунду (FLOPS), 60
Операция оконная, 221
Описатель заявки, 78
Оптимизатор, 179
Оптимизация
запросов, 178
логическая, 178
физическая, 178
Оркестровка, 56
Отбор данных, 104
Отказоустойчивость, 104
Отправка, 87
Оценивание ленивое, 124, 129, 178
Очередь, 89
П
Пакет, 177
данных, 190, 197
Память, 61
коллективная, 86, 94, 100, 105, 185
распределенная, 86, 183
Парадигма программирования, 73
Параллелизм, 45
данных, 110, 126, 135, 145, 155, 162,
170, 177, 184, 193
заданий, 64, 110, 126, 135, 145, 177, 193
массовый синхронный (BSP), 90, 161,
192, 230
независимый, 64
неявный, 101
одна программа, несколько
элементов данных (SPMD), 64, 101, 155
с распределенной памятью, 105
фермерский, 65
Параллельность
данных, 46, 237
заданий, 46
Парсер, 178
Передача сообщений, 85, 154, 192, 230
косвенная, 87
прямая, 87
Переменная широковещательная, 126
Перепланирование, 149
Предметный указатель 269
Переработка данных глубокая, 25, 212,
229
коллективная, 69
Перетасовка, 79, 80, 136
и сортировка, 79, 115
Периодичность, 91, 94
Планировщик, 146
изоляции для многоарендаторской
обработки, 137
План исполнения, 223
Платформа облачная, 51
Повторение, 83
Подсчет
количества треугольников, 162
параллельный, 210
Получатель
избирательный, 90
неизбирательный, 90
Получение, 87
Популярность, 195
Порция данных входная, 115
Последовательность, 82
Пост геотегированный, 199
Поток, 134
данных, 194, 198
дискретизированный (DStream), 217
заданий, 147
рабочий, 81, 145, 176, 192, 198
твитов, 140
Правило процедурное, 81
Преобразование, 123
Преобразование (map), 75, 80, 110, 114,
129, 229
Преобразователь, 115, 194, 205
Приложение
итеративное параллельное, 192
масштабируемое по обработке
больших данных, 109
пакетное, 111, 247
потоковое, 247
Примитив передачи сообщений, 87
Программа драйверная, 126
Программирование
распределенное, 191
резидентное, 121
функциональное, 75, 129
Продуктивность, 99
Пространство
адресное глобальное, 99
разделенное асинхронное
(APGAS), 101, 107, 183, 197
разделенное (PGAS), 99, 107,
182, 192
адресное коллективное, 86
Процесс, 81, 183
автономный, 66, 86
деловой, 24, 81
Процессор графический (GPU), 48
Пул
процессов-исполнителей, 127
узлов-работников, 126
Р
Рабочий поток как исходный код, 145
Разбиение
графов, 163
на разделы, 99
Раздел, 123
Разделитель, 115
Разделяй и властвуй, 46, 75
Разложение на сингулярные
значения, 245
Разрыв, 93
Ранг, 155, 183
Рандеву, 89
Распространенность, 195
Регламент общий по защите
данных, 250
Редуктор, 115, 194, 205
Редукция (reduce), 75, 110
Резидентность, 198
Резюмирование
абстрактивное, 229
статей, 229
экстрактивное, 229
Рекурсия, 84
Репликация, 36
С
Сеанс липкий, 35
Сегментация (шардирование), 32, 40,
210
Сегмент коллективный, 186
Семейство столбцов, 32
Сеть нейронная искусственная, 234
Синхронизация, 46, 83, 92, 104
барьерная, 92
270 Предметный указатель
Система
высокопроизводительных
вычислений (HPC), 44
массивно-параллельной
обработки, 63, 169
обнаружения вторжений, 217
в сеть (NIDS), 212
общецелевая, 195
отказоустойчивая, 75, 80, 104, 138,
197, 217
программирования, 74
реально-временной обработки, 134
управления
базами данных, 26
рабочими потоками (WMS), 81
файловая распределенная, 122
файловая сетевая (NFS), 250
хранения распределенная, 122
Ситуация «доходяга», 80
Склад данных, 43, 170, 229
Скрипт высокоуровневый, 193
Словарь, 177
Совместимость операционная, 98
Сообщение активное, 184
Сортировка вторичная, 117
Состояние мягкое, 39
Состыкованность, 38, 96
глобальная, 93
окончательная, 39, 96
Стабильность, 73
Стадия, 126
Стиль SQL-подобный, 176
Струя, 139
СУБД, 26, 28
реляционная, 28, 79, 96, 170
Супершаг, 91, 162
Схема прямо во время чтения, 170
Т
Табличка, 32
Таксономия Флинна, 46
Теорема
Брюера, 38
CAP, 38
Терпимость к разделениям, 38
Тип параллелизма, 109, 191
Топология, 135, 212
Точка заинтересованности (PoI), 199,
207, 221
Траектория, 199
Трансляция широковещательная, 91
Тройка реберная, 162
У
Узел
данных, 113
именной, 113
кластера, 124
Узел-мастер, 137
Узел-работник, 137
Указатель глобальный, 184, 188
Управляемость данными, 98
Уровень абстракции, 109, 124, 134, 145,
157, 170, 176, 184, 191
Ускорение, 36, 49
максимальное, 50
теоретическое, 50
Усреднение, 69
Устойчивость, 63
Устройство мобильное, 70
Ф
Фильтр, 85
фон Нейман, 91
Формат
входной, 114
данных, 191
Функция
пользовательская, 164, 171, 222, 227
агрегатная, 171, 222, 229
генерации таблиц, 171, 222
стоимости, 101
Х
Хеш-таблица распределенная, 29
Хранение данных, 28
Хранилище
данных
графовое, 29
документное, 29
ключей–значений, 29, 35
разреженное, 32
столбцовое, 29, 33
объектное облачное, 122
распределенное, 80, 86
Предметный указатель 271
Ц
Я
Цикл
произвольный, 83
структурированный, 83
Ядро фреймворка Spark, 123
Язык
выполнения запросов к данным
(DQL), 97
декларативный, 97
манипулирования данными
(DML), 97, 171
общецелевой, 74
описания аппаратного
обеспечения, 49
определения данных (DDL), 97, 171
предметно-специфичный, 74
разметки Keyhole (KML), 201, 207
SQL-подобный, 170
Ящик почтовый, 88
Ш
Шаблонизатор, 145
Шаблон рабочего потока, 82
Э
Экзабайт, 20
Экзафлопс, 59
Энергия, 61
Эффективность, 49
Книги издательства «ДМК Пресс»
можно купить оптом и в розницу на складе издательства по адресу:
Москва, ул. Электродная, д. 2, стр. 12, офис 7, тел. +7 (499) 322-19-38,
а также заказать на сайте www.dmkpress.com
с доставкой в любой регион РФ
Доминико Талия, Паоло Трунфио, Фабрицио Мароццо,
Лорис Белькастро, Риккардо Кантини и Алессио Орсино
Большие данные
Современные фреймворки и разработка приложений
Главный редактор
Зам. главного редактора
Мовчан Д. А.
Яценков В. С.
Перевод
Корректор
Верстка
Дизайн обложки
Логунов А. В.
Синяева Г. И.
Чаннова А. А.
Мовчан А. Г.
editor@dmkpress.com
Гарнитура PT Serif. Печать цифровая.
Усл. печ. л. 22,1. Тираж 100 экз.
Веб-сайт издательства: www.dmkpress.com