從 App Engine 工作佇列提取工作遷移至 Cloud Pub/Sub (模組 19)

1. 總覽

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

本程式碼研究室旨在向 Python 2 App Engine 開發人員說明如何從 App Engine 工作佇列提取工作遷移至 Cloud Pub/Sub。我們還提供了從 App Engine NDB 間接遷移Cloud NDB 的做法,給予 Datastore 存取權 (主要涵蓋於單元 2),以及升級至 Python 3。

在單元 18 中,您將瞭解如何在應用程式中新增「提取」工作。在本單元中,您將會取得完成的單元 18 應用程式,並將該使用資料遷移至 Cloud Pub/Sub。如果使用者使用的是工作佇列執行「推送」工作,則會改為遷移至 Cloud Tasks,並應參照單元 7 至 9。

在接下來的研究室中

軟硬體需求

問卷調查

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

只能閱讀 閱讀並完成練習

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

新手 中級 還算容易

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

新手 中級 還算容易

2. 背景

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

遷移模組 7 到 9 涵蓋推送工作遷移,單元 18 至 19 則著重於提取工作遷移。雖然 Cloud Tasks 會更緊密地比對工作佇列推送工作,但 Pub/Sub 並未與工作佇列提取工作的類比關閉。

Pub/Sub 的功能比工作佇列提供的提取功能還要多。舉例來說,Pub/Sub 也有「推送」功能,但 Cloud Tasks 更類似於工作佇列推送工作,因此 Pub/Sub 推送作業「不」受到任何遷移模組的涵蓋範圍。本單元 19 程式碼研究室示範如何將佇列機制從工作佇列提取佇列切換至 Pub/Sub,以及從 App Engine NDB 遷移至 Cloud NDB 以存取 Datastore,並重複執行模組 2 遷移作業。

當模組 18 程式碼為「已宣傳」時如同 Python 2 範例應用程式,原始碼本身與 Python 2 和 Python 3 相容,而且在本單元 19 的單元 19 遷移至 Cloud Pub/Sub (和 Cloud NDB) 後,來源本身仍保持不變。

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

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

3. 設定/事前作業

本節說明如何:

  1. 設定 Cloud 專案
  2. 取得基準範例應用程式
  3. (重新) 部署及驗證基準應用程式
  4. 啟用新的 Google Cloud 服務/API

以下步驟可確保您從有效的程式碼開始著手,並準備好遷移至 Cloud 服務。

1. 設定專案

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

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

必要條件之一是正常運作的模組 18 App Engine 應用程式,因此請完成其程式碼研究室 (建議;上方連結),或從存放區複製模組 18 程式碼。無論您使用自有或我們的資料,我們都會從這裡開始 (「START」)。本程式碼研究室將引導您逐步完成遷移作業,最後程式碼會與單元 19 存放區資料夾 (「FINISH」) 中顯示的程式碼類似。

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

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

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

執行下列指令,部署模組 18 應用程式:

  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. 確認應用程式運作正常,不會出現任何問題。如果您已完成單元 18 程式碼研究室,應用程式會顯示熱門訪客以及最近的造訪 (如下圖所示)。否則無法顯示任何訪客人數。

b667551dcbab1a09.png

遷移模組 18 範例應用程式之前,您必須先啟用修改後應用程式要使用的雲端服務。

4. 啟用新的 Google Cloud 服務/API

舊版應用程式使用 App Engine 套裝組合服務,您不需要進行額外設定,但獨立的 Cloud 服務需要額外設定,而且更新後的應用程式會同時採用 Cloud Pub/Sub 和 Cloud Datastore (透過 Cloud NDB 用戶端程式庫)。App Engine 和這兩個 Cloud API 都有「一律免費」級別的配額,而且只要不超過限制,完成本教學課程就不會產生費用。視您的偏好而定,您可以透過 Cloud 控制台或指令列啟用 Cloud API。

使用 Cloud 控制台

請前往 Cloud 控制台中的 API Manager 的程式庫頁面 (需執行專案),然後使用頁面中間的搜尋列搜尋 Cloud Datastore 和 Cloud Pub/Sub API:

c7a740304e9d35b.png

分別點選各個 API 的「啟用」按鈕,系統可能會提示您提供帳單資訊。例如,以下是「Cloud Pub/Sub API Library」頁面:

