1. 程式人生 > >在AWS lambda上執行用Java編寫的APLs

在AWS lambda上執行用Java編寫的APLs

從 Spring 和 Spring Boot 到 Jersey 到 Spark,Java 開發人員可以隨心選擇各種開放源來構建伺服器端 API。這些框架通常都在編譯包內嵌入了用來執行伺服器的 Servlet 容器引擎,例如 Tomcat。AWS LambdaAmazon API Gateway 在無伺服器環境中作為 HTTP 前端和計算平臺。今天,我們釋出了 aws-serverless-java-container 框架 1.0 版。使用無伺服器 Java 容器,可以方便地在 Java 中使用 Spring、Spring Boot、Jersey 或 Spark 等框架編寫應用程式,並且只需極少的程式碼修改即可在 AWS Lambda 內部執行。

無伺服器 Java 容器庫在 Lambda 執行時和您選擇的框架之間扮演代理的角色,好比一個 Servlet 引擎,將傳入的事件翻譯為框架可以理解的請求物件,然後將來自應用程式的應答轉換為 API Gateway 理解的格式。無伺服器 Java 容器庫現已在 Maven 上推出。我們針對不同的框架提供不同風格的庫:SpringSpring BootJerseySpark。在本博文中,我們將構建一個 Jersey 應用程式。該庫的其他實現也具有類似的結構,請參見 GitHub 上的快速入門指南

使用 Maven 原型

我們為所有支援的框架釋出了基本的 Maven 原型。如要執行此教程,您需要在本地計算機上安裝

Apache Maven。使用任何終端開啟工作區目錄,然後執行 Maven 命令以利用原型生成新的專案。請使用您需要的設定代替 groupId 和 artifactId 設定:

$ cd myworkspace
$ mvn archetype:generate -DgroupId=my.service -DartifactId=jersey-sample -Dversion=1.0-SNAPSHOT \
      -DarchetypeGroupId=com.amazonaws.serverless.archetypes \
	  -DarchetypeArtifactId=aws-serverless-jersey-archetype \
	  -DarchetypeVersion=1.0.1 -Dinteractive=false

mvn 客戶段將要求您確認引數,然後生成專案結構。在此例中,我們使用 aws-serverless-jersey-archetype – 我們為 spring、springboot 和 spark 準備了類似的工件。下面我們詳細介紹生成的程式碼結構。如果您僅僅需要執行命令並測試您的基本應用程式,請直接跳轉至本地測試部分。

Jersey 應用程式

使用您選擇的 IDE 開啟原型專案。Jersey 原型中包含的簡單應用程式定義了一個 /ping 路徑,將會返回一條 JSON hello world 訊息。在程式碼包中,以我的例子為例,在 my.service下,有一個 resource 包,它採用 PingResource 類。Ping 類採用 JAX-RS’ @Path 註解,它定義了單一的 @GET 方法。

@Path("/ping")
public class PingResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.WILDCARD)
    public Response createPet() {
		// return a basic map. This will be marshalled automatically into a 
		// json object with a single property
        Map<String, String> pong = new HashMap<>();
        pong.put("pong", "Hello, World!");
        return Response.status(200).entity(pong).build();
    }
}

Lambda 控制代碼

在我們應用程式的主包中,原型還會生成一個 StreamLambdaHandler 類。下面我們介紹該類中的程式碼:

public class StreamLambdaHandler implements RequestStreamHandler

我們的類會實現 Lambda 的 RequestStreamHandler 介面。該類是 AWS Lambda 在我們應用程式中的主要入口:Lambda 行話裡的“控制代碼”。我們使用流控制代碼而不是基於 POJO 的控制代碼,因為我們的事件模型需要利用註解來編組和解組,但 Lambda 的內嵌序列化器部支援註解。


private static final ResourceConfig jerseyApplication = new ResourceConfig()
                                                            .packages("com.sapessi.jersey.resource")
                                                            .register(JacksonFeature.class);

首先,我們會宣告一個靜態的 ResourceConfig物件,這是 Jersey Application 實現物件。我們配置此物件以掃描有註解的類的 resource 包,然後載入 JacksonFeature 類以處理 JSON 內容型別。

private static final JerseyLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler
            = JerseyLambdaContainerHandler.getAwsProxyHandler(jerseyApplication);

