2017年11月24日 星期五

AWS Lambda 呼叫另一個 AWS Lambda

一般來說,Lambda 都是透過中介服務去觸發的
例如透過 DynamoDB Stream、SNS、Kinesis、S3 等等的
當這些服務發生變更時,發送訊息觸發 Lambda。

不過實務上,Lambda 本身只要具備 Invoke 權限,就能夠直接透過 AWS 的 Lambda API 去觸發另一個 Lambda。

範例

在下面這個範例中,是實驗的是呼叫自己,並且把呼叫的 counter 增加,直到呼叫自己的次數超過 3 次時就停止。

Maven 設定檔

Maven 的設定中,最重要的地方在於要加入 Lambda SDK。
這裡用的 AWS SDK 版本是 1.11.202,不過其實現在還有更新的版本了。

<properties>
	<aws.version>1.11.202</aws.version>
</properties>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>com.amazonaws</groupId>
			<artifactId>aws-java-sdk-bom</artifactId>
			<version>${aws.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
</dependencyManagement>

<dependencies>
	<dependency>
		<groupId>commons-io</groupId>
		<artifactId>commons-io</artifactId>
		<version>2.5</version>
	</dependency>
	<dependency>
		<groupId>com.google.code.gson</groupId>
		<artifactId>gson</artifactId>
		<version>2.6.2</version>
	</dependency>
	<!-- AWS -->
	<dependency>
		<groupId>com.amazonaws</groupId>
		<artifactId>aws-java-sdk-lambda</artifactId>
	</dependency>
	<dependency>
		<groupId>com.amazonaws</groupId>
		<artifactId>aws-lambda-java-core</artifactId>
		<version>1.1.0</version>
	</dependency>
</dependencies>

稍微複習 (?) 一下,Maven 中的「aws-lambda-java-core」是 Lambda 的套件,讓我們可以實作 RequestHandler
而「aws-java-sdk-lambda」則是 AWS 的 Lambda SDK,是為了操作 AWS 的 Lambda API(也就是用來管理 AWS Lambda 的 API)。

「commons-io」和「gson」則是下面程式碼會用到的東西,分別是讀取 Lambda 輸入的串流以及把他們解析成 JSON 物件。

程式碼

public class EventHandler implements RequestStreamHandler {
  private static final Logger LOGGER = 
      LoggerFactory.getLogger(EventHandler.class);

  @Override
  public void handleRequest(
      InputStream input, OutputStream output, Context context) 
          throws IOException {
    try {
      String str = IOUtils.toString(input, StandardCharsets.UTF_8);
      LOGGER.trace("Source message: {}", str);
      
      handleRequest(
          new JsonParser().parse(str).getAsJsonObject(),
          context);
    } catch (Throwable t) {
      LOGGER.error("Error when handling a request.", t);
    }
  }
  
  protected void handleRequest (JsonObject json, Context context) {
    int count = (json.has("count")) ? json.get("count").getAsInt() : 1;
    
    // Terminate the self-triggering if it triggers 3 times.
    if (count < 3) {
      LOGGER.debug("Attempt to trigger itself ‘{}’: {}", 
          context.getFunctionName(), count);
      AWSLambdaAsync client = AWSLambdaAsyncClientBuilder.defaultClient();
      
      JsonObject eventContent = new JsonObject();
      eventContent.addProperty("count", ++count);
      
      InvokeRequest request = new InvokeRequest()
          .withFunctionName(context.getFunctionName())
          .withInvocationType(InvocationType.Event)
          .withPayload(eventContent.toString());
      InvokeResult result = client.invoke(request);
      
      LOGGER.debug("Invoke result status: {}", result.getStatusCode());
    }
  }
}

上述程式碼中,主要分為兩個部份:

  1. #5 ~ #19 是實作接收事件的介面,會把接收到的事件內容轉換成 JSON 格式。
  2. #21 ~ #41 是解析事件的內容,並且決定是否要再次呼叫自己。

而真正呼叫自己的部份,就是 #28 ~ #37 行。

#28 行先建立一個非同步的 Lambda Client,非同步的意思是,呼叫 AWS Lambda API 時,會馬上得到一個具有 Future 特性的結果
可以不用實際地等待 AWS Lambda API 的回應。

#33 ~ #37 是決定要如何呼叫 Lambda 的方法。
具體來說,因為是要呼叫自己,因此設定了 withFunctionName() 是指定自己的函式名字。
而呼叫時,我想要射後不理,亦即我不要等待呼叫的 Lambda 告訴我結果,我只想要丟個東西給它,然後它就自己去跑自己的。
因此這裡設定 InvocationType 是 Event~這就是屬於射後不理的類型。
如果想要的是呼叫後還要取得 Lambda 執行後回傳的結果,那就應該要用 RequestResponse 這個類型。
(但這時要記得 Lambda 的執行時間最長只能 5 分鐘,等待別的 Lambda 執行時,自己的 5 分鐘血條依然在扣血~)
最後,因為要告訴下一個自己一些訊息,因此要設定 Payload。

執行結果

執行結果在 CloudWatch 上,大概會跑出類似這樣的 log。

START RequestId: a4d562af-d0e7-11e7-b8c8-b3b93b225046 Version: $LATEST
19.345 | EventHandler | Source message: {"key3": "value3","key2": "value2","key1": "value1"}
19.543 | EventHandler | Attempt to trigger itself ‘my_self_triggered_function’: 1
38.637 | EventHandler | Invoke result status: 200
END RequestId: a4d562af-d0e7-11e7-b8c8-b3b93b225046
REPORT RequestId: a4d562af-d0e7-11e7-b8c8-b3b93b225046 Duration: 19306.95 ms Billed Duration: 19400 ms Memory Size: 512 MB Max Memory Used: 107 MB 

START RequestId: aad79f94-d0e7-11e7-8e7b-91c27f1bebea Version: $LATEST
28.727 | EventHandler | Source message: {"count": 2}
28.907 | EventHandler | Attempt to trigger itself ‘my_self_triggered_function’: 2
END RequestId: aad79f94-d0e7-11e7-8e7b-91c27f1bebea
REPORT RequestId: aad79f94-d0e7-11e7-8e7b-91c27f1bebea Duration: 9813.51 ms Billed Duration: 9900 ms Memory Size: 512 MB Max Memory Used: 86 MB 

START RequestId: b0879db9-d0e7-11e7-9e0f-9d2befc8992a Version: $LATEST
38.239 | EventHandler | Source message: {"count": 3}
END RequestId: b0879db9-d0e7-11e7-9e0f-9d2befc8992a
REPORT RequestId: b0879db9-d0e7-11e7-9e0f-9d2befc8992a Duration: 228.63 ms Billed Duration: 300 ms Memory Size: 512 MB Max Memory Used: 37 MB 
參考資料
  1. Invoke

沒有留言: