如何在 Flask 應用程式中使用 App Engine 工作佇列 (提取工作) (模組 18)

1. 總覽

無伺服器遷移工作站系列程式碼研究室 (自學式實作教學課程) 和相關影片,旨在協助 Google Cloud 無伺服器開發人員完成一或多項遷移作業 (主要是從舊版服務遷移),進而翻新應用程式。這樣做可提高應用程式的可攜性,並提供更多選項和彈性,讓您整合及存取更多 Cloud 產品,並更輕鬆地升級至新版語言。雖然一開始的重點是早期 Cloud 使用者,主要是 App Engine (標準環境) 開發人員,但本系列涵蓋範圍廣泛,也包括其他無伺服器平台,例如 Cloud FunctionsCloud Run,或適用於其他平台。

本程式碼研究室將說明如何納入及使用 App Engine 工作佇列提取工作,將其加入第 1 模組程式碼研究室範例應用程式。我們會在第 18 個單元的教學課程中新增 提取工作的使用方式,然後在第 19 個單元中將該使用方式遷移至 Cloud Pub/Sub。如果使用工作佇列執行推送工作,請改用 Cloud Tasks,並參閱第 7 至 9 個單元。

在接下來的研究室中

  • 使用 App Engine 工作佇列 API/套裝組合服務
  • 在基本的 Python 2 Flask App Engine NDB 應用程式中新增提取佇列的使用情形

軟硬體需求

問卷調查

您會如何使用本教學課程?

僅閱讀 閱讀並完成練習

你對 Python 的使用體驗如何?

新手 中級 熟練

您對使用 Google Cloud 服務的體驗滿意嗎?

新手 中級 熟練

2. 背景

如要從 App Engine Task Queue 提取工作遷移,請將其用量新增至現有的 Flask 和 App Engine NDB 應用程式,該應用程式是從第 1 堂程式碼研究室建立而來。範例應用程式會顯示使用者最近的造訪記錄。這樣做當然沒問題,但如果能追蹤訪客,看看誰最常造訪,就有趣了。

雖然我們可以對這些訪客人數使用推送工作,但我們希望將責任劃分給負責註冊造訪次數並立即回應使用者的範例應用程式,以及負責在正常要求-回應工作流程外統計訪客人數的指定「工作人員」。

如要實作這項設計,我們會在主要應用程式中新增「提取佇列」的使用方式,並支援工作人員功能。Worker 可以做為獨立程序執行 (例如後端執行個體或在 VM 上執行的程式碼,且 VM 一律處於運作狀態)、cron 工作,或使用 curlwget 的基本指令列 HTTP 要求。完成這項整合後,您可以在下一個 (第 19 堂課) 程式碼研究室中,將應用程式遷移至 Cloud Pub/Sub

本教學課程包含下列步驟:

  1. 設定/準備工作
  2. 更新設定
  3. 修改應用程式程式碼

3. 設定/準備工作

本節將說明如何:

  1. 設定 Cloud 專案
  2. 取得基準範例應用程式
  3. (重新) 部署及驗證基準應用程式

這些步驟可確保您一開始使用的是可運作的程式碼。

1. 設定專案

如果您已完成第 1 模組程式碼研究室,請重複使用該專案 (和程式碼)。或者,您也可以建立全新專案,或重複使用其他現有專案。確認專案有可用的帳單帳戶,且已啟用 App Engine 應用程式。找出專案 ID,因為在本程式碼研究室中,您會多次需要使用這個 ID,並在遇到 PROJECT_ID 變數時使用。

2. 取得基準範例應用程式

本程式碼研究室的先決條件之一,是必須有可運作的第 1 模組 App Engine 應用程式。建議您完成第 1 模組程式碼研究室,或從存放區複製第 1 模組應用程式。無論您使用自己的程式碼或我們的程式碼,模組 1 的程式碼都是「起點」。本程式碼研究室會逐步說明每個步驟,最後的程式碼會與「FINISH」資料夾中的內容類似。

無論使用哪個第 1 模組應用程式,資料夾都應如下列輸出內容所示,可能也會有 lib 資料夾:

$ ls
README.md               appengine_config.py     requirements.txt
app.yaml                main.py                 templates

3. (重新) 部署基準應用程式

請按照下列步驟部署第 1 課的應用程式:

  1. 刪除 lib 資料夾 (如有),然後執行 pip install -t lib -r requirements.txt 重新填入 lib。如果您同時安裝了 Python 2 和 3,可能需要改用 pip2 指令。
  2. 請確認您已安裝初始化 gcloud 指令列工具,並已瞭解其用法。
  3. 如不想在發出的每個 gcloud 指令中輸入 PROJECT_ID,請使用 gcloud config set project PROJECT_ID 設定 Cloud 專案。
  4. 使用 gcloud app deploy 部署範例應用程式
  5. 確認 Module 1 應用程式可正常執行,並顯示最近的造訪記錄 (如下圖所示)

