Hive SQL для компиляции MapReduce

Большие данные

Источник статьи:Техническая команда Meituan - Процесс компиляции Hive SQL
Статья длинная, рекомендуется сначала ее собрать

Hive — это система хранения данных на основе Hadoop, которая широко используется в крупных компаниях. Хранилище данных Meituan также построено на основе Hive, оно ежедневно выполняет около 10 000 вычислений Hive ETL и ежедневно отвечает за хранение и анализ сотен ГБ данных. Стабильность и производительность улья имеют решающее значение для нашего анализа данных.

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

Как MapReduce реализует базовые операции SQL

Прежде чем подробно объяснять компиляцию SQL в MapReduce, давайте рассмотрим принцип работы фреймворка MapReduce для реализации основных операций SQL.

Принцип реализации Join

select u.name, o.orderid from order o join user u on o.uid = u.uid;

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

Join 的实现原理

Принцип реализации Group By

select rank, isonline, count(*) from city group by rank, isonline;

Объедините поля GroupBy в выходное значение ключа Map и используйте сортировку MapReduce, чтобы сохранить LastKey на этапе Reduce, чтобы различать разные ключи. Процесс MapReduce выглядит следующим образом (конечно, это только для иллюстрации процесса агрегации без Hash на стороне Reduce):

Group By 的实现原

Принцип реализации Distinct

select dealid, count(distinct uid) num from order group by dealid;

При наличии только одного поля Distinct, если Hash GroupBy на этапе Map не учитывается, необходимо только объединить поле GroupBy и поле Distinct в выходной ключ Map, использовать сортировку MapReduce и использовать поле GroupBy в качестве ключа Reduce, и сохранить LastKey на этапе Reduce, т.е. можно сделать дедупликацию

Distinct 的实现原理

Что делать, если имеется несколько полей Distinct, как в следующем SQL-запросе?

select dealid, count(distinct uid), count(distinct date) from order group by dealid;

Есть две реализации:

  1. Если вы все еще следуете методу поля Distinct выше, то есть методу реализации, показанному на рисунке ниже, вы не можете сортировать по uid и дате соответственно, и вы не можете дедуплицировать через LastKey, и вам все равно нужно дедуплицировать в памяти через Hash на этапе сокращения.

    Distinct 的实现原理 - 多个 Distinct 字段 - 1

  2. Второй способ реализации может нумеровать все Distinct поля, и генерировать n строк данных для каждой строки данных, тогда те же самые поля будут сортироваться отдельно.На данный момент на этапе Reduce нужно записать только LastKey для удаления дубликатов.

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

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

    Distinct 的实现原理 - 多个 Distinct 字段 - 2

Процесс преобразования SQL в MapReduce

Поняв, как MapReduce реализует основные операции SQL, давайте посмотрим, как Hive преобразует SQL в задачи MapReduce Весь процесс компиляции разбит на шесть этапов:

  1. Antlr определяет правила грамматики SQL, завершает лексический анализ SQL, анализ грамматики и преобразует SQL в абстрактное синтаксическое дерево AST Tree
  2. Пройдите по дереву AST и абстрагируйте базовую единицу запроса QueryBlock.
  3. Пройдите QueryBlock и транслируйте его в дерево операций выполнения OperatorTree.
  4. Оптимизатор логического уровня выполняет преобразование OperatorTree, объединяет ненужные ReduceSinkOperators и уменьшает количество перемешиваемых данных.
  5. Обход OperatorTree и преобразование в задачи MapReduce
  6. Оптимизатор физического уровня преобразует задачи MapReduce и генерирует окончательный план выполнения.

Лексический анализ SQL фазы 1, синтаксический анализ

Antlr

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

Файл определения правил грамматики в Hive — это файл Hive.g до версии 0.10.По мере того, как правила грамматики становятся все более и более сложными, класс синтаксического анализа Java, сгенерированный правилами грамматики, может превышать максимальный верхний предел файла класса Java. В версии 0.11 Hive.g разделен на 5 файлов, 4 файла для лексических правил HiveLexer.g и грамматических правил SelectClauseParser.g, FromClauseParser.g, IdentifiersParser.g, HiveParser.g.

Абстрактное синтаксическое дерево Дерево AST

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

Следующая грамматика является правилом грамматики SelectStatement в Hive SQL. Можно увидеть, что SelectStatement содержитselect, from, where, groupby, having, orderbyОговорка о равенстве (В следующих правилах грамматики стрелка указывает на переписывание исходного оператора. После перезаписи будут добавлены некоторые специальные слова для обозначения конкретной грамматики, например TOK_QUERY для обозначения блока запроса)

selectStatement
   :
   selectClause
   fromClause
   whereClause?
   groupByClause?
   havingClause?
   orderByClause?
   clusterByClause?
   distributeByClause?
   sortByClause?
   limitClause?
 -> ^(TOK_QUERY fromClause ^(TOK_INSERT ^(TOK_DESTINATION ^(TOK_DIR TOK_TMP_FILE))
                     selectClause
 whereClause? groupByClause? havingClause? orderByClause? clusterByClause?
                     distributeByClause?
 sortByClause? limitClause?))
   ;

Пример SQL

Чтобы подробно объяснить процесс преобразования SQL в MapReduce, вот простой пример SQL, который содержит подзапрос и, наконец, записывает данные в таблицу.

FROM
(
  SELECT
    p.datekey datekey,
    p.userid userid,
    c.clienttype
  FROM
    detail.usersequence_client c
    JOIN fact.orderpayment p ON p.orderid = c.orderid
    JOIN dim.user du ON du.userid = p.userid
  WHERE p.datekey = 20131118
) base
INSERT OVERWRITE TABLE `test`.`customer_kpi`
SELECT
  base.datekey,
  base.clienttype,
  count(distinct base.userid) buyer_count
GROUP BY base.datekey, base.clienttype

SQL генерирует дерево AST

Код Antlr для синтаксического анализа Hive SQL выглядит следующим образом: HiveLexerX и HiveParser — это классы лексического синтаксического анализа и синтаксического анализа, автоматически генерируемые Antlr после компиляции файла грамматики Hive.g. В этих двух классах выполняется сложный синтаксический анализ.

HiveLexerX lexer = new HiveLexerX(new ANTLRNoCaseStringStream(command));    // 词法解析,忽略关键词的大小写
TokenRewriteStream tokens = new TokenRewriteStream(lexer);
if (ctx != null) {
  ctx.setTokenRewriteStream(tokens);
}
HiveParser parser = new HiveParser(tokens);                                 // 语法解析
parser.setTreeAdaptor(adaptor);
HiveParser.statement_return r = null;
try {
  r = parser.statement();                                                   // 转化为AST Tree
} catch (RecognitionException e) {
  e.printStackTrace();
  throw new ParseException(parser.errors);
}

Окончательное сгенерированное дерево AST показано в правой части рисунка ниже (сгенерировано с помощью Antlr Works, редактора для написания файлов грамматики, предоставленного Antlr).На рисунке развернуты только несколько узлов скелета, а не полностью расширен. Подзапрос 1/2, соответствующий частям 1/2 справа

AST Tree 1

Обратите внимание, что внутренний подзапрос также создает узел TOK_DESTINATION. Пожалуйста, ознакомьтесь с грамматическими правилами SelectStatement выше, этот узел является узлом, специально добавленным при переписывании грамматики. Причина в том, что все данные запроса в Hive будут сохранены во временных файлах HDFS.Будь то промежуточный подзапрос или окончательный результат запроса, оператор Insert в конечном итоге запишет данные в каталог HDFS, где находится таблица.