然後,我們宣告 JerseyLambdaContainerHandler 物件的第二個靜態例項。我們將使用 getAwsProxyHandler 靜態方法和 ResourceConfig 物件,將此物件初始化。 getAwsProxyHandler 方法會自動建立一個配置處理 API Gateway 代理整合事件的庫例項。您可以建立 RequestReader 以及 ResponseWriter 物件的自定義實現,以支援自定義事件型別。您將注意到這兩個變數都被宣告為靜態類成員。它們之所以是類成員,是因為我們只需要這些物件的單一例項。AWS Lambda 嘗試在不同的呼叫之間重複使用容器。我們控制代碼類由執行時作為單例模式持有,每次都會呼叫 handleRequest 方法。我們可以重複使用 ResourceConfigJerseyLambdaContainerHandler。靜態變數將在 Lambda 啟動它時由 Java 執行時例項化;從而提高了繁重內省操作的效能。

public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
            throws IOException {
    handler.proxyStream(inputStream, outputStream, context);

控制代碼方法的實現時 Lambda 的主要切入點。在控制代碼方法內部,我們會單次呼叫容器控制代碼的 proxyStream 方法。 proxyStream 方法將負責讀取我們的輸入流並利用其資料建立 HttpServletRequest 。應用程式生成的 HttpServletResponse 將按照 Amazon API Gateway 要求的格式自動寫入輸入流。

專案根

在專案根中,原型會生成三個檔案: pom.xmlsam.yamlREADME.md。pom 檔案宣告專案並定義 Maven 依存關係。它包含 serverless-java-container 庫。

<dependency>
    <groupId>com.amazonaws.serverless</groupId>
   <artifactId>aws-serverless-java-container-jersey</artifactId>
   <version>1.0</version>
</dependency>

pom 檔案還使用 Maven Shade 外掛來生成一個可以載入到 AWS Lambda 的“uber-jar”。請參閱 <build> 部分。 sam.yaml 檔案是一種無伺服器應用程式模型 (SAM) 模板,我們可以用它將應用程式部署到 AWS 或在本地使用 SAM Local 進行測試。SAM 是對 CloudFormation 的進一步抽象,從而可以更方便地定義程式碼中的無伺服器堆疊。SAM 檔案定義了我們單一資源 AWS::Serverless::Function。該函式配置使用編譯程序生成的“uber-jar”並指向我們的控制代碼類。API 前端在函式資源中的 Events 部分定義。在部署模板時將會隱含建立 API Gateway RestApiStageDeploymentREADME.md 檔案包含了所生成的應用程式構建、部署和測試指令集。

在本地測試應用程式

您可以使用 AWS SAM Local 在本地計算機上啟動服務。如要啟動 SAM Local,您需要安裝並執行 Docker (社群版或企業版)。首先安裝 SAM Local (如果尚未安裝):

$ npm install -g aws-sam-local

然後使用一臺終端,開啟專案根資料夾並構建 jar 檔案。

$ cd myworkspace/jersey-sample
$ mvn clean package

仍在 sam.yaml 檔案所在的專案根資料夾中 — 使用 SAM Local CLI 啟動 API。

$ sam local start-api --template sam.yaml

...
Mounting com.sapessi.jersey.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH]
...

現在我們已經啟動並運行了 API Gateway 和 Lambda 的本地模擬器。使用新的殼,您可以向 API 傳送測試 Ping 請求:

$ curl -s http://127.0.0.1:3000/ping | python -m json.tool

{
    "pong": "Hello, World!"
}

部署到 AWS

您可以使用 AWS CLI 快速將應用程式部署到 AWS Lambda 和 Amazon API Gateway。您將需要 S3 儲存桶來儲存需要部署的工件。建立了 S3 儲存桶後,從檔案 sam.yaml 所在的專案根資料夾執行如下命令:

$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket <YOUR S3 BUCKET NAME>
Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx  6464692 / 6464692.0  (100.00%)
Successfully packaged artifacts and wrote output template to file output-sam.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name <YOUR STACK NAME>

如命令的輸出所顯示,您現在可以使用 CLI 來部署應用程式。選擇堆疊名稱並從包命令的輸出中執行 aws cloudformation deploy 命令。

$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessJerseyApi --capabilities CAPABILITY_IAM

應用程式部署完成後,您可以描述堆疊以顯示 API 終端節點已經建立。終端節點應當是 OutputsServerlessJerseyApikey 屬性:

$ aws cloudformation describe-stacks --stack-name ServerlessJerseyApi --query 'Stacks[0].Outputs[*].{Service:OutputKey,Endpoint:OutputValue}'
[
    {
		"Service": "JerseySampleApi",
		"Endpoint": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping"
    }
]

