[譯] Python 與大資料:Airflow、 Jupyter Notebook 與 Hadoop 3、Spark、Presto

cf020031308發表於2018-07-27

最近幾年裡,Python 已成為資料科學、機器學習和深度學習領域的一門流行的程式語言。只需再配上查詢語言 SQL 即可完成大多數工作。SQL 很棒,用英語即可發出指令,且只需指示想要什麼,而無需關心具體如何查詢。這使得底層的查詢引擎可以不改變 SQL 查詢就能對其進行優化。Python 也很棒,它有大量高質量的庫,本身也易於使用。

作業編排是執行日常任務並使其自動化的行為。在過去,這通常是通過 CRON 作業完成的。而在最近幾年,越來越多的企業開始使用 Apache AirflowSpotify 的 Luigi 等建立更強大的系統。這些工具可以監控作業、記錄結果並在發生故障時重新執行作業。如果您有興趣,我曾寫過一篇部落格文章,其中包括 Airflow 的背景故事,題為《使用 Airflow 構建資料管道》

作為資料探索和視覺化工具的 Notebooks 在過去幾年中也在資料領域變得非常流行。像 Jupyter NotebookApache Zeppelin 這樣的工具旨在滿足這一需求。Notebooks 不僅向您顯示分析結果,還顯示產生這些結果的程式碼和查詢。這有利於發現疏忽並可幫助分析師重現彼此的工作。

Airflow 和 Jupyter Notebook 可以很好地協同工作,您可以使用 Airflow 自動將新資料輸入資料庫,然後資料科學家可以使用 Jupyter Notebook 進行分析。

在這篇博文中,我將安裝一個單節點的 Hadoop,讓 Jupyter Notebook 執行並展示如何建立一個 Airflow 作業,它可以獲取天氣資料來源,將其儲存在 HDFS 上,再轉換為 ORC 格式,最後匯出到 Microsoft Excel 格式的電子表格中。

我正在使用的機器有一個主頻為 3.40 GHz 的 Intel Core i5-4670K CPU、12 GB 的 RAM 和 200 GB 的 SSD。我將使用全新安裝的 Ubuntu 16.04.2 LTS,並根據我的部落格文章《Hadoop 3:單節點安裝指南》 中的說明構建安裝單節點 Hadoop。

安裝依賴項

接下來將安裝 Ubuntu 上的依賴項。 git 包將用於從 GitHub 獲取天氣資料集,其餘三個包是 Python 本身、Python 包安裝程式和 Python 環境隔離工具包。

$ sudo apt install \
    git \
    python \
    python-pip \
    virtualenv
複製程式碼

Airflow 將依靠 RabbitMQ 的幫助來跟蹤其作業。下面安裝 Erlang,這是編寫 RabbitMQ 的語言。

$ echo "deb http://binaries.erlang-solutions.com/debian xenial contrib" | \
    sudo tee /etc/apt/sources.list.d/erlang.list
$ wget -O - http://binaries.erlang-solutions.com/debian/erlang_solutions.asc | \
    sudo apt-key add -
$ sudo apt update
$ sudo apt install esl-erlang
複製程式碼

下面安裝 RabbitMQ。

$ echo "deb https://dl.bintray.com/rabbitmq/debian xenial main" | \
    sudo tee /etc/apt/sources.list.d/bintray.rabbitmq.list
$ wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | \
    sudo apt-key add -
$ sudo apt update
$ sudo apt install rabbitmq-server
複製程式碼

下面將安裝此博文中使用的 Python 上的依賴項和應用程式。

$ virtualenv .notebooks
$ source .notebooks/bin/activate
$ pip install \
    apache-airflow \
    celery \
    cryptography \
    jupyter \
    jupyterthemes \
    pyhive \
    requests \
    xlsxwriter
複製程式碼

配置 Jupyter Notebook

我將為 Jupyter 建立一個資料夾來儲存其配置,然後為伺服器設定密碼。如果不設定密碼,您就會獲得一個冗長的 URL,其中包含用於訪問 Jupyter 網頁介面的金鑰。每次啟動 Jupyter Notebook 時,金鑰都會更新。

$ mkdir -p ~/.jupyter/
$ jupyter notebook password
複製程式碼

Jupyter Notebook 支援使用者介面主題。以下命令將主題設定為 Chesterish

$ jt -t chesterish
複製程式碼