1b6c0a2a73124f6b.jpeg

透過指令列

從控制台啟用 API 可在視覺上取得豐富的資訊,但有些偏好使用指令列。發出 gcloud services enable pubsub.googleapis.com datastore.googleapis.com 指令,即可同時啟用這兩個 API:

$ gcloud services enable pubsub.googleapis.com datastore.googleapis.com
Operation "operations/acat.p2-aaa-bbb-ccc-ddd-eee-ffffff" finished successfully.

系統可能會提示您輸入帳單資訊。如要啟用其他 Cloud API 並想瞭解其 URI,您可以在各個 API 程式庫頁面底部找到這些 API。舉例來說,您可以將 pubsub.googleapis.com 觀察為「服務名稱」。

完成步驟後,您的專案就能存取 API。接下來請更新應用程式,以便使用這些 API。

4. 建立 Pub/Sub 資源

回顧單元 18 中工作佇列工作流程的順序:

  1. 模組 18 使用 queue.yaml 檔案建立名為 pullq 的提取佇列。
  2. 應用程式會將工作新增至提取佇列,以追蹤訪客。
  3. 工作最終是由工作站處理,且租用的時間有限 (一個小時)。
  4. 系統會執行工作以計算最近的訪客人數。
  5. 工作完成後,工作就會從佇列中刪除。

您將透過 Pub/Sub 複製類似的工作流程。下一節將介紹基本的 Pub/Sub 術語,當中包含三種建立必要的 Pub/Sub 資源的方法。

App Engine 工作佇列 (提取) 與 Cloud Pub/Sub 術語的比較

如要切換至 Pub/Sub,您必須稍微調整詞彙。以下列出兩項產品的主要類別,以及相關字詞。另請參閱遷移指南,其中提供類似比較功能。

  • 排入資料結構:有了工作佇列,資料會進入提取佇列;與 Pub/Sub 整合後,資料會歸入「主題」
  • 已排入佇列的資料單位: 具有工作佇列的提取工作稱為具有 Pub/Sub 的「訊息」
  • 資料處理者:透過工作佇列,工作站可存取提取工作。使用 Pub/Sub,您需要訂閱/訂閱者才能接收訊息
  • 資料擷取: 「釋出」提取工作與「提取」主題訊息 (透過訂閱項目) 相同。
  • 清除/完成: 完成後,從提取佇列刪除工作佇列工作,類似於確認 Pub/Sub 訊息

雖然將產品排入佇列後,工作流程會保持類似:

  1. 應用程式會使用名為 pullq主題,而非提取佇列。
  2. 應用程式不會將工作新增至提取佇列,而會將訊息傳送至主題 (pullq)。
  3. 名為 worker訂閱者不會從提取佇列租用工作,而是從 pullq 主題中「提取」訊息。
  4. 應用程式會處理訊息酬載,增加 Datastore 中的訪客數。
  5. 應用程式不會從提取佇列中刪除工作,而是確認已處理的訊息。

使用工作佇列時,設定包括建立提取佇列。使用 Pub/Sub 進行設定時,您必須同時建立主題和訂閱項目。在單元 18 中,我們在應用程式執行作業外處理 queue.yaml。這項操作與 Pub/Sub 的方式相同

您可以透過下列三種方式建立主題和訂閱項目:

  1. 使用 Cloud 控制台
  2. 使用指令列
  3. 透過程式碼 (簡短的 Python 指令碼)

請選取下列其中一個選項,並按照對應的操作說明建立 Pub/Sub 資源。

使用 Cloud 控制台

如要透過 Cloud 控制台建立主題,請按照下列步驟操作:

  1. 前往 Cloud 控制台的 Pub/Sub 主題頁面
  2. 按一下頂端的「建立主題」;新的對話方塊視窗隨即開啟 (請見下圖)
  3. 在「Topic ID」(主題 ID) 欄位中輸入 pullq
  4. 取消選取所有已勾選的選項,然後選取「Google 代管的加密金鑰」
  5. 按一下「建立主題」按鈕。

主題建立對話方塊的外觀如下:

a05cfdbf64571ceb.png