a7a9d2b80d706a2b.png

4. 更新設定

您不需要變更標準 App Engine 設定檔 (app.yamlrequirements.txtappengine_config.py),只要新增設定檔 queue.yaml,並將下列內容放入該檔案,然後放在同一個頂層目錄中即可:

queue:
- name: pullq
  mode: pull

queue.yaml 檔案會指定應用程式的所有工作佇列 (App Engine 自動建立的 default [發送] 佇列除外)。在本例中,只有一個名為 pullq 的提取佇列。App Engine 規定 mode 指令必須指定為 pull,否則系統預設會建立發送佇列。如要進一步瞭解如何建立提取佇列,請參閱說明文件。如要瞭解其他選項,請參閱 queue.yaml 參考頁面

請將這個檔案與應用程式分開部署。您仍會使用 gcloud app deploy,但也會在指令列中提供 queue.yaml

$ gcloud app deploy queue.yaml
Configurations to update:

descriptor:      [/tmp/mod18-gaepull/queue.yaml]
type:            [task queues]
target project:  [my-project]

WARNING: Caution: You are updating queue configuration. This will override any changes performed using 'gcloud tasks'. More details at
https://cloud.google.com/tasks/docs/queue-yaml

Do you want to continue (Y/n)?

Updating config [queue]...⠹WARNING: We are using the App Engine app location (us-central1) as the default location. Please use the "--location" flag if you want to use a different location.
Updating config [queue]...done.

Task queues have been updated.

Visit the Cloud Platform Console Task Queues page to view your queues and cron jobs.
$

5. 修改應用程式程式碼

本節提供下列檔案的更新內容:

  • main.py - 在主要應用程式中新增提取佇列的使用方式
  • templates/index.html:更新網頁範本,顯示新資料

匯入和常數

第一步是新增一個匯入項目和數個常數,以支援提取佇列:

  • 新增 Task Queue 程式庫的匯入作業,即 google.appengine.api.taskqueue
  • 新增三個常數,支援從提取佇列 (QUEUE) 租用一小時 (HOUR) 的提取工作數上限 (TASKS)。
  • 新增常數,顯示最近的造訪次數和熱門訪客 (LIMIT)。

以下是原始程式碼,以及更新後的程式碼:

BEFORE:

from flask import Flask, render_template, request
from google.appengine.ext import ndb

app = Flask(__name__)

修改後:

from flask import Flask, render_template, request
from google.appengine.api import taskqueue
from google.appengine.ext import ndb

HOUR = 3600
LIMIT = 10
TASKS = 1000
QNAME = 'pullq'
QUEUE = taskqueue.Queue(QNAME)
app = Flask(__name__)

新增提取工作 (收集工作資料並在提取佇列中建立工作)

資料模型 Visit 維持不變,在 fetch_visits() 中顯示的造訪查詢作業也相同。這部分程式碼只需要變更 store_visit()。除了註冊造訪記錄,請在提取佇列中新增工作,並附上訪客的 IP 位址,以便工作站遞增訪客計數器。

BEFORE:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

修改後:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit in Datastore and queue request to bump visitor count'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()
    QUEUE.add(taskqueue.Task(payload=remote_addr, method='PULL'))

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

建立訪客追蹤的資料模型和查詢函式

新增資料模型 VisitorCount 來追蹤訪客,該模型應包含 visitor 本身的欄位,以及追蹤造訪次數的整數 counter。接著新增名為 fetch_counts() 的函式 (也可以是 Python classmethod),依造訪次數由多到少排序,查詢並傳回前幾名訪客。在 fetch_visits() 的主體正下方新增類別和函式:

class VisitorCount(ndb.Model):
    visitor = ndb.StringProperty(repeated=False, required=True)
    counter = ndb.IntegerProperty()

def fetch_counts(limit):
    'get top visitors'
    return VisitCount.query().order(-VisitCount.counter).fetch(limit)

新增工作者程式碼

新增函式 log_visitors(),透過對 /log 的 GET 要求記錄訪客。這項功能會使用字典/雜湊來追蹤最近的訪客人數,並盡可能在一小時內租用多項工作。系統會針對每項工作,計算同一位訪客的所有造訪次數。取得計數後,應用程式會更新 Datastore 中所有對應的 VisitorCount 實體,或視需要建立新的實體。最後一個步驟會傳回純文字訊息,指出已處理多少項工作,以及有多少訪客完成註冊。在 fetch_counts() 正下方將這個函式新增至 main.py

@app.route('/log')
def log_visitors():
    'worker processes recent visitor counts and updates them in Datastore'
    # tally recent visitor counts from queue then delete those tasks
    tallies = {}
    tasks = QUEUE.lease_tasks(HOUR, TASKS)
    for task in tasks:
        visitor = task.payload
        tallies[visitor] = tallies.get(visitor, 0) + 1
    if tasks:
        QUEUE.delete_tasks(tasks)

    # increment those counts in Datastore and return
    for visitor in tallies:
        counter = VisitorCount.query(VisitorCount.visitor == visitor).get()
        if not counter:
            counter = VisitorCount(visitor=visitor, counter=0)
            counter.put()
        counter.counter += tallies[visitor]
        counter.put()
    return 'DONE (with %d task[s] logging %d visitor[s])\r\n' % (
            len(tasks), len(tallies))

使用新的顯示資料更新主要處理常式

如要顯示熱門訪客,請更新主要處理常式 root() 來叫用 fetch_counts()。此外,範本也會更新,顯示熱門訪客和最近造訪次數。將訪客人數與最近一次的造訪記錄從呼叫封裝至 fetch_visits(),然後放入單一 context 傳遞至網頁範本。以下是變更前後的程式碼:

BEFORE:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)

修改後:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    context = {
        'limit':  LIMIT,
        'visits': fetch_visits(LIMIT),
        'counts': fetch_counts(LIMIT),
    }
    return render_template('index.html', **context)

以上是 main.py 的所有必要變更。為方便說明,我們以圖片呈現這些更新,讓您大致瞭解對 main.py 所做的變更:

ad5fd3345efc13d0.png

使用新的顯示資料更新網頁範本

網頁範本templates/index.html需要更新,才能顯示熱門訪客,以及最近訪客的一般有效負載。將熱門訪客及其計數放入頁面頂端的表格,並繼續照常顯示最近的造訪記錄。唯一其他變更,就是透過 limit 變數指定顯示的數字,而不是將數字硬式編碼。請對網頁範本進行下列更新:

BEFORE:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

修改後:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>

<h3>Top {{ limit }} visitors</h3>
<table border=1 cellspacing=0 cellpadding=2>
    <tr><th>Visitor</th><th>Visits</th></tr>
{% for count in counts %}
    <tr><td>{{ count.visitor|e }}</td><td align="center">{{ count.counter }}</td></tr>
{% endfor %}
</table>

<h3>Last {{ limit }} visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

至此,您已完成必要變更,將 App Engine 工作佇列提取工作新增至 Module 1 範例應用程式。您的目錄現在代表 Module 18 範例應用程式,應包含下列檔案:

$ ls
README.md               appengine_config.py     queue.yaml              templates
app.yaml                main.py                 requirements.txt

6. 摘要/清除

本節將部署應用程式,並驗證應用程式是否正常運作,以及是否反映在輸出內容中,為本程式碼研究室畫下句點。請分開執行工作站,處理訪客人數。應用程式驗證完成後,請執行任何清理步驟,並考慮後續步驟。

部署及驗證應用程式

請務必先設定提取佇列,就像我們在本程式碼研究室頂端使用 gcloud app deploy queue.yaml 進行設定一樣。如果已完成上述步驟,且範例應用程式已準備就緒,請使用 gcloud app deploy 部署應用程式。輸出內容應與第 1 堂課程的應用程式相同,但頂端現在會顯示「熱門訪客」表格:

b667551dcbab1a09.png

更新後的網頁前端會顯示熱門訪客和最近的造訪記錄,但請注意,訪客人數包含這次造訪。應用程式會顯示先前的訪客人數,同時在提取佇列中捨棄一項新工作,這項工作會遞增該訪客的計數,並等待處理。

您可以透過多種方式呼叫 /log 來執行工作:

舉例來說,如果您使用 curl 將 GET 要求傳送至 /log,並提供 PROJECT_ID,輸出內容會如下所示:

$ curl https://PROJECT_ID.appspot.com/log
DONE (with 1 task[s] logging 1 visitor[s])

更新後的計數會在下次造訪網站時反映。就是這麼簡單!

恭喜您完成本程式碼研究室,並成功在範例應用程式中新增 App Engine 工作佇列提取佇列服務。現在可以遷移至第 19 節中的 Cloud Pub/Sub、Cloud NDB 和 Python 3。

清除所用資源

一般

如果暫時不需要使用,建議停用 App Engine 應用程式,以免產生費用。不過,如果您想進一步測試或實驗,App Engine 平台提供免付費配額,只要不超出該用量層級,就不會產生費用。這是指運算費用,但您可能也需要支付相關 App Engine 服務的費用,因此請參閱其定價頁面瞭解詳情。如果這項遷移作業涉及其他雲端服務,則這些服務會另外計費。無論是哪種情況,請視需要參閱下方的「本程式碼研究室專用」一節。

為求完整揭露,部署至 App Engine 等 Google Cloud 無伺服器運算平台時,會產生少量建構和儲存空間費用Cloud BuildCloud Storage 都有各自的免費配額。儲存該圖片會耗用部分配額。不過,你所在的區域可能沒有這類免付費層級,因此請留意儲存空間用量,盡量減少潛在費用。您應審查的特定 Cloud Storage「資料夾」包括:

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • 上述儲存空間連結取決於您的PROJECT_ID和 *LOC*ation,例如,如果您的應用程式託管於美國,則為「us」。

另一方面,如果您不打算繼續使用這個應用程式或其他相關的遷移 Codelab,並想完全刪除所有內容,請關閉專案

本程式碼研究室專用

下列服務是本程式碼研究室的專屬服務。詳情請參閱各項產品的說明文件:

後續步驟

在這次「遷移」中,您已在第 1 模組範例應用程式中新增工作佇列推送佇列的使用方式,方法是新增追蹤訪客的支援功能,進而實作第 18 模組範例應用程式。在下一次遷移中,您會將 App Engine 提取工作升級至 Cloud Pub/Sub。自 2021 年底起,升級至 Python 3 時,使用者不再需要遷移至 Cloud Pub/Sub。詳情請參閱下一節。

如要遷移至 Cloud Pub/Sub,請參閱第 19 堂程式碼研究室課程。此外,您還需要考慮其他遷移作業,例如 Cloud Datastore、Cloud Memorystore、Cloud Storage 或 Cloud Tasks (推送佇列)。此外,您也可以將其他產品遷移至 Cloud Run 和 Cloud Functions。所有 Serverless Migration Station 內容 (程式碼研究室、影片、原始碼 [如有]) 都可在開放原始碼存放區存取。

7. 遷移至 Python 3

2021 年秋季,App Engine 團隊將許多套裝組合服務的支援範圍擴展至第 2 代執行階段 (具有第 1 代執行階段)。因此,將應用程式移植到 Python 3 時,您不必再從 App Engine 工作佇列等套裝組合服務,遷移至 Cloud Pub/Sub 等獨立的 Cloud 或第三方服務。換句話說,只要您改造程式碼,從新一代執行階段存取套裝組合服務,就能繼續在 Python 3 App Engine 應用程式中使用工作佇列。

如要進一步瞭解如何將套裝服務的使用情形遷移至 Python 3,請參閱第 17 堂程式碼研究室和相關影片。雖然這項主題不在第 18 堂課的範圍內,但下方連結提供已移植到 Python 3 的第 1 堂課應用程式 Python 3 版本,且仍使用 App Engine NDB。(我們也會在日後提供 Module 18 應用程式的 Python 3 版本)。

8. 其他資源

以下列出其他資源,供開發人員進一步瞭解這個或相關的遷移模組,以及相關產品。包括提供內容意見回饋的位置、程式碼連結,以及您可能會覺得實用的各種文件。

程式碼研究室問題/意見回饋

如果發現本程式碼研究室有任何問題,請先搜尋問題,再提出回報。搜尋及建立新問題的連結:

遷移資源

下表提供第 1 堂課 (START) 和第 18 堂課 (FINISH) 的存放區資料夾連結。您也可以從所有 App Engine Codelab 遷移作業的存放區存取這些檔案,方法是複製存放區或下載 ZIP 檔案。

Codelab

Python 2

Python 3

Module 1

code

程式碼 (本教學課程未介紹)

單元 18 (本程式碼研究室)

code

不適用

線上參考資料

以下是與本教學課程相關的資源:

App Engine Task Queue

App Engine 平台

App Engine 說明文件

Python 2 App Engine (標準環境) 執行階段

Python 3 App Engine (標準環境) 執行階段

Python 2 和 3 App Engine (標準環境) 執行階段的差異

Python 2 到 3 App Engine (標準環境) 遷移指南

App Engine 定價配額資訊

推出第二代 App Engine 平台 (2018 年)

對舊版執行階段的長期支援

說明文件遷移範例

其他雲端資訊

影片

授權

這項內容採用的授權為 Creative Commons 姓名標示 2.0 通用授權。