В частности, после расширения предложения from подзапроса памяти получается следующее дерево AST.Каждая таблица создает узел TOK_TABREF, а условие соединения создает узел "=". Другие части SQL аналогичны и подробно описываться не будут.

AST Tree 2

Базовый стандартный блок SQL Phase2 QueryBlock

Дерево AST по-прежнему очень сложное и недостаточно структурировано для прямого преобразования в программы MapReduce.Преобразование дерева AST в QueryBlock необходимо для дальнейшего абстрагирования и структурирования SQL.

QueryBlock

QueryBlock — это самая основная единица SQL, включающая три части: источник ввода, процесс вычисления и вывод. Проще говоря, QueryBlock — это подзапрос.

На следующем рисунке представлена ​​диаграмма классов связанных с QueryBlock объектов в Hive, поясняющая несколько важных свойств на диаграмме.

  • QB#aliasToSubq (представляющий атрибут aliasToSubq класса QB) содержит объект QB подзапроса, а значение ключа aliasToSubq является псевдонимом подзапроса.
  • QB#qbp означает, что QBParseInfo хранит структуру дерева AST данной части операции в базовой единице SQL. Insert, выходных данных может быть несколько), значением является соответствующий узел ASTNode, узел TOK_DESTINATION. Остальные свойства HashMap класса QBParseInfo соответственно сохраняют соответствующую связь между выходом и узлами ASTNode каждой операции.
  • QBParseInfo#JoinExpr содержит узел TOK_JOIN. QB#QBJoinTree — это структура синтаксического дерева соединения.
  • QB#qbm сохраняет метаинформацию каждой входной таблицы, такую ​​как путь к таблице в HDFS, формат файла для сохранения данных таблицы и т. д.
  • QBExpr Этот объект используется для представления операций Union.

QueryBlock 相关对象类图

Дерево AST генерирует QueryBlock

Процесс генерации дерева AST QueryBlock является рекурсивным процессом. Он проходит по дереву AST по порядку, сталкивается с различными узлами токена и сохраняет их в соответствующих атрибутах. В основном он включает следующие процессы:

  • TOK_QUERY => Создать объект QB, рекурсивно зациклить дочерние узлы
  • TOK_FROM => сохранить часть синтаксиса имени таблицы в объекте QB TOK_INSERT => зациклить рекурсивно дочерние узлы
  • TOK_DESTINATION => сохраняет синтаксическую часть цели вывода в свойстве nameToDest объекта QBParseInfo
  • TOK_SELECT => сохранить синтаксическую часть выражения запроса в destToAggregationExprs, TOK_WHERE => сохранить синтаксис части Where в свойстве destToWhereExpr объекта QBParseInfo

Окончательный образец SQL создает два объекта QB.Отношения между объектами QB следующие: QB1 — это внешний запрос, а QB2 — подзапрос.

Логический оператор Phase3 Оператор

Operator

Задача MapReduce, окончательно сгенерированная Hive, этап Map и этап Reduce состоят из OperatorTree. Логический оператор должен выполнить одну конкретную операцию на этапе сопоставления или на этапе сокращения.

Основные операторы включаютTableScanOperator,SelectOperator,FilterOperator,JoinOperator,GroupByOperator,ReduceSinkOperator

Из названия можно догадаться, какую функцию выполняет каждый оператор.TableScanOperatorДанные исходной входной таблицы из интерфейса Map платформы MapReduce контролируют количество строк данных в отсканированной таблице, а отметка предназначена для извлечения данных из исходной таблицы;JoinOperatorЗавершите операцию Присоединения;FilterOperatorзавершить операцию фильтрации;ReduceSinkOperatorСериализировать комбинацию полей на стороне карты в ключ/значение сокращения, ключ раздела, который может отображаться только на этапе карты, а также отмечает конец этапа карты в программе MapReduce, сгенерированной Hive.

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