現在您已有了主題,就必須為該主題建立訂閱項目:

  1. 前往 Cloud 控制台的 Pub/Sub 訂閱頁面
  2. 按一下頂端的「建立訂閱項目」 (見下圖)。
  3. 在「Subscription ID」(訂閱 ID) 欄位中輸入 worker
  4. 從「Select a Cloud Pub/Sub topic」(選取 Cloud Pub/Sub 主題) 下拉式選單中,挑選 pullq,並註明「完整路徑名稱」。例如:projects/PROJECT_ID/topics/pullq
  5. 在「傳送類型」部分,選取「提取」
  6. 所有其他選項維持不變,然後按一下「Create」(建立) 按鈕。

以下是訂閱項目建立畫面的外觀:

c5444375c20b0618.jpeg

您也可以在「主題」頁面 (這個「捷徑」) 建立訂閱項目。可協助你將主題與訂閱項目建立關聯。如要進一步瞭解如何建立訂閱項目,請參閱說明文件

透過指令列

Pub/Sub 使用者可以分別使用 gcloud pubsub topics create TOPIC_IDgcloud pubsub subscriptions create SUBSCRIPTION_ID --topic=TOPIC_ID 指令建立主題和訂閱項目。只要使用 pullqTOPIC_IDworkerSUBSCRIPTION_ID 執行這些內容,專案 PROJECT_ID 就會產生下列輸出內容:

$ gcloud pubsub topics create pullq
Created topic [projects/PROJECT_ID/topics/pullq].

$ gcloud pubsub subscriptions create worker --topic=pullq
Created subscription [projects/PROJECT_ID/subscriptions/worker].

另請參閱快速入門導覽課程說明文件中的這個頁面。使用指令列可以簡化定期建立主題和訂閱項目的工作流程,您可以在殼層指令碼中使用這類指令來達成此目的。

透過程式碼 (簡短的 Python 指令碼)

另一種自動化建立主題和訂閱項目的方式,是在原始碼中使用 Pub/Sub API。以下是模組 19 存放區資料夾中的 maker.py 指令碼程式碼。

from __future__ import print_function
import google.auth
from google.api_core import exceptions
from google.cloud import pubsub

_, PROJECT_ID = google.auth.default()
TOPIC = 'pullq'
SBSCR = 'worker'
ppc_client = pubsub.PublisherClient()
psc_client = pubsub.SubscriberClient()
TOP_PATH = ppc_client.topic_path(PROJECT_ID, TOPIC)
SUB_PATH = psc_client.subscription_path(PROJECT_ID, SBSCR)

def make_top():
    try:
        top = ppc_client.create_topic(name=TOP_PATH)
        print('Created topic %r (%s)' % (TOPIC, top.name))
    except exceptions.AlreadyExists:
        print('Topic %r already exists at %r' % (TOPIC, TOP_PATH))

def make_sub():
    try:
        sub = psc_client.create_subscription(name=SUB_PATH, topic=TOP_PATH)
        print('Subscription created %r (%s)' % (SBSCR, sub.name))
    except exceptions.AlreadyExists:
        print('Subscription %r already exists at %r' % (SBSCR, SUB_PATH))
    try:
        psc_client.close()
    except AttributeError:  # special Py2 handler for grpcio<1.12.0
        pass

make_top()
make_sub()

執行這個指令碼會產生預期的輸出內容 (前提是沒有錯誤):

$ python3 maker.py
Created topic 'pullq' (projects/PROJECT_ID/topics/pullq)
Subscription created 'worker' (projects/PROJECT_ID/subscriptions/worker)

呼叫 API 以建立既有的資源,會導致用戶端程式庫擲回 google.api_core.exceptions.AlreadyExists 例外狀況,並由指令碼妥善處理:

$ python3 maker.py
Topic 'pullq' already exists at 'projects/PROJECT_ID/topics/pullq'
Subscription 'worker' already exists at 'projects/PROJECT_ID/subscriptions/worker'

如果您是 Pub/Sub 新手,請參閱 Pub/Sub 架構白皮書瞭解詳情。

5. 更新設定

配置的更新包括變更各種設定檔,以及建立相等的 App Engine 提取佇列,但在 Cloud Pub/Sub 生態系統內。

刪除 Queue.yaml

我們即將停用工作佇列,因此請刪除 queue.yaml,因為 Pub/Sub 不會使用這個檔案。請勿建立提取佇列,而是建立一個 Pub/Sub 主題 (和訂閱項目)。

requirements.txt