OutputValue 複製到瀏覽器中,或者使用 curl 來測試您的第一個請求:

$ curl -s https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping | python -m json.tool

{
    "pong": "Hello, World!"
}

冷啟動說明

Java 是一種大型執行時;因此有必要在此包含有關冷啟動的內容。冷啟動是在 AWS Lambda 需要啟動基礎設施、啟動執行時以及啟動程式碼時第一次呼叫您的 Lambda 函式。多個因素可能會影響函式首次啟動的速度:

  • 記憶體和 CPU 分配:AWS Lambda 將一定比例的 CPU 週期分配給您分配到函式的記憶體。生成的 SAM 模板預設使用 512MB 記憶體。如果您的程式碼為 CPU 密集型程式碼,請增加此引數以提高效能。
  • 程式碼包大小:每當 Lambda 函式首次啟動時,它需要下載並解壓程式碼包。“uber-jar”的大小十分重要。在匯入依存關係時要特別注意,請使用 Maven Shade 外掛來剔除不必要的臨時性依存關係。例如,在此庫的 Spark 實現中,我們刪除了嵌入的 Jetty 容器
  • 程式碼生命週期:在第一次啟動函式時,AWS Lambda 會建立一個控制代碼物件例項,並且將在未來呼叫時以單例模式重複使用,直接針對您的 handleRequest 方法。這意味著您可以使用類成員來快取您希望在不同的呼叫中重複使用的元資料、物件以及連線。不要快取保密資料:無法保證 Lambda 例項將在有一天被任何人使用,並且有人在某一天會不可避免地忘記清除呼叫之間快取的資料。
  • 框架有不同的功能和效能特徵。Spring 和 Spring Boot 在注入依存關係以及自動連線應用程式方面的功能極其強大。但這會讓您喪失冷啟動時間的靈活性 — 反射十分緩慢。Jersey 僅執行少量的反射以查詢其提供商和資源。Spark 中的一切都是“靜態連結”的,是目前啟動最快的框架。

由於所有這些引數的原因,我們要如何選擇正確的框架?如果您沒有嚴格的延遲要求,請選擇自己最滿意的框架。在實踐中利用生產流量,您將發現冷啟動僅影響 1% (如果不是 0.1%) 的指標。

結論

利用無伺服器 Java 容器,可以方便地使用任意 Java 框架建立可擴充套件的 API。在本博中,我使用 Jersey,該庫還提供 SpringSpring BootSpark 原型。使用原型建立的專案預封裝了一個執行的 Lambda 控制代碼、一個示例 /ping 資源以及一個便於您快速在本地測試應用程式並將其部署到 AWS 的 SAM 模板。如果您遇到有關無伺服器 Java 容器庫的問題,請在我們的 GitHub 儲存庫報告。有關 AWS LambdaAmazon API Gateway 的更多資訊,請使用 AWS 論壇。

相關推薦

AWS lambda執行Java編寫APLs

從 Spring 和 Spring Boot 到 Jersey 到 Spark,Java 開發人員可以隨心選擇各種開放源來構建伺服器端 API。這些框架通常都在編譯包內嵌入了用來執行伺服器的 Servlet 容器引擎,例如 Tomcat。AWS Lambda 和 Amazon API Gat

java編寫spark程式,簡單示例及執行

最近因為工作需要,研究了下spark,因為scala還不熟,所以先學習了java的spark程式寫法,下面是我的簡單測試程式的程式碼,大部分函式的用法已在註釋裡面註明。 我的環境:hadoop 2.2.0                    spark-0.9.0  

JAVA編寫瀏覽器內核之實現javascript的document對象與內置方法

public urn cti cli 原理 null 編寫 代碼塊 頁面 原創文章。轉載請註明。 閱讀本文之前,您須要對瀏覽器怎樣載入javascript有一定了解。當然,對java與javascript本身也須要了解。 本文首先介紹瀏覽器載入並執行javasc

Java編寫銀行存錢取錢

con 輸入密碼 lse 正在 組成 require money one amp const readline = require(‘readline-sync‘)//引用readline-sync let s = 2;//錯誤的次數 for (let i = 0; i

Java編寫的http下載工具類,包含下載進度回調

listener layout output @override extends zh-cn st2 NPU .info HttpDownloader.java package com.buyishi; import java.io.FileOutputStream;

java 編寫 Hello World 程式

一、安裝 JDK 可在360軟體管家內下載並安裝 安裝如下圖: 設定安裝目錄 等待進度條完成: 二、eclipse下載與安裝 下載連結為:https://www.eclipse.org/ 下載安裝包: 點選 Eclipse

輸出100到1000以內的迴文素數,JAVA編寫

老師的要求是:使用JAVA語言程式設計輸出100到999的所有迴文素數。 落實到實際編寫上,意思也就是找出100-1000以內的所有迴文素數並顯示到螢幕上。 先上程式碼: public class te

實戰案例-- Java編寫基礎小程式

如果是剛接觸或者剛學習Java,練習一些基礎的演算法還是必須的,可以提升思維和語法的使用。 1、輸出兩個int數中的最大值 import java.util.Scanner; public class demo { public static void mai

Java編寫基礎小程式&&經典案例

     如果是剛接觸或者剛學習java,練習一些基礎的演算法還是必須的,可以提升思維和語法的使用。 1、輸出兩個int數中的最大值 import java.util.Scanner; publi

Appium+java+Android二(uiautomatorviewer定位手機頁面元素+Java編寫自動化測試例)

uiautomatorviewer定位手機頁面元素+編寫自動化測試用例 如何安裝及搭建appium的環境請參考我的上篇部落格appium+java+Android環境搭建 uiautomatorviewer工具是用來給手機頁面元素定位的,所以在使用uiautomatorviewer之前,

12個Java編寫基礎小程式&經典案例(收藏篇)

如果是剛接觸或者剛學習java,練習一些基礎的演算法還是必須的,可以提升思維和語法的使用。 1、輸出兩個int數中的最大值 import java.util.Scanner; public class demo { public static void main(String[] arg

JAVA編寫漢諾塔程式

漢諾塔問題:三根堅柱和一組中間有洞能在柱子上滑動的盤子,每個盤子有不同的直徑。初始時,所有的盤子按照大小依次堆放在一個柱子上,最大的盤子在最下面。 目標:將所有的盤子從初始的第一根柱子移動到第三根柱子

java編寫一個記事本的心得

專案:<記事本> 要求:1.實現記事本的基本介面2.實現記事本的基本功能   2.1 基本功能: (1)新建檔案 (2)開啟檔案  (3)儲存檔案 (4)退出檔案 (5)複製、貼上、剪下 (6)設定字型、自動換行 心得:1.搭建好記事本的基本框架,記事本基本

java編寫棧的經典應用-表示式求值

     表示式求值是程式設計程式設計中的基本問題也是棧的經典應用,這裡使用的是書上的方法,也是最廣為流傳的方法“算符優先法”      所謂算符優先就是算術運算中不同運算子有不同的計算優先順序,所以需要使用一個算符優先表來確定計算順序。下面程式碼中有算符優先表,這裡就不寫

Java編寫你自己的簡單HTTP伺服器

HTTP是個大協議,完整功能的HTTP伺服器必須響應資源請求,將URL轉換為本地系統的資源名。響應各種形式的HTTP請求(GET、POST等)。處理不存在的檔案請求,返回各種形式的狀態碼,解析MIME型別等。但許多特定功能的HTTP伺服器並不需要所有這些功能。例如,很多網站只

JAVA 編寫程式從鍵盤讀入10個整數存入整型陣列a中

package javaTest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Read {  pub

JAVA編寫MD5大寫32位加密

用JAVA編寫的MD5加密技術,大寫的32位加密 package test; import java.io.UnsupportedEncodingException; public class MD5Test { /* * 下面這些S11-S44實際上是一個4

java編寫在1,2,…,9(順序不能變)數字之間插入+或-或什麼都不插入,使得計算結果總是100的程式,並輸出所有的可能性。例如:1 + 2 + 34 – 5 + 67 – 8 + 9 = 100

今天看到一個題目,編寫一個在1,2,…,9(順序不能變)數字之間插入+或-或什麼都不插入,使得計算結果總是100的程式,並輸出所有的可能性。例如:1 + 2 + 34 – 5 + 67 – 8 + 9 = 100。 剛開始看到題目的時候一籌莫展,但是題目下一條

怎樣Java編寫一個簡單的計算器

我是自己純手工用Java編寫的計算器 //filename:JiShuan import java.awt.Color; import java.awt.EventQueue; import java.awt.Image; import javax.swing.JBu

Java編寫九九乘法表

1*1=1 2*1=2  3*1=3  4*1=4  5*1=5  6*1=6  7*1=7  8*1=8  9*1=91*2=22*2=4 3*2=64*2=8 5*2=106*2=12 7*2=148*2=16 9*2=181*3=32*3=6 3*3=94*3=12 5*3=156*3=18 7*3=2