MapReduce, как разрешить Mapper читать XML-файл для поиска

1

В моих заданиях MapReduce я передаю имя продукта в Mapper как строковый аргумент. Mapper.py script импортирует вторичный script, называемый Process.py, который что-то делает с именем продукта и возвращает некоторые испускающие строки в Mapper. Затем он преобразует эти строки в структуру Hadoop, чтобы их можно было взять с помощью редуктора. Все работает отлично, за исключением следующего:

В Process.py script содержится словарь значений поиска, которые я хочу переместить из script в xml файл для более легкого обновления. Я тестировал это локально, и он отлично работает, если я включаю путь Windows к XML файлу в Process.py script. Однако тестирование этого параметра в среде Hadoop MapReduce не работает по какой-либо причине.

Я попытался указать путь HDFS к XML-документу внутри Process.py script, и я попытался добавить имя XML-документа в качестве аргумента -file в команде задания MapReduce, но ни один из них не работал.

Например, внутри Process.py я попытался:
xml_file = r'[email protected]:/nfs_home/appers/cnielsen/product_lookups.xml '
и
xml_file = r '/nfs_home/appers/cnielsen/product_lookups.xml'

В команде MapReduce я включил имя xml файла в качестве аргумента -file. Например:
... -file product_lookups.xml -reducer...

Вопрос: В среде MapReduce, как мне разрешить Process.py script читать этот XML-документ, который хранится на HDFS?

Теги:
hadoop
mapreduce

2 ответа

1
Лучший ответ

Вот пример конца, который адаптирует методы, упомянутые в этом предыдущем вопросе, ближе к вашему вопросу.

Python читает файл как поток из HDFS

Это небольшое приложение Python Hadoop Streaming, которое считывает пары ключ-значение, проверяет ключ на файле конфигурации XML, хранящемся в HDFS, и затем испускает значение только в том случае, если ключ соответствует конфигурации. Логика соответствия отключена в отдельный модуль Process.py, который считывает файл конфигурации XML из HDFS, используя внешний вызов hdfs dfs -cat.

Сначала мы создаем каталог с именем pythonapp, содержащий исходные файлы Python для нашей реализации. Мы увидим позже, когда мы представим потоковое задание, которое мы передадим этому каталогу в аргументе -files.

Почему мы помещаем файлы в промежуточный каталог вместо простого перечисления каждого файла отдельно в аргументе -files? Это потому, что, когда YARN локализует файлы для выполнения в контейнерах, он вводит слой символической ссылки. Python не может правильно загрузить модуль через символическую ссылку. Решение состоит в том, чтобы упаковать оба файла в один и тот же каталог. Затем, когда YARN локализует файлы, символическая ссылка выполняется на уровне каталога вместо отдельных файлов. Поскольку основной script и модуль физически находятся в одном каталоге, Python сможет правильно загрузить модуль. Этот вопрос более подробно объясняет проблему:

Как импортировать настраиваемый модуль в задание MapReduce?

Mapper.py

import subprocess
import sys
from Process import match

for line in sys.stdin:
    key, value = line.split()
    if match(key):
        print value

Process.py

import subprocess
import xml.etree.ElementTree as ElementTree

hdfsCatProcess = subprocess.Popen(
        ['hdfs', 'dfs', '-cat', '/pythonAppConf.xml'],
        stdout=subprocess.PIPE)
pythonAppConfXmlTree = ElementTree.parse(hdfsCatProcess.stdout)
matchString = pythonAppConfXmlTree.find('./matchString').text.strip()

def match(key):
    return key == matchString

Затем мы помещаем 2 файла в HDFS./testData - это входной файл, содержащий пары ключ-значение с разделителями табуляции. /pythonAppConf.xml - это XML файл, в котором мы можем настроить конкретный ключ для соответствия.

/Testdata​​h1 >

foo 1
bar 2
baz 3

/pythonAppConf.xml

<pythonAppConf>
    <matchString>foo</matchString>
</pythonAppConf>

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

> hadoop jar share/hadoop/tools/lib/hadoop-streaming-*.jar \
      -D mapreduce.job.reduces=0 \
      -files pythonapp \
      -input /testData \
      -output /streamingOut \
      -mapper 'python pythonapp/Mapper.py'

> hdfs dfs -cat /streamingOut/part*
1   

Альтернативный способ сделать это - указать файл HDFS в аргументе -files. Таким образом, YARN вытащит XML файл в качестве локализованного ресурса для отдельных узлов, запускающих контейнеры перед запуском Python script. Затем код Python может открыть XML файл, как если бы он был локальным файлом в рабочем каталоге. Для очень больших заданий, выполняющих несколько задач/контейнеров, этот метод, скорее всего, превзойдет вызов hdfs dfs -cat из каждой задачи.

Чтобы протестировать эту технику, мы можем попробовать другую версию модуля Process.py.

Process.py

import xml.etree.ElementTree as ElementTree