google-cloud-ndbgoogle-cloud-pubsub 附加至 requirements.txt,以便彙整單元 18 的 flask。更新後的模組 19 requirements.txt 現在應如下所示:

flask
google-cloud-ndb
google-cloud-pubsub

這個 requirements.txt 檔案不提供任何版本號碼,表示已選取最新版本。如果發生任何不相容的問題,請遵守使用版本號碼的標準做法,讓應用程式鎖定可正常運作的版本。

app.yaml

根據要使用 Python 2 還是升級至 Python 3,app.yaml 的變更會有所不同。

Python 2

上述更新到 requirements.txt 後,新增了使用 Google Cloud 用戶端程式庫的功能。但這些需要 App Engine 額外支援,包括幾個內建程式庫setuptoolsgrpcio。如要使用內建程式庫,您必須使用 app.yaml 中的 libraries 區段和程式庫版本號碼,或為「最新」,瞭解 App Engine 伺服器中的最新功能。模組 18 app.yaml 還沒有以下其中一個部分:

變更前:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

app.yaml 中新增 libraries 區段,以及 setuptoolsgrpcio 的項目,並選取其最新版本。另外也為 Python 3 新增預留位置 runtime 項目,並在撰寫期間加上註解,以及目前的 3.x 版本,例如 3.10。經過這些變更後,app.yaml 現在看起來會像這樣:

變更後:

#runtime: python310
runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: setuptools
  version: latest
- name: grpcio
  version: latest

Python 3

Python 3 使用者和 app.yaml 可完全移除內容。在本節中,您將刪除 handlers 部分、threadsafeapi_version 指令,而且不會建立 libraries 區段。

第二代執行階段不提供內建的第三方程式庫,因此app.yaml「不需要」使用 libraries 區段。此外,您不再需要複製 (有時稱為廠商或自我套裝組合) 內建第三方套件。您只需要在 requirements.txt 中列出應用程式使用的第三方程式庫。

app.yaml 中的 handlers 區段用於指定應用程式 (指令碼) 和靜態檔案處理常式。由於 Python 3 執行階段需要網路架構來執行自己的轉送作業,因此所有指令碼處理常式都必須變更為 auto。如果應用程式 (例如模組 18) 未提供靜態檔案,則所有路徑都會是 auto,使其不相關。因此,您也不需要 handlers 區段,因此請將其刪除。

最後,Python 3 中都不會使用 threadsafeapi_version 指令,因此也請刪除這些指令。簡單來說,您應刪除 app.yaml 的所有部分,只保留 runtime 指令,並指定新版 Python 3,例如 3.10。app.yaml 在上述更新前後的運作方式如下:

變更前:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

變更後:

runtime: python310

如果使用者尚未準備好刪除 Python 3 的 app.yaml 中的所有內容,我們在模組 19 存放區資料夾中提供了 app3.yaml 替代檔案。如果您想在部署作業中使用這個名稱,請務必在指令的結尾加上這個檔案名稱:gcloud app deploy app3.yaml (否則,預設會使用您未變更的 Python 2 app.yaml 檔案,並部署應用程式)。

appengine_config.py

如果您要升級至 Python 3,則不需要使用 appengine_config.py,因此請將其刪除。因此不需要在第三方程式庫 requirements.txt 中指定。Python 2 使用者,請繼續閱讀。

模組 18 appengine_config.py 具有支援第三方程式庫的適當程式碼,例如剛新增至 requirements.txt 的 Flask 和 Cloud 用戶端程式庫:

變更前:

from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)

不過,單靠這段程式碼無法支援剛新增的內建程式庫 (setuptoolsgrpcio)。還需要幾行,因此請更新 appengine_config.py,如下所示:

變更後:

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)

如要進一步瞭解支援 Cloud 用戶端程式庫所需的變更,請參閱遷移套裝組合服務說明文件

其他設定更新

如有 lib 資料夾,請刪除。如果您是 Python 2 使用者,請執行以下指令補充 lib 資料夾:

pip install -t lib -r requirements.txt  # or pip2

如果您在開發系統中同時安裝 Python 2 和 3,您可能須使用 pip2 而非 pip

6. 修改應用程式程式碼

本節將更新主應用程式檔案 main.py 的功能,同時將 App Engine 工作佇列提取佇列替換為 Cloud Pub/Sub。網路範本「templates/index.html」沒有任何變更。這兩個應用程式應能以相同方式運作,顯示相同資料。