下面命令列出當前安裝的主題。內建的主題在 GitHub上都有螢幕截圖

$ jt -l
複製程式碼

要返回預設主題,請執行以下命令。

$ jt -r
複製程式碼

通過 Jupyter Notebook 查詢 Spark

首先確保您執行著 Hive 的 Metastore、Spark 的 Master & Slaves 服務,以及 Presto 的服務端。以下是啟動這些服務的命令。

$ hive --service metastore &
$ sudo /opt/presto/bin/launcher start
$ sudo /opt/spark/sbin/start-master.sh
$ sudo /opt/spark/sbin/start-slaves.sh
複製程式碼

下面將啟動 Jupyter Notebook,以便您可以與 PySpark 進行互動,PySpark 是 Spark 的基於 Python 的程式設計介面。

$ PYSPARK_DRIVER_PYTHON=ipython \
    PYSPARK_DRIVER_PYTHON_OPTS="notebook
        --no-browser
        --ip=0.0.0.0
        --NotebookApp.iopub_data_rate_limit=100000000" \
    pyspark \
    --master spark://ubuntu:7077
複製程式碼

請注意,上面的 master 的 URL 以 ubuntu 為主機名。此主機名是 Spark Master 服務端繫結的主機名。如果無法連線到 Spark,請檢查 Spark Master 服務端的日誌,查詢它已選擇繫結的主機名,因為它不接受定址其他主機名的連線。這可能會令人困惑,因為您通常會期望像 localhost 這樣的主機名無論如何都能正常工作。

執行 Jupyter Notebook 服務後,用下面命令開啟網頁介面。

$ open http://localhost:8888/
複製程式碼

系統將提示您輸入為 Jupyter Notebook 設定的密碼。在右上角輸入後,您可以從下拉選單中建立新的筆記本。我們感興趣的兩種筆記本型別是 Python 和終端。終端筆記本使用您啟動 Jupyter Notebook 的 UNIX 帳戶為您提供 shell 訪問許可權。而我將使用的是 Python 筆記本。

啟動 Python 筆記本後,將以下程式碼貼上到單元格中,它將通過 Spark 查詢資料。調整查詢以使用您在安裝中建立的資料集。

cab_types = sqlContext.sql("""
 SELECT cab_type, COUNT(*)
 FROM trips_orc
 GROUP BY cab_type
""")

cab_types.take(2)
複製程式碼

這就是上面查詢的輸出結果。只返回了一條記錄,包括兩個欄位。

[Row(cab_type=u'yellow', count(1)=20000000)]
複製程式碼

通過 Jupyter Notebook 查詢 Presto

在前面用來查詢 Spark 的筆記本中,也可以查詢 Presto。某些 Presto 查詢的效能可能超過 Spark,趁手的是這兩者可以在同一個筆記本中進行切換。在下面的示例中,我使用 Dropbox 的 PyHive 庫來查詢 Presto。

from pyhive import presto

cursor = presto.connect('0.0.0.0').cursor()
cursor.execute('SELECT * FROM trips_orc LIMIT 10')
cursor.fetchall()
複製程式碼

這是上述查詢的部分輸出。

