在Docker容器中使用Hadoop執行Python MapReduce作業

banq發表於2022-05-12

在 Apple Silicon Mac 上的 Docker 容器中使用 Hadoop 執行 Python MapReduce 作業。

開始時需要的步驟是。
  1. 安裝 Apple Silicon的docker桌面。
  2. 克隆這個Hadoop Docker容器的 repo。
  3. 為容器啟用docker-compose構建步驟。
  4. 在Docker檔案中安裝python3(並將python3設為預設)。
  5. 使用docker-compose構建容器並啟動你的Hadoop叢集。
  6. 建立mapper.py和reducer.py指令碼。
  7. 建立你的資料輸入檔案。
  8. 在namenodeservice中啟動一個終端。
  9. 使用hadoop dfs將你的資料輸入檔案複製到HDFS。
  10. 使用mapred執行你的工作,並在輸出資料夾中檢視結果。


1、安裝Docker Desktop
為Apple Silicon安裝Docker Desktop。考慮增加可用的記憶體量。

2、在Docker上克隆Hadoop
請看here的文章中的更多細節,但你可以直接去克隆 repo。

注意,這裡顯示$是為了提醒你,你是在docker容器之外執行這些命令。

$ git clone git@github.com:wxw-matt/docker-hadoop.git ~/docker-hadoop


3、啟用容器的構建步驟
確保docker-compose build會觸發各種元件的構建,例如在docker-compose.yml中的namenode。

注意,這裡顯示的是+,表示在現有檔案中增加了幾行。

namenode:
    image: wxwmatt/hadoop-namenode:2.1.1-hadoop3.3.1-java8
+   build:
+       context: namenode
+       dockerfile: Dockerfile
    container_name: namenode



針對以下重複此步驟:
  • namenode
  • datanode
  • resourcemanager
  • nodemanager1


4、將Python3安裝為預設值
在每個元件的docker檔案中,安裝python3。你可以在一開始就這樣做,因為它是Docker容器環境的高階依賴,與Hadoop沒有具體關係。

FROM wxwmatt/hadoop-base:2.1.1-hadoop3.3.1-java8
+RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends python3.6
+RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 1
HEALTHCHECK CMD curl -f http://localhost:9870/ || exit 1


針對以下重複此步驟:
  • namenode
  • datanode
  • resourcemanager
  • nodemanager1


5、構建容器並啟動你的Hadoop叢集
一旦你構建了其中一個容器,基本映象就應該被快取起來。你可能需要等待所有的容器都執行起來,以便它們可以看到彼此。

$ docker-compose up --build -d


6、編寫你的對映器和還原器指令碼
在 /jobs/jars 資料夾中,建立 mapper.py。

# -*-coding:utf-8 -*
import sys
import string
import collections
wordCount = collections.Counter()
for line in sys.stdin:
    # remove leading and trailing whitespace
    line = line.strip()
    # ignore case to avoid duplicates
    lower = line.lower()
    # replace punctuation with spaces, except for hyphens
    punc = string.punctuation
    punc_nohyphens = punc.replace('-', '')
    justText = lower.translate(
        str.maketrans(' ', ' ', punc_nohyphens))
    # allow hyphenated words, but watch out for
    # double-hyphens which might be a stand-in for em-dash
    emdashRemoved = justText.replace('--', ' ')
    # split the line into words
    words = emdashRemoved.split()
    # now we can remove leading and trailing curly quotes 
    # and hyphens, but leave apostrophes in the middle 
    # and allow hyphenated words.
    # note that we lose possessive ending in s, e.g Angus'
    punc_curly = '”’‘“-'
    for word in words:
        word = word.strip(punc_curly)
        if len(word) > 0:
            wordCount[word] += 1
# write the results, tab delimited
# which will be the input for reducer.py
for word, count in wordCount.most_common():
    print ('%s\t%s' % (word, count))


reducer.py程式碼:

# -*-coding:utf-8 -*
import sys
import collections
import statistics
wordCount = collections.Counter()
wordLengthCount = collections.Counter()
wordLengthList = []
for line in sys.stdin:
    # remove leading and trailing whitespace
    line = line.strip()
    # parse the input we got from mapper.py
    word, count = line.split('\t', 1)
    # ensure that it's a string followed by an int
    try:
        word = str(word)
        count = int(count)
    except ValueError:
        # count was not a number, so silently
        # ignore/discard this line
        continue
    length = len(word)
    wordCount[word] += count
    wordLengthCount[length] += count
    # expand the counted words to a full list
    # to enable median to be calculated later
    lengthRepeated = [length] * count
    wordLengthList.extend(lengthRepeated)
for commonWord, commonWordCount in wordCount.most_common(100):
    print ("{}\t{}".format(commonWord, commonWordCount))
print ("\n")
for commonLength, lengthCount in wordLengthCount.most_common():
    print ("{}\t{}".format(commonLength, commonLengthCount))
print ("\n")
print("Number of words: {}".format(
    len(wordLengthList)))
print("Median word length: {:.0f}".format(
    statistics.median(wordLengthList)))


這些檔案將在Docker容器內的/app/jars資料夾中可用。

7、建立你的資料輸入檔案
在./jobs/data/input資料夾中建立你的文字輸入檔案(在Docker容器之外)。

接著,我們從Docker容器內將檔案複製到HDFS中,檔案被對映到/app/data/input。

8、在名稱節點內啟動一個終端
你可以點選Docker桌面內的圖示,也可以從命令列執行它。
$ docker exec -it namenode bash

9、將你的資料輸入檔案複製到HDFS中

> hadoop fs -mkdir -p /test/
> hadoop fs -copyFromLocal -f /app/data/input /test/。

不要忘記清理任何過去的作業。

> hadoop fs -rm -r -f /test/output


10、執行你的工作並檢視結果
重要的一點是要避免使用Hadoop流媒體jar,而要使用mapred。

> cd /app/jars
> mapred streaming \
-files mapper.py,reducer.py \
-input /test/input \
-output /test/output \
-mapper “/usr/bin/python mapper.py” \
-reducer “/usr/bin/python reducer.py”


輸出需要為萬用字元加上引號:

> hadoop fs cat “/test/output/*”

  • Matt Wang擴充套件了 BigDataEurope在 Hadoop for Docker 上的工作。
  • 來自Grégory Lang的[url=https://stackoverflow.com/questions/52076577/error-java-lang-runtimeexception-pipemapred-waitoutputthreads-subprocess-fa/61624913#61624913]建議[/url]是對 mapred 的重大啟示。
  • 感謝Bert Freeba的鼓勵和鼓舞人心的建議!

相關文章