在現代軟體開發流程中,有效結合本地端開發和雲端資源至關重要。本文將探討如何在本地端建立開發環境,利用容器化技術、模擬選項和設定指令碼提升開發效率,同時有效控制雲端資源成本。我們將探討如何使用 Docker 等容器化技術確保開發環境與生產環境的一致性,並使用 LocalStack 等工具模擬雲端服務,減少對實際雲端資源的依賴。此外,我們也將探討如何設定自動化指令碼以清理閒置資源,並使用標籤和標記有效管理雲端資源。文章同時涵蓋了軟體開發的最佳實踐,例如模組化設計和單一職責原則,以確保程式碼易於維護和修改。最後,我們將探討如何在不同的編碼環境(如 IDE、Jupyter Notebook 和 Web UI)下有效管理程式碼,並說明如何應用這些策略來構建更具彈性和可維護性的資料管線。
在本地端開發與雲端資源的有效管理
在開發過程中,使用雲端資源進行測試和佈署是一種常見的做法。然而,這種做法可能會帶來一些問題,例如成本過高、除錯困難等。在本章中,我們將探討如何在本地端開發和測試,同時有效地管理雲端資源。
雲端資源的挑戰
使用雲端資源進行開發和測試可能會帶來一些挑戰。首先,建立和組態雲端資源可能需要大量的時間和精力。其次,雲端資源的成本可能會很高,尤其是當資源沒有被正確地清理時。此外,除錯雲端資源中的問題可能會很困難,因為日誌和錯誤資訊可能不容易被存取。
混合式開發方法
為瞭解決這些挑戰,我們可以採用混合式開發方法。這種方法涉及在本地端開發和測試,同時使用雲端資源進行必要的操作。例如,我們可以在本地端使用容器化技術來開發和測試,同時連線到雲端資源進行資料儲存或處理。
例項:使用本地端容器和雲端儲存
在一個例項中,我們需要開發一個資料處理管道,該管道需要將處理後的資料寫入雲端儲存。為了驗證資料是否正確地被寫入,我們可以在本地端使用容器化技術來開發和測試。同時,我們可以連線到雲端儲存來驗證資料是否正確地被寫入。
import pandas as pd
from pyspark.sql import SparkSession
# 建立SparkSession
spark = SparkSession.builder.appName("Data Processing").getOrCreate()
# 讀取資料
data = spark.read.csv("data.csv", header=True, inferSchema=True)
# 處理資料
processed_data = data.filter(data["age"] > 18)
# 將資料寫入本地端檔案
processed_data.write.csv("processed_data.csv", header=True)
# 將資料寫入雲端儲存
processed_data.write.parquet("gs://bucket/processed_data", mode="overwrite")
#### 內容解密:
1. 建立SparkSession是為了初始化Spark環境。
2. 讀取資料是從本地端或雲端儲存讀取原始資料。
3. 處理資料是根據特定的條件過濾資料。
4. 將資料寫入本地端檔案是用於除錯和驗證。
5. 將資料寫入雲端儲存是將處理後的資料儲存在雲端。
設定指令碼和模擬選項
在開發過程中,使用設定指令碼和模擬選項可以提高效率。設定指令碼可以用於建立和組態開發環境,而模擬選項可以用於模擬外部服務的回應。
例項:使用模擬API回應
在一個例項中,我們需要開發一個資料處理管道,該管道需要呼叫內部API來取得客戶資訊。為了提高效率,我們可以新增模擬API回應的選項。
import requests
# 模擬API回應
def mock_api_response():
return {"customer_id": 1, "tier": "premium"}
# 取得客戶資訊
def get_customer_info(customer_id):
# 使用模擬API回應
if customer_id == 1:
return mock_api_response()
else:
# 呼叫內部API
response = requests.get(f"https://api.example.com/customers/{customer_id}")
return response.json()
#### 內容解密:
1. 模擬API回應是用於模擬外部服務的回應。
2. 取得客戶資訊是根據客戶ID取得客戶資訊。
3. 使用模擬API回應可以提高開發效率。
資源清理
在使用雲端資源時,資源清理是非常重要的。未使用的資源可能會導致成本過高,因此我們需要定期清理未使用的資源。
例項:使用自動終止和自動縮放
我們可以使用自動終止和自動縮放來清理未使用的資源。例如,我們可以設定EMR叢集在閒置一段時間後自動終止。
建立高效能的開發環境
在開發資料管線(Data Pipelines)時,開發一個高效能且可重複使用的開發環境至關重要。這不僅能提升開發效率,還能有效控制雲端資源的成本。以下是一些實務經驗和策略,幫助您建立一個完善的開發環境。
使用容器化技術
容器化技術(如 Docker)可以幫助您將資料管線拆解成獨立的服務,並確保開發環境與生產環境的一致性。透過 Docker Compose,您可以定義和管理多個服務,並在本地端執行測試。
程式碼範例:Docker Compose 設定檔
version: '3'
services:
db:
image: postgres
environment:
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
內容解密:
version: 指定 Docker Compose 的版本。services: 定義服務列表,這裡定義了一個名為db的 PostgreSQL 服務。image: 指定使用的 Docker 映像檔。environment: 設定環境變數,用於組態 PostgreSQL 資料函式庫的使用者名稱和密碼。volumes: 將資料儲存在持久化卷中,以確保資料不會因容器重啟而遺失。
最佳化雲端資源使用
在開發過程中,適當地使用雲端資源可以有效控制成本。以下是一些最佳實踐:
- 使用暫時性環境(Ephemeral Environments):針對測試和開發,使用暫時性環境可以減少雲端資源的浪費。
- 自動清理閒置資源:設定自動化指令碼,定期清理閒置或過期的雲端資源,如 EMR 叢集。
- 標籤和標記資源:使用標籤和標記來識別開發相關的資源,以便進行自動清理。
本地端開發與測試
在本地端開發和測試資料管線,可以減少對雲端資源的依賴,並加快開發速度。以下是一些工具和策略:
- LocalStack:用於在本地端模擬 AWS 服務,減少對實際雲端資源的依賴。
- 使用容器化技術:在本地端執行容器化的服務,以確保與生產環境的一致性。
維護和最佳化
- 定期清理本地端容器和映像檔:確保本地端環境的乾淨和一致性。
- 最佳化雲端資源組態:根據實際需求調整雲端資源的組態,以降低成本。
軟體開發策略:開發易於變更的資料管執行緒式碼
在《The Pragmatic Programmer》(20週年版)這本文中,David Thomas和Andrew Hunt提出了一個基礎概念:程式碼應該是易於變更(Easy To Change, ETC)的。在處理資料管線時,這個觀念顯得尤為重要,因為資料管線的開發需要支援多個領域的變更,例如資料大小、格式、形狀、資料擷取和儲存,以及不斷演變的資料轉換和驗證需求,更不用說雲端服務、供應商和資料處理引擎的變化。
面對多變的資料管線設計
當資料管線的設計不斷變化時,即使是最精心設計的程式碼函式庫也可能變得混亂,難以修改、擴充套件和測試功能。這反過來又會對效能、可靠性和成本產生負面影響,因為需要花費更多時間和資源來除錯和演進管線。
本章將幫助您設計出能夠抵禦資料管線設計變化的程式碼函式庫,重點在於開發出易於變更(ETC)的程式碼。首先,將討論在資料管線中常見的程式設計環境,並根據經驗分享如何在每種情況下有效地管理程式碼。然後,將介紹建立模組化程式碼函式庫的技術,應用軟體工程的最佳實踐於常見的資料管線工作場景。
程式碼範例結構說明
# 示例程式碼結構
def main():
# 主要邏輯
pass
if __name__ == "__main__":
main()
內容解密:
此範例展示了一個基本的 Python 程式結構。main 函式是程式的入口點,包含了主要的邏輯。if __name__ == "__main__": 這一行確保了 main 函式只在直接執行該指令碼時被呼叫,而不是在被匯入作為模組時。
管理不同的程式設計環境
資料管執行緒式碼可以以多種方式開發。軟體開發人員可能更習慣於在整合開發環境(IDE)中思考程式碼開發,而資料分析師、資料科學家和機器學習(ML)科學家可能更熟悉筆記本介面,如 Jupyter。此外,一些雲端服務(如 AWS Lambda 和 AWS Glue)提供了網頁 UI,讓您能夠在瀏覽器中編寫程式碼和管理與其他服務的連線。
跨不同環境管理程式碼的挑戰
在這些不同的環境中管理程式碼可能具有挑戰性。從可靠性的角度來看,程式碼應該遵循軟體開發的最佳實踐進行原始碼控制和測試。然而,對於筆記本和網頁 UI 來說,這可能很棘手或根本不可能,這可能會為錯誤和 bug 開啟大門。
範例:多模式管線
在某個資料管線專案中,同時使用了筆記本、IDE 和 AWS Lambda 網頁 UI 三種環境。管線架構如圖 6-1 所示。
圖 6-1:具有不同程式設計環境的管線架構
此圖示展示了不同程式設計環境如何協同工作於一個完整的資料管線中。
內容解密:
圖 6-1 中的架構展示了一個複雜的資料管線,其中不同的元件使用了不同的程式設計環境。分析師團隊使用 Databricks 筆記本開發了轉換邏輯,該邏輯對資料倉函式庫執行並將結果儲存到 S3。S3 中的新物件建立觸發了一個 Lambda 函式,該函式啟動了一個 EMR 叢集,以執行第二步資料轉換,然後將資料載入到 DynamoDB 中。
筆記本的使用
在這個範例中,筆記本的使用是合理的,因為它允許分析師團隊使用他們熟悉的語言(SQL)表達轉換邏輯,並在測試查詢時獲得即時回饋。這也有助於快速測試分析師所做的更新,而無需進行傳統的構建和佈署流程。
筆記本的優缺點
筆記本非常適合原型設計和在設計過程早期分享想法。將程式碼、視覺化和筆記結合起來的能力非常適合進行探索性資料分析和確定資料轉換邏輯,同時也為協作提供了上下文。
然而,筆記本的一個缺點是缺乏對程式碼進行測試的有效方法。如果您正在使用筆記本,最好將關鍵程式碼分解到可以進行測試的套件中。對於筆記本來說,原始碼控制也在不斷演進,但仍然存在一些缺陷。這是另一個傾向於將程式碼封裝以匯入筆記本的原因。
管理不同的編碼環境與策略
在軟體開發過程中,如何有效地管理不同的編碼環境是一個重要的課題。無論是開發、測試還是生產環境,都需要一套完善的策略來確保程式碼的品質和可維護性。本章將探討幾種不同的編碼環境管理策略,包括使用 widgets、Web UI、基礎設施即程式碼(Infrastructure as Code, IaC)等方法。
使用 Widgets 管理輸入引數
在 Jupyter 或 Databricks notebooks 中,可以使用 widgets 來提供輸入引數,而無需修改程式碼。這使得相同的 notebook 可以用於不同的輸入引數,從而提高程式碼的靈活性。例如,在一個執行查詢效能分析的 notebook 中,可以使用 widgets 讓使用者指定時間範圍和查詢名稱,從而使 notebook 可以用於不同的使用案例。
import ipywidgets as widgets
# 建立一個文字輸入 widget
text_widget = widgets.Text(
value='',
placeholder='輸入名稱',
description='名稱:',
disabled=False
)
# 顯示 widget
display(text_widget)
# 使用 widget 的值
name = text_widget.value
print(f"你輸入的名稱是:{name}")
內容解密:
import ipywidgets as widgets:匯入ipywidgets函式庫,這是一個用於建立互動式 widgets 的工具。text_widget = widgets.Text():建立一個文字輸入 widget,用於接收使用者輸入。display(text_widget):顯示 widget,讓使用者可以與之互動。name = text_widget.value:取得 widget 中的輸入值,並將其賦給變數name。
Web UI 的優缺點
像 AWS Lambda 這樣的服務提供了 Web UI,讓開發者可以直接在瀏覽器中編寫程式碼。這種方式對於快速開發和測試非常方便,但也存在一些缺點。例如,直接在 Web UI 中修改程式碼可能會導致難以追蹤的錯誤,而且如果沒有適當的版本控制,錯誤的修改可能會導致嚴重的問題。
基礎設施即程式碼(IaC)
IaC 是一種透過組態檔案來管理和佈署雲端基礎設施的方法。這種方法可以確保基礎設施的佈署是一致的、可重複的,並且可以節省時間。使用 IaC 工具,如 AWS Serverless Application Model (SAM),可以輕鬆地佈署多個環境,並且可以快速地回復到之前的版本。
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Resources:
MyLambdaFunction:
Type: 'AWS::Serverless::Function'
Properties:
FunctionName: !Sub 'my-lambda-${AWS::Region}'
Runtime: python3.8
Handler: index.handler
CodeUri: .
內容解密:
AWSTemplateFormatVersion和Transform:指定範本的格式版本和轉換器,用於定義 SAM 範本。Resources:定義資源的部分,在這裡定義了一個 Lambda 函式。MyLambdaFunction:定義了一個名為MyLambdaFunction的 Lambda 函式,指定了執行時、處理程式和程式碼位置。
程式碼開發策略
在任何環境中,使用良好的程式碼開發策略都是非常重要的。本章接下來的部分將探討一些程式碼開發的最佳實踐,包括如何避免程式碼變得難以修改。
程式碼變得難以修改的例子
以 HoD 社交媒體平台為例,最初使用者可以直接向 ML 引擎提交問題。隨著時間的推移,團隊建立了一些固定問題並增加了邏輯來執行這些問題。然而,這種做法使得程式碼變得越來越複雜和難以修改。
軟體開發策略:模組化設計與單一職責原則
在處理資料處理流程時,系統的演變往往伴隨著複雜度的增加。以Head of Data(HoD)團隊開發的「股票問題」(stock questions)功能為例,最初該功能與主要的機器學習(ML)引擎緊密耦合,旨在快速推出實驗性功能。然而,隨著功能的流行和使用者數量的增加,系統面臨效能和可靠性問題。
從緊密耦合到模組化設計
圖6-3展示了最初的ML引擎與執行層緊密耦合的設計,這種設計雖然能夠快速實作功能,但隨著系統的發展,維護和除錯變得越來越困難。圖6-4顯示了系統演變後的架構,執行引擎承擔了更多的責任,並增加了多個資料來源和儲存目的地,導致系統變得更加複雜。
這種演變過程揭示了緊密耦合設計的缺點:
- 增加故障點:更多的依賴關係意味著更多的潛在故障點。
- 效能影響:隨著資料量的指數增長,系統效能成為一個重大問題。
- 測試困難:緊密耦合使得測試和除錯變得耗時且困難。
模組化設計與單一職責原則
為了克服這些挑戰,模組化設計和單一職責原則(Single Responsibility Principle, SRP)成為瞭解決方案。模組化設計將系統分解為可重用的構建塊,就像樂高積木一樣,每個構建塊都有明確的介面和職責。
單一職責原則實踐
考慮create_aggregate_data函式,它負責聚合資料並將結果儲存在S3儲存桶中。這個函式違反了單一職責原則,因為它同時處理資料聚合和儲存兩個不同的任務。
def create_aggregate_data(data):
bird_agg = aggregate_by_species(data)
key = f"aggregate_data_{datetime.utcnow().isoformat()}.json"
s3.put_object(Bucket="bucket_name", Key=key, Body=bytes(json.dumps(bird_agg)))
透過將資料聚合和儲存功能重構為獨立的函式,可以提高程式碼的可維護性和可測試性:
def write_to_s3(data, key):
s3 = boto3.client('s3', region_name='us-east-1')
s3.put_object(Bucket="bucket_name", Key=key, Body=data)
def create_species_agg(data):
bird_agg = aggregate_by_species(data)
key = f"aggregate_data_{datetime.utcnow().isoformat()}.json"
write_to_s3(bytes(json.dumps(bird_agg)), key)
內容解密:
write_to_s3函式:封裝了將資料寫入S3儲存桶的邏輯,使得create_species_agg函式不再依賴於S3的實作細節。create_species_agg函式:負責資料聚合,並呼叫write_to_s3將結果儲存在S3中,實作了單一職責原則。