Основные свойства и методы класса Operator следующие:

  • RowSchema представляет поля вывода оператора.
  • InputObjInspector OutputObjInspector анализирует поля ввода и вывода
  • processOp получает данные, переданные родительским оператором, и передает обработанные данные дочернему оператору для обработки.
  • После обработки оператором каждой строки данных в Hive поля будут перенумерованы.colExprMap записывает соответствие имён каждого выражения до и после обработки текущим оператором, что используется для трассировки имён полей на следующем этапе логики оптимизация.
  • Поскольку программа Hive MapReduce является динамической программой, то есть неизвестно, какую операцию будет выполнять задание MapReduce, которая может быть Join или GroupBy, поэтому Operator сохраняет все параметры, необходимые во время выполнения, в OperatorDesc и OperatorDesc перед отправкой задачи Serialized в HDFS, чтение из HDFS и десериализация перед выполнением задачи MapReduce. Расположение OperatorTree этапа карты в HDFS:Job.getConf("hive.exec.plan") + "/map.xml"

Operator

QueryBlock генерирует дерево операторов

QueryBlock создает дерево операторов, просматривая атрибуты сохраненного синтаксиса объектов QB и QBParseInfo, созданных в предыдущем процессе, включая следующие шаги:

  • QB#aliasToSubq => есть подзапрос, рекурсивный вызов
  • QB#aliasToTabs => TableScanOperator
  • QBParseInfo#joinExpr => QBJoinTree => ReduceSinkOperator + JoinOperator
  • QBParseInfo#destToWhereExpr => FilterOperator
  • QBParseInfo#destToGroupby => ReduceSinkOperator + GroupByOperator
  • QBParseInfo#destToOrderby => ReduceSinkOperator + ExtractOperator

Поскольку все соединения Join/GroupBy/OrderBy должны быть завершены на этапе сокращения, ReduceSinkOperator будет сгенерирован до того, как будет сгенерирован оператор соответствующей операции, а поля будут объединены и сериализованы в ключ/значение сокращения, ключ раздела.

Затем подробно проанализируйте процесс создания OperatorTree из примера SQL.

Предварительный обход объектов QB, созданных на предыдущем этапе

  1. Сначала по дочернему QueryBlockQB2#aliasToTabs {du=dim.user, c=detail.usersequence_client, p=fact.orderpayment}Сгенерировать TableScanOperator

    TableScanOperator("dim.user") TS[0]
    TableScanOperator("detail.usersequence_client") TS[1]
    TableScanOperator("fact.orderpayment") TS[2]
    
  2. обход предварительного заказаQBParseInfo#joinExprгенерироватьQBJoinTree,своего родаQBJoinTreeтакже является древовидной структурой,QBJoinTreeСохраните ASTNode левой и правой таблиц и псевдоним этого запроса, и результирующее дерево запросов будет выглядеть следующим образом.

  3. обход предварительного заказаQBJoinTree, сгенерировать сначалаdetail.usersequence_clientиfact.orderpaymentДерево операций соединения

    qb-to-operator-1
    На рисунке TS=TableScanOperator RS=ReduceSinkOperator JOIN=JoinOperator

    Создайте дерево операций соединения между промежуточной таблицей и dim.user

    qb-to-operator-2

    Согласно QB2QBParseInfo#destToWhereExprСоздайте оператор фильтра. На этом обход QB2 завершен.На следующем рисунке SelectOperator будет судить, нужно ли анализировать поле в соответствии с некоторыми условиями в соответствии с некоторыми условиями.

    qb-to-operator-3
    На рисунке FIL=FilterOperator SEL=SelectOperator

    Согласно QB1QBParseInfo#destToGroupbyгенерироватьReduceSinkOperator + GroupByOperator

    qb-to-operator-4
    На рисунке GBY=GroupByOperator GBY[12] — агрегация HASH, то есть операция агрегации выполняется в памяти через Hash

    После завершения окончательного анализаFileSinkOperator, записать данные в HDFS

    qb-to-operator-5
    На рисунке FS=FileSinkOperator