更新匯入和初始化作業

以下是匯入和初始化的幾項更新:

  1. 匯入時,請以 Cloud NDB 和 Pub/Sub 取代 App Engine NDB 和工作佇列。
  2. pullqQUEUE 名稱重新命名為 TOPIC 名稱。
  3. 透過提取工作,工作站租用了一小時。但是,如果使用 Pub/Sub,逾時時間是根據每則訊息計算,因此請刪除 HOUR 常數。
  4. Cloud API 需要使用 API 用戶端,因此針對 Cloud NDB 和 Cloud Pub/Sub 啟動用戶端,後者需要同時為主題和訂閱項目提供用戶端。
  5. Pub/Sub 需要 Cloud 專案 ID,因此請匯入並從 google.auth.default() 取得。
  6. Pub/Sub 需要「完整路徑名稱」主題和訂閱項目,因此請使用 *_path() 便利函式建立這些內容。

以下是單元 18 的匯入和初始化作業,以及實作上述變更後各節應如何呈現,其中大部分的新程式碼都是各種 Pub/Sub 資源:

變更前:

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__)

變更後:

from flask import Flask, render_template, request
import google.auth
from google.cloud import ndb, pubsub

LIMIT = 10
TASKS = 1000
TOPIC = 'pullq'
SBSCR = 'worker'

app = Flask(__name__)
ds_client  = ndb.Client()
ppc_client = pubsub.PublisherClient()
psc_client = pubsub.SubscriberClient()
_, PROJECT_ID = google.auth.default()
TOP_PATH = ppc_client.topic_path(PROJECT_ID, TOPIC)
SUB_PATH = psc_client.subscription_path(PROJECT_ID, SBSCR)

造訪資料模型更新

Visit 資料模型不會變更。如要使用 Datastore 存取權,您必須明確使用 Cloud NDB API 用戶端結構定義管理員 ds_client.context()。在程式碼中,這表示您將 Datastore 呼叫納入 Python with 區塊內的 store_visit()fetch_visits() 中。這次更新與第 2 單元相同。

與 Pub/Sub 最相關的變更,是將 Pub/Sub 訊息發布至 pullq 主題,取代將工作佇列提取工作排入佇列。以下是更新前後的程式碼:

變更前:

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)

變更後:

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'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()
    ppc_client.publish(TOP_PATH, remote_addr.encode('utf-8'))

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

VisitorCount 資料模型更新

VisitorCount 資料模型不會改變且會執行 fetch_counts(),但必須將 Datastore 查詢包裝在 with 區塊中,如下所示:

變更前:

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

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

變更後:

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

def fetch_counts(limit):
    'get top visitors'
    with ds_client.context():
        return VisitorCount.query().order(-VisitorCount.counter).fetch(limit)

更新工作站程式碼

工作站程式碼在更新時,仍會將 NDB 替換為 Cloud NDB 和工作佇列,但工作流程仍保持不變。

  1. 在 Cloud NDB 結構定義管理工具 with 區塊中包裝 Datastore 呼叫。
  2. 「工作佇列」清理作業包括刪除提取佇列中的所有工作。使用 Pub/Sub,具備「確認 ID」,然後會在 acks 中收集,並在結尾刪除/確認。
  3. 「工作佇列」提取工作的租用方式與 Pub/Sub 訊息的提取方式類似。刪除提取工作是由工作物件本身完成,但 Pub/Sub 訊息會透過確認 ID 刪除。
  4. Pub/Sub 訊息酬載需要位元組 (非 Python 字串),因此在發布主題訊息及從主題提取訊息時,您必須分別進行部分 UTF-8 編碼和解碼作業。

log_visitors() 換成下方更新過的程式碼,並實作上述變更:

變更前:

@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))

變更後:

@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 = {}
    acks = set()
    rsp = psc_client.pull(subscription=SUB_PATH, max_messages=TASKS)
    msgs = rsp.received_messages
    for rcvd_msg in msgs:
        acks.add(rcvd_msg.ack_id)
        visitor = rcvd_msg.message.data.decode('utf-8')
        tallies[visitor] = tallies.get(visitor, 0) + 1
    if acks:
        psc_client.acknowledge(subscription=SUB_PATH, ack_ids=acks)
    try:
        psc_client.close()
    except AttributeError:  # special handler for grpcio<1.12.0
        pass

    # increment those counts in Datastore and return
    if tallies:
        with ds_client.context():
            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(msgs), len(tallies))

