本頁說明如何從第一代遷移至第二代 Java 執行階段。如要升級第二代應用程式,以使用最新支援的 Java 版本,請參閱「升級現有應用程式」。
Java 8 已於 2024 年 1 月 31 日終止支援。現有的 Java 8 應用程式會繼續執行並接收流量。不過,如果應用程式使用的執行階段已過支援期限,App Engine 可能會禁止重新部署。建議您按照本頁面的指南,改用最新支援的 Java 版本。
遷移至第二代 Java 執行階段後,您就能使用最新的語言功能,並以慣用程式碼建構可攜性更高的應用程式。
瞭解遷移選項
為減少執行階段遷移作業的負擔和複雜度,App Engine 標準環境可讓您在第二代 Java 執行階段存取許多舊版綁定服務和 API,例如 Memcache。您的 Java 應用程式可以透過 App Engine API JAR 呼叫套裝組合服務 API,並存取與 Java 8 執行階段相同的大部分功能。
您也可以使用 Google Cloud 產品,這些產品提供的功能與舊版套裝組合服務類似。這些產品提供慣用的 Java 適用的 Cloud 用戶端程式庫。 Google Cloud如果套裝組合中的服務無法在Google Cloud中以獨立產品的形式提供 (例如圖片處理、搜尋和訊息服務),您可以改用第三方供應商或其他解決方法。
如要進一步瞭解如何遷移至未綁定的服務,請參閱「從套裝組合服務遷移」。
根據您是否選擇使用舊版套裝服務,執行執行階段遷移作業的方式會有所不同:
遷移至第二代 Java 執行階段 (含套裝組合服務) | 遷移至不含套裝組合服務的第二代 Java 執行階段 |
---|---|
使用 App Engine API JAR 存取套裝組合服務。 | 視需要使用 建議的產品或第三方服務。 Google Cloud |
使用
視應用程式使用的功能而定,您可能也需要設定其他 YAML 檔案。 |
使用
視應用程式使用的功能而定,您可能也需要設定其他 YAML 檔案。 |
應用程式會透過 Jetty 部署。使用 WAR 格式封裝應用程式。 | 應用程式是透過您自己的伺服器部署。請使用 JAR 格式封裝應用程式。如要進一步瞭解如何將現有 WAR 檔案轉換為可執行的 JAR,請參閱「重新封裝 WAR 檔案」。 |
轉換程序總覽
如要使用第二代 Java 執行階段,您可能需要對現有的 App Engine Java 8 應用程式和部署程序進行下列變更:
- 下載 Google Cloud CLI。
- 從獨立的 App Engine Maven 外掛程式遷移至以 gcloud CLI 為基礎的 Maven 外掛程式,或以 gcloud CLI 為基礎的 Gradle 外掛程式。
- 如果您使用舊版套裝組合服務,請安裝 App Engine API JAR。
Java 8 與第二代 Java 執行階段的主要差異
以下摘要列出 App Engine 標準環境中 Java 8 與第二代 Java 執行階段之間的差異:
Java 8 執行階段 | 第二代 Java 執行階段 | |
---|---|---|
伺服器部署作業 | 使用 Jetty 為您部署的伺服器 | 如果應用程式未使用舊版套裝服務,您必須自行部署伺服器。1 |
App Engine 舊版套裝組合服務 | 提供 | 提供 |
可使用 Java 適用的 Cloud 用戶端程式庫 | 是 | 是 |
語言擴充功能與系統程式庫支援 | 是 | 是 |
外部網路存取權 | 是 | 是 |
檔案系統存取權 | /tmp 的讀取/寫入權限
|
/tmp 的讀取/寫入權限
|
語言執行階段 | 已針對 App Engine 修改 | 未修改的開放原始碼執行階段 |
隔離機制 | gVisor 式容器沙箱 | gVisor 式容器沙箱 |
使用本機開發伺服器進行測試 | 支援 | 支援 |
執行緒安全設定 | 可在 appengine-web.xml 檔案中指定。
|
無法在設定檔中指定。系統會假設所有應用程式皆具備「執行緒安全」特性。3 |
記錄 | 使用 java.util.logging. ConsoleHandler ,將資料寫入 stderr ,並在每筆記錄後排清串流 。 |
標準 Cloud Logging 2 |
支援 DataNucleus 外掛程式 2.x 版 | 支援 | 不支援 4 |
注意:
如果應用程式未使用舊版套裝服務,只要您封裝的網頁伺服器已設定為回應
PORT
環境變數指定的通訊埠 (建議) 或通訊埠 8080 的 HTTP 要求,第二代 Java 執行階段就能執行任何 Java 架構。舉例來說,第二代 Java 執行階段可直接執行 Spring Boot Uber JAR。如需更多範例,請參閱「架構彈性」一節。如果應用程式使用舊版綁定服務,App Engine 會以與 Java 8 執行階段相同的方式,使用 Jetty 部署應用程式。
第二代 Java 執行階段的記錄作業遵循 Cloud Logging 的記錄標準。在第二代 Java 執行階段中,應用程式記錄檔不再與要求記錄檔捆綁在一起,而是分開記錄。如要進一步瞭解如何在第二代 Java 執行階段讀取及寫入記錄,請參閱記錄指南。
如要在第二代 Java 執行階段中設定非安全執行緒應用程式,請在
app.yaml
檔案中將並行作業數上限設為 1,或在使用舊版套裝服務時,在appengine-web.xml
檔案中將並行作業數上限設為 1,做法與在 Java 8 中設定<threadsafe>false</threadsafe>
類似。Google 不支援第二代執行階段中的 DataNucleus 程式庫。較新版本的 DataNucleus 與 Java 8 中使用的版本不相容。如要存取 Datastore,建議使用 Datastore 模式用戶端程式庫或 Java Objectify (第 6 版以上) 解決方案。Objectify 是 Datastore 適用的開放原始碼 API,可提供更高層級的抽象化。
記憶體用量差異
與第一代執行階段相比,第二代執行階段的記憶體用量基準較高。這是因為多項因素,例如基本圖片版本不同,以及兩代產品計算記憶體用量的方式不同。
第二代執行階段會計算執行個體記憶體用量,方法是將應用程式程序使用的記憶體量,以及動態快取在記憶體中的應用程式檔案數量加總。為避免記憶體用量過高的應用程式因超出記憶體限制而導致執行個體關閉,請升級至記憶體較大的執行個體類別。
CPU 用量差異
第二代執行階段在執行個體冷啟動時,CPU 使用率的基準可能會較高。視應用程式的縮放設定而定,這可能會產生非預期的副作用,例如:如果應用程式設定為根據 CPU 使用率進行縮放,執行個體數量可能會超出預期。為避免發生這個問題,請檢查並測試應用程式的擴充設定,確保執行個體數量可接受。
要求標頭差異
第一代執行階段允許將含有底線 (例如 X-Test-Foo_bar
) 的要求標頭轉送至應用程式。第二代執行階段會在主機架構中導入 Nginx。這項變更會導致第二代執行階段自動移除含有底線 (_
) 的標頭。為避免應用程式發生問題,請勿在應用程式要求標頭中使用底線。
架構彈性
除非使用舊版套裝服務,否則第二代 Java 執行階段不會包含任何網頁服務架構。也就是說,您可以使用 Servlet 架構以外的架構。如果您使用舊版套裝服務,第二代 Java 執行階段會提供 Jetty 網路服務架構。
Google Cloud GitHub 存放區提供 hello world
範例,使用熱門的 Java 網頁架構:
將 XML 轉換成 YAML 檔案格式
gcloud CLI 不支援下列檔案格式:
cron.xml
datastore-index.xml
dispatch.xml
queue.xml
以下範例說明如何將 xml
檔案轉換成 yaml
檔案。
自動遷移檔案
如何自動遷移 xml
檔案:
您必須具有 gcloud CLI 226.0.0 以上版本。 如要更新至最新版本:
gcloud components update
對您要遷移的每個檔案,指定下列其中一個子指令 (
cron-xml-to-yaml
、datastore-indexes-xml-to-yaml
、dispatch-xml-to-yaml
、queue-xml-to-yaml
) 和檔案名稱:gcloud beta app migrate-config queue-xml-to-yaml MY-QUEUE-XML-FILE.xml
在部署到實際工作環境之前,先手動再次檢查已轉換的檔案。
如需
xml
到yaml
的檔案成功轉換範例,請參閱「手動遷移檔案」一節的各分頁。
手動遷移檔案
如要手動將 xml
檔案遷移至 yaml
檔案,請按照下列步驟操作:
cron.yaml
使用包含物件清單的 cron
物件建立 cron.yaml
檔案,每個物件都包含與 cron.xml
檔案中每個 <cron>
標記屬性對應的欄位,如下所示。
轉換後的 cron.yaml
檔案:
cron:
- url: '/recache'
schedule: 'every 2 minutes'
description: 'Repopulate the cache every 2 minutes'
- url: '/weeklyreport'
schedule: 'every monday 08:30'
target: 'version-2'
timezone: 'America/New_York'
description: 'Mail out a weekly report'
原始 cron.xml
檔案:
<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
<cron>
<url>/recache</url>
<description>Repopulate the cache every 2 minutes</description>
<schedule>every 2 minutes</schedule>
</cron>
<cron>
<url>/weeklyreport</url>
<description>Mail out a weekly report</description>
<schedule>every monday 08:30</schedule>
<timezone>America/New_York</timezone>
<target>version-2</target>
</cron>
</cronentries>
詳情請參閱 cron.yaml
參考資料說明文件。
dispatch.yaml
使用包含物件清單的 dispatch
物件建立 dispatch.yaml
檔案,每個物件都包含與 dispatch.xml
檔案中每個 <dispatch>
標記屬性對應的欄位,如下所示。
轉換後的 dispatch.yaml
檔案:
dispatch:
- url: '*/favicon.ico'
module: default
- url: 'simple-sample.uc.r.appspot.com/'
module: default
- url: '*/mobile/*'
module: mobile-frontend
原始 dispatch.xml
檔案
<?xml version="1.0" encoding="UTF-8"?>
<dispatch-entries>
<dispatch>
<url>*/favicon.ico</url>
<module>default</module>
</dispatch>
<dispatch>
<url>simple-sample.uc.r.appspot.com/</url>
<module>default</module>
</dispatch>
<dispatch>
<url>*/mobile/*</url>
<module>mobile-frontend</module>
</dispatch>
</dispatch-entries>
詳情請參閱 dispatch.yaml
參考資料說明文件。
index.yaml
使用包含物件清單的 indexes
物件建立 index.yaml
檔案,每個物件都包含與 datastore-indexes.xml
檔案中每個 <datastore-index>
標記屬性對應的欄位,如下所示。
轉換後的 index.yaml
檔案:
indexes:
- ancestor: false
kind: Employee
properties:
- direction: asc
name: lastName
- direction: desc
name: hireDate
- ancestor: false
kind: Project
properties:
- direction: asc
name: dueDate
- direction: desc
name: cost
原始 datastore-index.xml
檔案:
<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes
autoGenerate="true">
<datastore-index kind="Employee" ancestor="false">
<property name="lastName" direction="asc" />
<property name="hireDate" direction="desc" />
</datastore-index>
<datastore-index kind="Project" ancestor="false">
<property name="dueDate" direction="asc" />
<property name="cost" direction="desc" />
</datastore-index>
</datastore-indexes>
詳情請參閱 index.yaml
參考資料說明文件。
queue.yaml
使用包含物件清單的 queue
物件建立 queue.yaml
檔案,每個物件都包含與 queue.xml
檔案中每個 <queue>
標記屬性對應的欄位,如下所示。
轉換後的 queue.yaml
檔案:
queue:
- name: fooqueue
mode: push
rate: 1/s
retry_parameters:
task_retry_limit: 7
task_age_limit: 2d
- name: barqueue
mode: push
rate: 1/s
retry_parameters:
min_backoff_seconds: 10
max_backoff_seconds: 200
max_doublings: 0
原始 queue.xml
檔案:
<queue-entries>
<queue>
<name>fooqueue</name>
<rate>1/s</rate>
<retry-parameters>
<task-retry-limit>7</task-retry-limit>
<task-age-limit>2d</task-age-limit>
</retry-parameters>
</queue>
<queue>
<name>barqueue</name>
<rate>1/s</rate>
<retry-parameters>
<min-backoff-seconds>10</min-backoff-seconds>
<max-backoff-seconds>200</max-backoff-seconds>
<max-doublings>0</max-doublings>
</retry-parameters>
</queue>
<queue-entries>