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

1. 總覽

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

本程式碼研究室將說明如何在模組 1 程式碼研究室中,納入並使用 App Engine 工作佇列提取工作範例應用程式。我們會在本單元單元 18 教學課程中新增對提取工作的相關用途,然後再於單元 19 中將該用法遷移至 Cloud Pub/Sub。如果使用者使用的是工作佇列執行「推送」工作,則會改為遷移至 Cloud Tasks,並應參照單元 7 至 9。

在接下來的研究室中

  • 使用 App Engine Task Queue API/套裝組合服務
  • 將提取佇列使用新增至基本的 Python 2 Flask App Engine NDB 應用程式

軟硬體需求

問卷調查

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

僅供閱讀 閱讀並完成練習

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

新手 中級 還算容易

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

新手 中級 還算容易

2. 背景

如要從 App Engine 工作佇列提取工作,請將任務新增至現有 Flask 和 App Engine NDB 應用程式 (依據單元 1 程式碼研究室產生)。範例應用程式會顯示使用者最近的造訪記錄。雖然沒關係,但最好同時追蹤訪客,看看誰最常造訪名單。

雖然我們可以使用「推送工作」來計算這些訪客人數,但我們仍想將範例應用程式的工作分離為應用程式登錄造訪並立即回應使用者,以及指定「工作站」的工作。其工作會計算一般要求/回應工作流程之外的訪客數。

為實作這項設計,我們新增了使用提取佇列至主要應用程式,以及支援 worker 功能。工作站可以做為獨立程序 (例如後端執行個體或在 VM 上執行的程式碼、Cron 工作,或基本的指令列 HTTP 要求使用 curlwget)。整合完成之後,您可以在下一個 (單元 19) 程式碼研究室中將應用程式遷移至 Cloud Pub/Sub

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

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

3. 設定/事前作業

本節說明如何:

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

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

1. 設定專案

如果您已完成單元 1 程式碼研究室,請重複使用相同的專案和程式碼。或者,您可以建立新的專案,或是重複使用其他現有專案。請確認專案具備有效的帳單帳戶和已啟用的 App Engine 應用程式。請找出您需要的專案 ID,因為在本程式碼研究室中,您需要多次使用這組 ID,每次遇到 PROJECT_ID 變數時都會使用該 ID。

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

本程式碼研究室的必備條件之一,就是擁有可正常運作的模組 1 應用程式。完成模組 1 程式碼研究室 (建議) 或從存放區複製模組 1 應用程式。無論您使用您自己的或我們的模組,我們都會在單元 1 程式碼下「開始」。本程式碼研究室會逐步引導您完成每個步驟,最後程式碼會與單元 18 存放區資料夾「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. 確認模組 1 應用程式可正常運作,並顯示最近的造訪記錄 (如下圖所示)

a7a9d2b80d706a2b.png

4. 更新設定

不需要對標準 App Engine 設定檔 (app.yamlrequirements.txtappengine_config.py) 進行任何變更。請改為新增設定檔 queue.yaml,並在當中加入以下內容,並將該檔案放在相同的頂層目錄中:

queue:
- name: pullq
  mode: pull

queue.yaml 檔案會指定應用程式現有的所有工作佇列 (除了 App Engine 自動建立的 default [push] 佇列以外)。在這個範例中,只有一個提取佇列,名為 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)。

以下是原始程式碼,以及完成更新後的外觀:

變更前:

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 位址的提取佇列,以便工作站增加訪客計數器。

變更前:

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() 的函式 classmethod (或者是 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(),以便透過 GET 要求將訪客記錄到 /log。它使用字典/雜湊來追蹤最近的訪客計數,並在一小時內盡可能租用工作。在每個工作中,它都會計算同一個訪客的所有造訪。完成計算後,應用程式會更新 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,再傳遞至網路範本。以下是變更之前及之後的程式碼:

變更前:

@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 變數指定顯示的數字,而非對數字進行硬式編碼。以下是應對網頁範本進行的更新:

變更前:

<!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 工作佇列提取工作到模組 1 範例應用程式中。您的目錄現在代表模組 18 範例應用程式,且應包含下列檔案:

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

6. 摘要/清除

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

部署及驗證應用程式

請確認您已設定提取佇列,如同我們在本程式碼研究室頂端使用 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 服務可能也會產生費用,詳情請參閱定價頁面。如果這項遷移作業涉及其他 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」如果應用程式是由美國代管

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

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

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

後續步驟

在「遷移」您藉由新增對追蹤訪客的支援,從而實作模組 18 範例應用程式,從而將工作佇列發送佇列使用新增至模組 1 範例應用程式。在下一次遷移作業中,您會將 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。所有無伺服器遷移站內容 (程式碼研究室、影片、原始碼 [如有]) 都可以透過其開放原始碼存放區存取。

7. 遷移至 Python 3

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

想進一步瞭解如何將套裝組合服務使用情形遷移至 Python 3,請參閱單元 17 程式碼研究室及其對應的影片。雖然該主題不在單元 18 的涵蓋範圍內,但下列連結是 Python 3 版模組 1 應用程式,已移植至 Python 3,且仍在使用 App Engine NDB。(在某些情況下,我們也會提供 Python 3 版本的模組 18 應用程式)。

8. 其他資源

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

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

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

遷移資源

下表提供單元 1 (START) 和單元 18 (FINISH) 的存放區資料夾連結。您也可從所有 App Engine 程式碼研究室遷移作業的存放區存取。或下載 ZIP 檔案。

Codelab

Python 2

Python 3

Module 1

程式碼

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

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

程式碼

不適用

線上參考資料

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

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)

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

說明文件遷移範例

其他 Cloud 資訊

影片

授權

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