主要應用程式處理常式 root() 沒有任何變更。您也不需要在 HTML 範本檔案 templates/index.html 中進行任何變更,因此檔案會納入所有必要的更新。恭喜您使用 Cloud Pub/Sub 進入新的單元 19 應用程式!

7. 摘要/清除

部署應用程式,確認應用程式是否能正常運作,以及任何反映的輸出內容。同時執行 worker 來處理訪客計數。驗證應用程式後,請執行所有清除步驟,並考慮後續步驟。

部署及驗證應用程式

請確認您已建立 pullq 主題和 worker 訂閱項目。如果已完成,且範例應用程式已準備就緒,請使用 gcloud app deploy 部署應用程式。輸出內容應與模組 18 應用程式相同,唯一差別在於您已成功取代整個基礎佇列機制:

b667551dcbab1a09.png

應用程式的網路前端會驗證這個應用程式的這個部分可正常運作。雖然應用程式的這個部分可成功查詢及顯示熱門訪客和最近造訪的內容,但提醒應用程式會登錄這次造訪,並建立提取工作,將這位訪客加到整體計數中。工作現已排入佇列,正在等待處理。

您可以使用 App Engine 後端服務、cron 工作、前往 /log 或發出指令列 HTTP 要求,執行這項作業。以下的執行範例為使用 curl 呼叫 worker 程式碼 (取代 PROJECT_ID):

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

下次造訪網站時,新的計數就會反映新的數量。這樣就大功告成了!

清除所用資源

一般

如果您現階段已完成設定,建議您停用 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」如果應用程式是由美國代管

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

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

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

  • Cloud Pub/Sub 的不同元件提供免費方案。確定您的整體用量,以便進一步瞭解影響費用,並參閱定價頁面瞭解詳情。
  • App Engine Datastore 服務是由 Cloud Datastore (Datastore 模式的 Cloud Firestore) 提供,這項服務也提供免費方案;詳情請參閱價格頁面

後續步驟

除了本教學課程之外,其他著重於淘汰舊版套裝組合服務的其他遷移模組,建議提供以下建議:

  • 模組 2:從 App Engine ndb 遷移至 Cloud NDB
  • 模組 7-9:從 App Engine 工作佇列 (推送工作) 遷移至 Cloud Tasks
  • 模組 12-13:從 App Engine Memcache 遷移至 Cloud Memorystore
  • 模組 15 至 16:從 App Engine Blob 遷移至 Cloud Storage

App Engine 不再是 Google Cloud 中唯一的無伺服器平台。如果您有小型 App Engine 應用程式,或功能有限,但想將其轉換為獨立微服務,或想將單體式應用程式拆解成多個可重複使用的元件,請考慮改用 Cloud Functions。如果容器化已成為應用程式開發工作流程的一部分,特別是在含有 CI/CD (持續整合/持續推送軟體更新或部署) 管道的情況下,請考慮遷移至 Cloud Run。以下單元將介紹這些情境:

  • 從 App Engine 遷移至 Cloud Functions:請參閱單元 11
  • 從 App Engine 遷移至 Cloud Run:請參閱模組 4,使用 Docker 將應用程式容器化;或參閱模組 5,瞭解如何在沒有容器、Docker 或 Dockerfile 的情況下執行應用程式

您可以選擇是否要改用其他無伺服器平台,在進行任何變更前,建議您考量應用程式和用途的最佳選擇。

無論接下來選擇使用哪個遷移模組,所有無伺服器遷移站內容 (程式碼研究室、影片、原始碼 [如有]) 都可以透過其開放原始碼存放區存取。存放區的 README 還針對應考慮遷移的項目和任何相關的「訂單」提供指引接下來介紹遷移模組

8. 其他資源

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

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

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

遷移資源

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

Codelab

Python 2

Python 3

單元 18

程式碼

(不適用)

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

程式碼

(與 Python 2 相同,但除非您已按照上述方式更新 app.yaml,否則請使用 app3.yaml)

線上參考資料

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

App Engine Task Queue

Cloud Pub/Sub

App Engine NDB 和 Cloud NDB (Datastore)

App Engine 平台

其他 Cloud 資訊

影片

授權

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