[(451221840,
  u'CMT',
  u'2011-08-23 21:03:34.000',
  u'2011-08-23 21:21:49.000',
  u'N',
  1,
  -74.004655,
  40.742162,
  -73.973489,
  40.792922,
...
複製程式碼

如果您想在 Jupyter Notebook 中生成資料圖表,可以看看《在 Jupyter Notebook 中使用 SQLite 視覺化資料》這篇博文,因為它有幾個使用 SQL 的繪圖示例,可以與 Spark 和 Presto 一起使用。

啟動 Airflow

下面將建立一個 ~/airflow 資料夾,設定一個用於儲存在網頁介面上設定的 Airflow 的狀態和配置集的 SQLite 3 資料庫,升級配置模式併為 Airflow 將要執行的 Python 作業程式碼建立一個資料夾。

$ cd ~
$ airflow initdb
$ airflow upgradedb
$ mkdir -p ~/airflow/dags
複製程式碼

預設情況下,Presto、Spark 和 Airflow 的網頁介面都使用 TCP 8080 埠。如果您先啟動了 Spark,Presto 就將無法啟動。但如果您是在 Presto 之後啟動 Spark,那麼 Presto 將在 8080 上啟動,而 Spark Master 服務端則會使用 8081,如果仍被佔用,會繼續嘗試更高階口,直到它找到一個空閒的埠。之後, Spark 將為 Spark Worker 的網頁介面選擇更高的埠號。這種重疊通常不是問題,因為在生產設定中這些服務通常存在於不同的機器上。

因為此安裝中使用了 8080 - 8082 的 TCP 埠,我將在埠 8083 上啟動 Airflow 的網頁介面。

$ airflow webserver --port=8083 &
複製程式碼

我經常使用以下命令之一來檢視正在使用的網路埠。

$ sudo lsof -OnP | grep LISTEN
$ netstat -tuplen
$ ss -lntu
複製程式碼

Airflow 的 Celery 代理和作業結果的儲存都預設使用 MySQL。這裡改為使用 RabbitMQ。

$ vi ~/airflow/airflow.cfg
複製程式碼

找到並編輯以下設定。

broker_url = amqp://airflow:airflow@localhost:5672/airflow

celery_result_backend = amqp://airflow:airflow@localhost:5672/airflow
複製程式碼

上面使用了 airflow 作為使用者名稱和密碼連線到 RabbitMQ。賬號密碼可以隨意自定。

下面將為 RabbitMQ 配置上述賬號密碼,以便它能訪問 Airflow 虛擬主機。

$ sudo rabbitmqctl add_vhost airflow
$ sudo rabbitmqctl add_user airflow airflow
$ sudo rabbitmqctl set_user_tags airflow administrator
$ sudo rabbitmqctl set_permissions -p airflow airflow ".*" ".*" ".*"
複製程式碼

將 Airflow 連線到 Presto

下面將開啟 Airflow 網頁介面。

$ open http://localhost:8083/
複製程式碼

開啟 Airflow 網頁介面後,單擊頂部的 “Admin” 導航選單,然後選擇 “Connections”。您將看到一長串預設資料庫連線。單擊以編輯 Presto 連線。 Airflow 連線到 Presto 需要進行以下更改。

  • 將 schema 從 hive 改為 default。
  • 將埠從 3400 改為 8080。

儲存這些更改,然後單擊頂部的 “Data Profiling” 導航選單,選擇 “Ad Hoc Query”。從查詢框上方的下拉選單中選擇 “presto_default”,您就應該可以通過 Presto 執行 SQL 程式碼了。下面是針對我在安裝中匯入的資料集執行的示例查詢。

SELECT count(*)
FROM trips_orc;
複製程式碼

下載天氣資料集

可以將 Airflow DAG 視為定時執行的作業。在下面的示例中,我將在 GitHub 上獲取 FiveThirtyEight 資料倉儲提供的天氣資料,將其匯入 HDFS,將其從 CSV 轉換為 ORC 並將其從 Presto 匯出為 Microsoft Excel 格式。

以下內容將 FiveThirtyEight 的資料儲存克隆到名為 data 的本地資料夾中。

$ git clone \
    https://github.com/fivethirtyeight/data.git \
    ~/data
複製程式碼

然後我將啟動 Hive 並建立兩個表。一個存資料集的 CSV 格式,另一個存資料集的 Presto 和 Spark 友好的 ORC 格式。

$ hive
複製程式碼
CREATE EXTERNAL TABLE weather_csv (
    date_                 DATE,
    actual_mean_temp      SMALLINT,
    actual_min_temp       SMALLINT,
    actual_max_temp       SMALLINT,
    average_min_temp      SMALLINT,
    average_max_temp      SMALLINT,
    record_min_temp       SMALLINT,
    record_max_temp       SMALLINT,
    record_min_temp_year  INT,
    record_max_temp_year  INT,
    actual_precipitation  DECIMAL(18,14),
    average_precipitation DECIMAL(18,14),
    record_precipitation  DECIMAL(18,14)
) ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
  LOCATION '/weather_csv/';

CREATE EXTERNAL TABLE weather_orc (
    date_                 DATE,
    actual_mean_temp      SMALLINT,
    actual_min_temp       SMALLINT,
    actual_max_temp       SMALLINT,
    average_min_temp      SMALLINT,
    average_max_temp      SMALLINT,
    record_min_temp       SMALLINT,
    record_max_temp       SMALLINT,
    record_min_temp_year  INT,
    record_max_temp_year  INT,
    actual_precipitation  DOUBLE,
    average_precipitation DOUBLE,
    record_precipitation  DOUBLE
) STORED AS orc
  LOCATION '/weather_orc/';
複製程式碼

建立 Airflow DAG

下面的 Python 程式碼是 Airflow 作業(也稱為DAG)。每隔 30 分鐘,它將執行以下操作。

  • 清除 HDFS上 /weather_csv/ 資料夾中的任何現有資料。
  • 將 ~/data 資料夾中的 CSV 檔案複製到 HDFS 上的 /weather_csv/ 資料夾中。
  • 使用 Hive 將 HDFS 上的 CSV 資料轉換為 ORC 格式。
  • 使用 Presto 將 ORC 格式的資料匯出為 Microsoft Excel 2013 格式。

在下面的 Python 程式碼中有一個指向 CSV 的位置,完整路徑為 /home/mark/data/us-weather-history/*.csv,請將其中的 “mark” 更換為您自己的 UNIX 使用者名稱。

$ vi ~/airflow/dags/weather.py
複製程式碼
from datetime import timedelta

import airflow
from   airflow.hooks.presto_hook         import PrestoHook
from   airflow.operators.bash_operator   import BashOperator
from   airflow.operators.python_operator import PythonOperator
import numpy  as np
import pandas as pd


default_args = {
    'owner':            'airflow',
    'depends_on_past':  False,
    'start_date':       airflow.utils.dates.days_ago(0),
    'email':            ['airflow@example.com'],
    'email_on_failure': True,
    'email_on_retry':   False,
    'retries':          3,
    'retry_delay':      timedelta(minutes=15),
}

dag = airflow.DAG('weather',
                  default_args=default_args,
                  description='將天氣資料複製到 HDFS 並匯出為 Excel',
                  schedule_interval=timedelta(minutes=30))

cmd = "hdfs dfs -rm /weather_csv/*.csv || true"
remove_csvs_task = BashOperator(task_id='remove_csvs',
                                bash_command=cmd,
                                dag=dag)

cmd = """hdfs dfs -copyFromLocal \
            /home/mark/data/us-weather-history/*.csv \
            /weather_csv/"""
csv_to_hdfs_task = BashOperator(task_id='csv_to_hdfs',
                                bash_command=cmd,
                                dag=dag)

cmd = """echo \"INSERT INTO weather_orc
                SELECT * FROM weather_csv;\" | \
            hive"""
csv_to_orc_task = BashOperator(task_id='csv_to_orc',
                               bash_command=cmd,
                               dag=dag)


def presto_to_excel(**context):
    column_names = [
        "date",
        "actual_mean_temp",
        "actual_min_temp",
        "actual_max_temp",
        "average_min_temp",
        "average_max_temp",
        "record_min_temp",
        "record_max_temp",
        "record_min_temp_year",
        "record_max_temp_year",
        "actual_precipitation",
        "average_precipitation",
        "record_precipitation"
    ]

    sql = """SELECT *
             FROM weather_orc
             LIMIT 20"""

    ph = PrestoHook(catalog='hive',
                    schema='default',
                    port=8080)
    data = ph.get_records(sql)

    df = pd.DataFrame(np.array(data).reshape(20, 13),
                      columns=column_names)

    writer = pd.ExcelWriter('weather.xlsx',
                            engine='xlsxwriter')
    df.to_excel(writer, sheet_name='Sheet1')
    writer.save()

    return True

presto_to_excel_task = PythonOperator(task_id='presto_to_excel',
                                      provide_context=True,
                                      python_callable=presto_to_excel,
                                      dag=dag)

remove_csvs_task >> csv_to_hdfs_task >> csv_to_orc_task >> presto_to_excel_task

if __name__ == "__main__":
    dag.cli()
複製程式碼

使用該程式碼開啟 Airflow 的網頁介面並將主頁底部的 “weather” DAG 旁邊的開關切換為 “on”。

排程程式將建立一個作業列表交給 workers 去執行。以下內容將啟動 Airflow 的排程程式服務和一個將完成所有預定作業的 worker。

$ airflow scheduler &
$ airflow worker &
複製程式碼

感謝您抽出寶貴時間閱讀這篇文章。我為北美和歐洲的客戶提供諮詢、架構和實際開發服務。如果您有意探討我的產品將如何幫助您的業務,請通過 LinkedIn 與我聯絡。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章