Оптимизатор логического уровня Phase4

Большинство оптимизаторов логического уровня достигают цели сокращения заданий MapReduce и перемешивания объема данных путем преобразования OperatorTree и объединения операторов.

название эффект
② Оптимизатор SimpleFetch Оптимизация агрегированных запросов без выражений GroupBy
② MapJoinProcessor MapJoin, требует подсказок в SQL, версия 0.11 больше не используется
② BucketMapJoinOptimizer BucketMapJoin
② Группировать по оптимизатору Агрегация на стороне карты
① Сокращение SinkDeDupplication Объединить редукторы с одним и тем же ключом раздела/сортировки в линейном OperatorTree
① PredicatePushDown предикат
① Оптимизатор корреляции Воспользуйтесь преимуществами корреляции в запросах, объедините задания с зависимостями, HIVE-2206
ColumnPruner обрезка полей

Оптимизаторы ① в таблице — это все одно задание, выполняющее как можно больше операций/слияний. ② Все, чтобы уменьшить количество данных в случайном порядке, и даже не делать Уменьшить

Оптимизатор CorrelationOptimizer очень сложен и может использовать корреляцию в запросе для объединения связанных заданий, см.Hive Correlation Optimizer

Для примера SQL есть два оптимизатора, которые оптимизируют его. Роли этих двух оптимизаторов описаны ниже, и добавлена ​​роль оптимизатора ReduceSinkDeDupplication.

Оптимизатор PredicatePushDown

Утверждают, что ранний оптимизатор продвигает FilterOperator в OperatorTree до TableScanOperator.

PredicatePushDown优化器

Оптимизатор NonBlockingOpDeDupProc

Оптимизатор NonBlockingOpDeDupProc объединяет SEL-SEL или FIL-FIL в один оператор.

NonBlockingOpDeDupProc 优化器

Оптимизатор ReduceSinkDeDupplication

ReduceSinkDeDupplication может объединить два RS, которые линейно связаны. По сути, CorrelationOptimizer — это надмножество ReduceSinkDeDuplication, которое может сочетать линейные и нелинейные операции RS, но ReduceSinkDeDuplication, реализованный Hive в первую очередь

Например, следующая инструкция SQL

from (select key, value from src group by key, value) s select s.key group by s.key;

После первых нескольких этапов будет сгенерировано следующее OperatorTree.Два дерева соединены и здесь не рисуются вместе.

OperatorTree

В это время, после обхода OperatorTree, вы можете найти значение ключа и вывод PartitionKey двумя RS до и после, как показано ниже.

Оптимизатор ReduceSinkDeDuplication обнаруживает: 1. ключ pRS полностью содержит ключ cRS, и порядок сортировки является постоянным 2. ключ pRS PartitionKey полностью содержит ключ раздела cRS. Если условия оптимизации соблюдены, план выполнения будет оптимизирован.

ReduceSinkDeDupplication удаляет оператор между childRS и parentheRS и childRS, зарезервированный ключ RS — это поля ключа и значения, а PartitionKey — поле ключа. Объединенное дерево операторов выглядит следующим образом:

Процесс Phase5 OperatorTree, генерирующий MapReduce Job

Процесс конвертации OperatorTree в MapReduce Job делится на следующие этапы

  1. Создать MoveTask для выходной таблицы
  2. Обход в глубину вниз от одного из корневых узлов OperatorTree
  3. ReduceSinkOperator отмечает границы Map/Reduce, границы между несколькими заданиями.
  4. Пройдите через другие корневые узлы, столкнитесь с JoinOperator и объедините MapReduceTask
  5. Создайте StatTask для обновления метаданных
  6. Сократите отношение оператора между Map и Reduce

Создать MoveTask для выходной таблицы

На предыдущем шаге оператором OperatorTree был сгенерирован только один FileSinkOperator, а MoveTask был сгенерирован непосредственно для завершения перемещения окончательно сгенерированного временного файла HDFS в целевой каталог таблицы.