pythonAppConfXmlTree = ElementTree.parse('pythonAppConf.xml')
matchString = pythonAppConfXmlTree.find('./matchString').text.strip()

def match(key):
    return key == matchString

Вызов командной строки изменяется для указания пути HDFS в -files, и еще раз мы видим ожидаемые результаты.

> hadoop jar share/hadoop/tools/lib/hadoop-streaming-*.jar \
      -D mapreduce.job.reduces=0 \
      -files pythonapp,hdfs:///pythonAppConf.xml \
      -input /testData \
      -output /streamingOut \
      -mapper 'python pythonapp/Mapper.py'

> hdfs dfs -cat /streamingOut/part*
1   

В документации Apache Hadoop обсуждается использование опции -files для локального размещения файлов HDFS.

http://hadoop.apache.org/docs/r2.7.1/hadoop-streaming/HadoopStreaming.html#Working_with_Large_Files_and_Archives

  • 0
    Моя проблема не та, что была задана в предыдущем вопросе, который вы задали. Я использую потоковый MapReduce для большого набора данных, но вместо того, чтобы Mapper делал всю тяжелую работу, он использует вспомогательный скрипт с именем Process.py. Mapper <--> Process.py Mapper по-прежнему направляет вывод в Reducer. Я хочу, чтобы скрипт Process.py мог читать небольшой файл XML, который находится в HDFS, чтобы построить словарь Python в памяти. Это работает локально на моем ПК, потому что я могу просто указать путь к файлу xml и использовать lxml для его анализа.
  • 0
    @ChrisNielsen, те же методы могут быть применены к вашему вопросу. Я отредактировал свой ответ, чтобы показать полный сквозной рабочий пример. Я также описал, как вы можете использовать аргумент -files чтобы сообщить YARN о необходимости извлечения файла в качестве локального ресурса перед запуском скрипта Python, что, возможно, проще и может обеспечить более высокую производительность для очень больших заданий.
Показать ещё 1 комментарий
0

Спасибо Крису Найроту за ответы, которые он изложил выше. С этой записью я хочу точно рассказать о том, что именно это решило мою проблему.

Второй ответ, который он дал, очень близок к тому, что я изначально пытался сделать. То, что я узнал, состоит в том, что для меня это привело несколько небольших изменений. Например, в Process.py script я ранее пытался включить полный путь к маленькому поисковому xml, например:

xml_file = r'[email protected]:/nfs_home/appers/cnielsen/product_lookups.xml'
и
xml_file = r'/nfs_home/appers/cnielsen/product_lookups.xml'

Оказывается, все, что мне нужно было сделать, это указать имя файла в моем Process.py script без пути. Например:
xml_file = 'product_lookups.xml'

Затем для фактической команды Hadoop, где я ранее пробовал это безуспешно: (используя -file product_lookups.xml после списка -mapper)

  > hadoop jar /share/hadoop/tools/lib/hadoop-streaming.jar \
  -file /nfs_home/appers/cnielsen/Mapper.py \
  -file /nfs_home/appers/cnielsen/Reducer.py \
  -mapper '/usr/lib/python_2.7.3/bin/python Mapper.py ProductName' \
  -file Process.py \
  -file product_lookups.xml \
  -reducer '/usr/lib/python_2.7.3/bin/python Reducer.py' \
  -input /nfs_home/appers/extracts/*/*.xml \
  -output /user/lcmsprod/output/cnielsen/test47

Правильный способ создания команды Hadoop - использовать -files и перечислить этот файл поиска перед любыми другими файлами. Например, это сработало:

  > hadoop jar /share/hadoop/tools/lib/hadoop-streaming.jar \
  -files /nfs_home/appers/cnielsen/product_lookups.xml \
  -file /nfs_home/appers/cnielsen/Mapper.py \
  -file /nfs_home/appers/cnielsen/Reducer.py \
  -mapper '/usr/lib/python_2.7.3/bin/python Mapper.py ProductName' \
  -file Process.py \
  -reducer '/usr/lib/python_2.7.3/bin/python Reducer.py' \
  -input /nfs_home/appers/extracts/*/*.xml \
  -output /user/lcmsprod/output/cnielsen/test47

Примечание. Несмотря на то, что эта страница говорит, что нужно построить команду -files следующим образом:

-files hdfs://host:fs_port/user/testfile.txt

Это не сработало для меня, если я включил hdfs://или часть хоста: как видно из фактической команды, указанной выше.

  • 0
    Спасибо за обобщение здесь. Я также хотел упомянуть, что есть причина, по которой я упаковал 2 файла Python в промежуточный каталог для передачи аргументу -files . Я только отредактировал свой ответ, чтобы объяснить это, и ссылку на предыдущий ответ, который предоставляет более подробное объяснение этой части.
  • 0
    Да, я заметил, что у вас есть имя промежуточной папки здесь с запятой перед перечислением файла xml: -files pythonapp,hdfs:///pythonAppConf.xml Должен ли я делать то же самое?
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню