如何在 Flask 應用程式中使用 App Engine 工作佇列 (推送工作) (模組 7)

1. 總覽

無伺服器遷移站系列的程式碼研究室系列 (自助式實作教學課程) 和相關影片,旨在引導 Google Cloud 無伺服器開發人員透過一或多種遷移作業 (主要用於遷移舊版服務) 逐步翻新應用程式。這麼一來,您的應用程式就能更具可攜性,並提供更多選擇和使用彈性,進而整合及使用更多 Cloud 產品,也更容易升級至較新的語言版本。本系列課程一開始將著重在最早的 Cloud 使用者 (主要是 App Engine (標準環境) 開發人員),但涵蓋其他無伺服器平台,包括 Cloud FunctionsCloud Run 或其他無伺服器平台 (如適用)。

本程式碼研究室將說明如何在模組 1 程式碼研究室範例應用程式中使用 App Engine 工作佇列推送工作單元 7 網誌文章和影片 與本教學課程的內容相輔相成,提供本教學課程內容的簡要介紹。

在這個單元中,我們會新增使用「推送」工作的功能,然後在單元 8 及後續單元中,將使用情況遷移至 Cloud Tasks,並在單元 9 中遷移至 Python 3 和 Cloud Datastore。使用工作佇列執行「提取」工作的使用者會遷移至 Cloud Pub/Sub,並應參照單元 18 至 19。

在接下來的研究室中

  • 使用 App Engine Task Queue API/套裝組合服務
  • 將推送工作用量新增至基本的 Python 2 Flask App Engine NDB 應用程式

軟硬體需求

問卷調查

您會如何使用這個教學課程?

僅供閱讀 閱讀並完成練習

您對 Python 的使用體驗有何評價?

新手 中級 還算容易

針對使用 Google Cloud 服務的經驗,您會給予什麼評價?

新手 中級 還算容易

2. 背景

App Engine 工作佇列支援推送和提取工作。為提升應用程式可攜性,Google Cloud 團隊建議從 Task Queue 等舊版套裝組合服務,遷移至其他 Cloud 獨立或第三方同等服務。

遷移模組 18 至 19 涵蓋提取工作遷移,而單元 7 至 9 則著重於推送工作遷移。如要從 App Engine 工作佇列推送工作遷移,請將相關用途新增至現有 Flask 和 App Engine NDB 應用程式 (依據單元 1 程式碼研究室產生)。在該應用程式中,新的網頁瀏覽會記錄一次新的「造訪」,並顯示使用者最近的造訪記錄。由於系統不會再顯示較舊的造訪記錄並佔用 Datastore 空間,因此我們會建立推送工作,自動刪除最舊的造訪記錄。在單元 8 中,我們會將該應用程式從工作佇列遷移至 Cloud Tasks。

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

  1. 設定/事前作業
  2. 更新設定
  3. 修改應用程式程式碼

3. 設定/事前作業

本節說明如何:

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

這些步驟可確保您一開始就使用有效的程式碼。

1. 設定專案

如果您已完成單元 1 程式碼研究室,建議您重複使用相同的專案和程式碼。或者,您可以建立新的專案,或是重複使用其他現有專案。請確認專案具備有效的帳單帳戶,且 App Engine 已啟用。

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

本程式碼研究室的必要條件之一,是擁有正常運作的單元 1 應用程式:完成單元 1 程式碼研究室 (建議) 或從存放區複製模組 1 應用程式。無論您使用您自己的或我們的模組,我們都會在單元 1 程式碼下「開始」。本程式碼研究室會逐步引導您完成每個步驟,最後程式碼會與單元 7 存放區資料夾「FINISH」中顯示的程式碼類似。

無論使用哪個模組 1 應用程式,資料夾應如下所示,可能也有 lib 資料夾:

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

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. 確認模組 1 應用程式可正常運作,而不會顯示最近的造訪情形 (如下圖所示)

a7a9d2b80d706a2b.png

4. 更新設定

不需要對標準 App Engine 設定檔 (app.yamlrequirements.txtappengine_config.py) 進行任何變更。

5. 修改應用程式檔案

主要應用程式檔案為 main.py,而本節中的所有更新都與該檔案相關。網路範本 templates/index.html 也進行了微幅更新。以下是在本節中實作的變更:

  1. 更新匯入作業
  2. 新增推送工作
  3. 新增工作處理常式
  4. 更新網路範本

1. 更新匯入作業

匯入 google.appengine.api.taskqueue 可提供工作佇列功能。也需要使用部分 Python 標準程式庫套件:

  • 我們新增了一項工作來刪除最舊的造訪記錄,因此應用程式需要處理時間戳記 (也就是 timedatetime 的使用情形)。
  • 如要記錄與工作執行作業相關的實用資訊,我們需要 logging

新增上述所有匯入項目,下列程式碼將如下所示:

變更前:

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

變更後:

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

2. 新增發送工作 (彙整工作資料、將新工作排入佇列)

發送佇列說明文件狀態:「如要處理工作,您必須將工作新增至發送佇列。App Engine 提供名為 default 的預設發送佇列,該佇列已設定完成,可直接與預設設定搭配使用。您可以視需要將所有工作新增至預設佇列,而不需建立及設定其他佇列。」為求簡潔,本程式碼研究室會使用 default 佇列。如要進一步瞭解如何以相同或不同的特性定義自己的發送佇列,請參閱建立發送佇列說明文件

本程式碼研究室的主要目標是新增工作 (至 default 發送佇列),其工作將刪除 Datastore 中不再顯示的舊造訪。基準應用程式會建立新的 Visit 實體,藉此登錄每次造訪 (向 / 提出 GET 要求),然後擷取並顯示最近的造訪記錄。由於沒有任何最舊的造訪記錄不會再顯示或再次使用,因此推送工作會刪除顯示時間最舊的造訪記錄。為此,應用程式的行為需要稍微改變:

  1. 查詢最近一次造訪時,請不要立即傳回這些造訪記錄,而是修改應用程式來儲存上個 Visit (顯示時間最久) 的時間戳記。您可以放心刪除超過這個時間的所有造訪記錄。
  2. 建立發送工作,將這個時間戳記做為其酬載,並將其導向至工作處理常式 (可透過 HTTP POST 存取至 /trim)。具體而言,請使用標準 Python 公用程式,轉換 Datastore 時間戳記,然後將其 (以浮點值的形式) 傳送至工作,但同時記錄它 (以字串形式) 並傳回該字串,做為向使用者顯示的數條值。

所有動作都會在 fetch_visits() 中進行,這些更新前後的外觀如下:

變更前:

def fetch_visits(limit):
    return (v.to_dict() for v in Visit.query().order(
            -Visit.timestamp).fetch(limit))

變更後:

def fetch_visits(limit):
    'get most recent visits and add task to delete older visits'
    data = Visit.query().order(-Visit.timestamp).fetch(limit)
    oldest = time.mktime(data[-1].timestamp.timetuple())
    oldest_str = time.ctime(oldest)
    logging.info('Delete entities older than %s' % oldest_str)
    taskqueue.add(url='/trim', params={'oldest': oldest})
    return (v.to_dict() for v in data), oldest_str

3. 新增工作處理常式 (工作執行時呼叫的程式碼)

雖然在 fetch_visits() 中可以輕鬆刪除舊的造訪記錄,但使用者如果能輕鬆刪除這項功能,這是輔助功能,非常適合在標準應用程式要求之外以非同步方式處理。Datastore 中的資訊會較少,因此使用者可享有加快查詢速度的優勢。建立新的函式 trim(),並透過對 /trim 的工作佇列 POST 要求呼叫該函式,此函式會執行以下動作:

  1. 擷取「最舊的造訪」時間戳記酬載
  2. 發出資料儲存庫查詢,找出早於該時間戳記的所有實體。
  3. 啟用更快速的「僅限金鑰」因為不需要實際的使用者資料。
  4. 記錄要刪除的實體數量 (包括零)。
  5. 呼叫 ndb.delete_multi() 以刪除任何實體 (如果沒有,則略過)。
  6. 傳回空字串 (以及隱含 HTTP 200 傳回代碼)。

你可以在下方的 trim() 中查看相關資訊。將其新增至 main.py 緊接在 fetch_visits() 之後:

@app.route('/trim', methods=['POST'])
def trim():
    '(push) task queue handler to delete oldest visits'
    oldest = request.form.get('oldest', type=float)
    keys = Visit.query(
            Visit.timestamp < datetime.fromtimestamp(oldest)
    ).fetch(keys_only=True)
    nkeys = len(keys)
    if nkeys:
        logging.info('Deleting %d entities: %s' % (
                nkeys, ', '.join(str(k.id()) for k in keys)))
        ndb.delete_multi(keys)
    else:
        logging.info('No entities older than: %s' % time.ctime(oldest))
    return ''   # need to return SOME string w/200

4. 更新網路範本

更新網路範本 templates/index.html 搭配這個 Jinja2 條件式,以在變數存在時顯示最舊時間戳記:

{% if oldest is defined %}
    <b>Deleting visits older than:</b> {{ oldest }}</p>
{% endif %}

在顯示的造訪清單後方,但在內文結束之前加入這段程式碼,讓範本看起來像這樣:

<!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>

{% if oldest is defined %}
    <b>Deleting visits older than:</b> {{ oldest }}</p>
{% endif %}
</body>
</html>

6. 摘要/清除

本節總結此程式碼研究室的內容,做法是部署應用程式,確認應用程式是否正常運作,以及任何反映的輸出內容。驗證應用程式後,請執行所有清理作業,並考慮後續步驟。

部署及驗證應用程式

使用 gcloud app deploy 部署應用程式。輸出內容應與模組 1 應用程式相同,但在底部顯示會刪除哪些造訪的一行:

4aa8a2cb5f527079.png

恭喜您完成本程式碼研究室。您的程式碼現在應與模組 7 存放區資料夾中的程式碼相符。現已準備好遷移至 Cloud Tasks 單元 8

清除所用資源

一般

如果您現階段已完成設定,建議您停用 App Engine 應用程式,以免產生帳單費用。不過,如果您想測試或進行其他測試,App Engine 平台提供免費配額,而且只要不超出用量限制,就不需支付任何費用。這適用於運算,但相關 App Engine 服務可能也會產生費用,詳情請參閱定價頁面。如果這項遷移作業涉及其他 Cloud 服務,我們會另外計費。無論採用哪種情況,請參閱「本程式碼研究室的專屬」以下章節。

如要完整揭露,部署至 Google Cloud 無伺服器運算平台 (如 App Engine) 會產生少許建構和儲存空間費用Cloud Build 提供的免費配額與 Cloud 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*,例如「us」如果應用程式是由美國代管

另一方面,如果您不打算繼續使用這個應用程式或其他相關的遷移程式碼研究室,且想要徹底刪除所有項目,請關閉專案

本程式碼研究室的專屬功能

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

後續步驟

在「遷移」您在模組 1 範例應用程式中新增了工作佇列發送佇列使用行為,新增支援追蹤訪客的功能,進而構成模組 7 範例應用程式。下次遷移工作則會說明如果您選擇這麼做,如何從 App Engine 推送工作升級至 Cloud Tasks。自 2021 年秋季起,使用者升級至 Python 3 時不必再遷移至 Cloud Tasks。詳情請參閱下一節。

如果您「確實」想改用 Cloud Tasks,請查看單元 8 程式碼研究室。除此之外,還有需要考慮的額外遷移工作,例如 Cloud Datastore、Cloud Memorystore、Cloud Storage 或 Cloud Pub/Sub (提取佇列)。還有產品跨產品遷移至 Cloud Run 和 Cloud Functions。所有無伺服器遷移站內容 (程式碼研究室、影片、原始碼 [如有]) 都可以透過其開放原始碼存放區存取。

7. 遷移至 Python 3

2021 年秋季,App Engine 團隊將許多套裝組合服務的支援延伸至第 2 代執行階段 (最初僅適用於第 1 代執行階段),也就是說,當您將應用程式移植至 Python 3 時,您不再需要從套裝組合的服務 (例如 App Engine 工作佇列) 遷移至獨立 Cloud 服務,或是類似第三方對等的服務 (例如 Cloud Tasks)。換句話說,您可以在 Python 3 App Engine 應用程式中繼續使用工作佇列,只要修改程式碼即可從新一代執行階段存取隨附的服務

想進一步瞭解如何將套裝組合服務使用情形遷移至 Python 3,請參閱單元 17 程式碼研究室及其對應的影片。雖然該主題不在單元 7 的涵蓋範圍內,不過下列連結是適用於單元 1 和 7 的 Python 3 版應用程式,現已轉移至 Python 3,且仍在使用 App Engine NDB 和工作佇列 (Task Queue)。

8. 其他資源

以下列出開發人員可進一步探索這個或相關的遷移模組和相關產品。包括提供這項內容意見回饋的地方、程式碼連結,以及各種實用的說明文件。

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

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

遷移資源

下表提供模組 2 (START) 和單元 7 (FINISH) 的存放區資料夾連結。

Codelab

Python 2

Python 3

Module 1

程式碼

code (並未出現在本教學課程中)

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

程式碼

code (並未出現在本教學課程中)

線上資源

以下為可能與本教學課程相關的線上資源:

App Engine Task Queue

App Engine 平台

其他 Cloud 資訊

影片

授權

這項內容採用的是創用 CC 姓名標示 2.0 通用授權。