MoveTask[Stage-0]
Move Operator

начать движение

Сохраните все корневые узлы в OperatorTree в массиве toWalk и зациклите элементы в массиве (QB1 опущен, не показан)

Возьмите последний элемент TS[p] и поместите его в стек opStack{TS[p]}

Правило №1 TS% Создать объект MapReduceTask, определить MapWork

Обнаружено, что элементы в стеке соответствуют следующему правилу R1 (представленному здесь кодом Python)

"".join([t + "%" for t in opStack]) == "TS%"

генерироватьMapReduceTask[Stage-1]объект,MapReduceTask[Stage-1]объектMapWorkСвойство содержит ссылку на корневой узел оператора. Из-за отношения Parent Child между OperatorTree в настоящее время MapReduceTask[Stage-1] содержит все операторы с TS[p] в качестве корня.

Правило №2 TS%.*RS% Определить ReduceWork

Продолжайте обход подоператоров TS[p] и сохраняйте подоператоры в стеке opStack После того, как первый RS будет помещен в стек, то есть когда стек opStack = {TS[p], FIL[ 18], RS[4]}, то удовлетворяло бы следующему правилу R2

"".join([t + "%" for t in opStack]) == "TS%.*RS%"

В настоящее времяMapReduceTask[Stage-1]объектReduceWorkсохранение собственностиJOIN[5]цитаты

Правило №3 RS%.*RS% Создание нового объекта MapReduceTask и разделение MapReduceTask

Продолжить обход подоператора JOIN[5] и сохранить подоператор в стеке opStack.

Когда второй RS помещается в стек, то есть когда стекopStack = {TS[p], FIL[18], RS[4], JOIN[5], RS[6]}, то выполняется следующее правило R3

"".join([t + "%" for t in opStack]) == “RS%.*RS%” # 循环遍历opStack的每一个后缀数组

Теперь создайте новыйMapReduceTask[Stage-2]объект, изменяя OperatorTree сJOIN[5]иRS[6]вырезать между ними, и дляJOIN[5]Генерация дочернего оператораFS[19],RS[6]генерироватьTS[20],MapReduceTask[Stage-2]объектMapWorkсохранение собственностиTS[20]цитаты.

вновь сгенерированныйFS[19]Загрузите промежуточные данные и сохраните их во временных файлах HDFS.

Продолжить обход субоператора RS[6] и сохранить субоператор в стеке opStack

когдаopStack = {TS[p], FIL[18], RS[4], JOIN[5], RS[6], JOIN[8], SEL[10], GBY[12], RS[13]}, то выполняется правило R3

Сгенерировать таким же образомMapReduceTask[Stage-3]объект и вырезать OperatorTree из Stage-2 и Stage-3

Rule4 FS% соединяет MapReduceTask и MoveTask

После окончательного помещения всех дочерних операторов в стек,opStack = {TS[p], FIL[18], RS[4], JOIN[5], RS[6], JOIN[8], SEL[10], GBY[12], RS[13], GBY[14], SEL[15], FS[17]}Удовлетворить правилу R4

"".join([t + "%" for t in opStack]) == “FS%”

В это время будетMoveTaskиMapReduceTask[Stage-3]объединить и создатьStatsTask, изменить метаинформацию таблицы

Объединить этапы

На этом все не закончилось, остались еще два непройденных корневых узла

Очистите стек opStack и добавьте в стек второй элемент toWalk. найдуopStack = {TS[du]}Продолжайте удовлетворять R1 TS%, генерируйтеMapReduceTask[Stage-5]

Продолжайте движение вниз от TS[du], когдаopStack={TS[du], RS[7]}когда выполняется правило R2 TS%.*RS%

