В моих заданиях 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?
Вот пример конца, который адаптирует методы, упомянутые в этом предыдущем вопросе, ближе к вашему вопросу.
Python читает файл как поток из HDFS
Это небольшое приложение Python Hadoop Streaming, которое считывает пары ключ-значение, проверяет ключ на файле конфигурации XML, хранящемся в HDFS, и затем испускает значение только в том случае, если ключ соответствует конфигурации. Логика соответствия отключена в отдельный модуль Process.py, который считывает файл конфигурации XML из HDFS, используя внешний вызов hdfs dfs -cat
.
Сначала мы создаем каталог с именем pythonapp, содержащий исходные файлы Python для нашей реализации. Мы увидим позже, когда мы представим потоковое задание, которое мы передадим этому каталогу в аргументе -files
.
Почему мы помещаем файлы в промежуточный каталог вместо простого перечисления каждого файла отдельно в аргументе -files
? Это потому, что, когда YARN локализует файлы для выполнения в контейнерах, он вводит слой символической ссылки. Python не может правильно загрузить модуль через символическую ссылку. Решение состоит в том, чтобы упаковать оба файла в один и тот же каталог. Затем, когда YARN локализует файлы, символическая ссылка выполняется на уровне каталога вместо отдельных файлов. Поскольку основной script и модуль физически находятся в одном каталоге, Python сможет правильно загрузить модуль. Этот вопрос более подробно объясняет проблему:
Как импортировать настраиваемый модуль в задание MapReduce?
import subprocess
import sys
from Process import match
for line in sys.stdin:
key, value = line.split()
if match(key):
print value
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 файл, в котором мы можем настроить конкретный ключ для соответствия.
foo 1
bar 2
baz 3
<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.
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.
Спасибо Крису Найроту за ответы, которые он изложил выше. С этой записью я хочу точно рассказать о том, что именно это решило мою проблему.
Второй ответ, который он дал, очень близок к тому, что я изначально пытался сделать. То, что я узнал, состоит в том, что для меня это привело несколько небольших изменений. Например, в 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://или часть хоста: как видно из фактической команды, указанной выше.
-files
. Я только отредактировал свой ответ, чтобы объяснить это, и ссылку на предыдущий ответ, который предоставляет более подробное объяснение этой части.
-files pythonapp,hdfs:///pythonAppConf.xml
Должен ли я делать то же самое?
Mapper <--> Process.py
Mapper по-прежнему направляет вывод в Reducer. Я хочу, чтобы скрипт Process.py мог читать небольшой файл XML, который находится в HDFS, чтобы построить словарь Python в памяти. Это работает локально на моем ПК, потому что я могу просто указать путь к файлу xml и использовать lxml для его анализа.-files
чтобы сообщить YARN о необходимости извлечения файла в качестве локального ресурса перед запуском скрипта Python, что, возможно, проще и может обеспечить более высокую производительность для очень больших заданий.