本頁面說明如何從第一代遷移至第二代 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 與舊版套裝組合服務類似的產品。這些 Google Cloud 產品提供慣用的 Java 適用的 Cloud 用戶端程式庫。對於無法在Google Cloud中以獨立產品形式提供的套裝服務 (例如圖片處理、搜尋和訊息傳送),您可以使用第三方供應商或其他解決方法。
如要進一步瞭解如何遷移至非套裝組合服務,請參閱「從套裝組合服務遷移」。
執行階段遷移作業的方式會因您是否選擇使用舊版套裝服務而有所不同:
遷移至採用套裝組合服務的第二代 Java 執行階段 | 遷移至不含套裝組合服務的第二代 Java 執行階段 |
---|---|
使用 App Engine API JAR 存取套裝組合服務。 | 您也可以 使用建議的產品或第三方服務。 Google Cloud |
使用
視應用程式使用的功能而定,您可能還需要設定其他 YAML 檔案。 |
使用
視應用程式使用的功能而定,您可能還需要設定其他 YAML 檔案。 |
應用程式會透過 Jetty 部署。使用 WAR 格式封裝應用程式。 | 應用程式會透過您自己的伺服器部署。使用 JAR 格式封裝應用程式。如要進一步瞭解如何將現有的 WAR 檔案轉換為可執行的 JAR,請參閱「重新封裝 WAR 檔案」。 |
轉換程序總覽
以下列出您可能需要對現有的 App Engine Java 8 應用程式和部署程序進行的變更,才能使用第二代 Java 執行階段:
- 下載 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 會使用 Jetty 部署應用程式,這與 Java 8 執行階段相同。
第二代 Java 執行階段的記錄功能會遵循 Cloud Logging 的記錄標準。在第二代 Java 執行階段中,應用程式記錄不再與要求記錄綁在一起,而是分開儲存在不同的記錄中。如要進一步瞭解如何在第二代 Java 執行階段中讀取及寫入記錄,請參閱記錄指南。
如要在第二代 Java 執行階段中設定非執行緒安全的應用程式,請在
app.yaml
檔案中,或在使用舊版內含服務的情況下,在appengine-web.xml
檔案中,將並行作業的最大值設為 1。<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 存放區提供使用熱門 Java 網路架構的 hello world
範例:
將 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
物件建立 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
物件的 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
參考說明文件
使用包含物件清單的 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
物件的 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>