В это время будетJOIN[8]Сохранить какMapReduceTask[Stage-5]изReduceWorkКогда найдена связь между оператором, сохраненным в объекте карты, и объектом MapReduceWork.Map<Operator, MapReduceWork>найденный в объекте,JOIN[8]уже существует. В это время будетMapReduceTask[Stage-2]иMapReduceTask[Stage-5]Объединены в одну MapReduceTask

Аналогично из последнего корневого узлаTS[c]Начать обход, также объединить MapReduceTask

Разделить карту Уменьшить этап

Последний этап — вырезать OperatorTree в MapWork и ReduceWork с RS в качестве границы.

OperatorTree генерирует обзор MapReduceTask

Наконец, всего генерируются 3 MapReduceTasks, как показано ниже.

Оптимизатор физического уровня Phase6

Принцип работы каждого оптимизатора здесь подробно описываться не будет, но оптимизатор MapJoin будет представлен отдельно.

Принцип MapJoin

MapJoin 原理

Проще говоря, MapJoin считывает небольшую таблицу в память на этапе Map и последовательно сканирует большую таблицу, чтобы завершить соединение.

На приведенном выше рисунке представлена ​​схема Hive MapJoin из статьи инженера Facebook Лийина Танга, представляющего слайс, оптимизированный с помощью Join.Из рисунка видно, что MapJoin разделен на два этапа:

  1. Через MapReduce Local Task считывайте небольшие таблицы в память, генерируйте HashTableFiles и загружайте их в Distributed Cache, где HashTableFiles будут сжаты
  2. Задание MapReduce На этапе Map каждый Mapper считывает HashTableFiles из распределенного кэша в память, последовательно сканирует большую таблицу, напрямую объединяется на этапе Map и передает данные следующей задаче MapReduce.

Если одна из двух таблиц Join является временной таблицей, будет сгенерирована ConditionalTask, чтобы определить, использовать ли MapJoin во время работы.

Оптимизатор CommonJoinResolver

Оптимизатор CommonJoinResolver преобразует CommonJoin в MapJoin Процесс преобразования выглядит следующим образом:

  1. Обход дерева задач в глубину
  2. Найдите JoinOperator и оцените размер данных левой и правой таблиц.
  3. Для маленькой таблицы + большой таблицы => MapJoinTask, для маленькой/большой таблицы + промежуточной таблицы => ConditionalTask

Просмотрите задачи MapReduce, созданные на предыдущем этапе, и найдите, чтоMapReduceTask[Stage-2],JOIN[8]Одна из таблиц является временной, сначала сделайте глубокую копию Stage-2 (поскольку исходный план выполнения необходимо сохранить как план резервного копирования, поэтому вот копия плана выполнения), сгенерируйте MapJoinOperator вместо JoinOperator, а затем сгенерируйте MapReduceLocalWork для чтения Generate HashTableFiles из небольших таблиц и загрузки их в DistributedCache.

Преобразованный план выполнения MapReduceTask показан на следующем рисунке.

Оптимизатор MapJoinResolver

Оптимизатор MapJoinResolver просматривает дерево задач и разбивает все задачи MapReduceTask с локальной работой на две задачи.

После обработки окончательного MapJoinResolver план выполнения показан на следующем рисунке.

Проектирование процесса компиляции Hive SQL

Из всего процесса компиляции SQL, упомянутого выше, видно, что дизайн процесса компиляции имеет несколько преимуществ, которые стоит изучать и изучать.

  • Использование программного обеспечения с открытым исходным кодом Antlr для определения правил грамматики значительно упрощает процесс компиляции и анализа лексики и грамматики, и требуется только поддерживать файл грамматики.
  • Общая идея очень ясна.Поэтапный дизайн упрощает поддержку всего кода процесса компиляции, что позволяет удобно переключать различные последующие оптимизаторы подключаемым способом.Например, последние функции Hive 0.13, векторизация и поддержка движка Tez подключаемые.
  • Каждый оператор выполняет только одну функцию, что упрощает всю программу MapReduce.