2017年8月2日 星期三

透過 AWS Lambda 開發 Serverless Framework(一):接收 Kinesis 訊息

AWS Lambda 是 AWS 發表並大力主打的 Serverless 服務之一。
在談怎麼使用 Lambda 之前,也許可以先談談為何要使用 Lambda?

為什麼需要 Lambda?

因為比較省錢(誤)。

在沒有 Lambda 以前,我們一般會直接開 EC2,並在 EC2 instance 裡面執行程式。
不過這時我們需要付出的成本,就是 EC2 instance 的執行費用,也就是每執行一小時就要付出一小時的執行費用。
接著會想到的,大概就是在 EC2 instance 裡面建立 container,也就是讓同一個 EC2 instance 跑多個程式
藉此希望可以更有效率地利用 EC2 instance 的資源。

不過想像一下,假設我的程式每 1 分鐘執行一次,每次執行費時 1 秒
那麼在 EC2 instance 裡,我跑這個程式,真正的使用效率就只有 1/60
也就是有 59 秒的 EC2 instance 執行時間是我需要付費、但是卻是買下來放著閒置的。

Lambda 就是這種問題的解決方案,它的本質實際上就是 container
運作時,我們不需要開 EC2 instance,而是直接上傳執行檔給 Lambda,Lambda 會幫我們執行。
而收費基準則是程式每次執行時「以秒計費」。
以上面的例子,一天有 1,440 分鐘,每分鐘我的程式會執行 1 秒
在 EC2 instance 的狀況下,我需要付費的時間是 1,440 分鐘的 EC2 執行費用
但在 Lambda 的狀況下,我需要付費的時間是實際執行的 1,440 秒的執行費用。

Lambda Handler

Handler 是負責處理事件的處理器,主要分成 input 和 output,也就是輸入的事件和輸出的結果。
詳細描述可以參考官方文件 [3]。
不過我自己使用時,目前還不太知道 output 何時會用到。

對於 input 的部份,AWS 的 Lambda 套件中,有一包套件有內建的 AWS Event [4]
因此這裡底下的範例是直接使用內建的 Event 去接 Kinesis。
不過其實就算自己寫也沒什麼就是了,畢竟實際上這只是 serialization 的事情。

要撰寫 Lambda Function 時,基本上必須實作 RequestHandler 這個介面
而實際主要要實作的,就是 handleRequest() 這個方法了。
外加上面提到的 input 和 output,對於一個 Lanbda Function 而言
真正要做的事情就是定義 input、output,以及實作如何處理 input 的程序。

使用 Lambda 接收 Kinesis 訊息的範例程式

範例程式如下:

package com.example.test.lambda_test;

import java.nio.charset.StandardCharsets;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.KinesisEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisEvent.KinesisEventRecord;

public class MyRequestHandler implements RequestHandler<KinesisEvent, Object> {
  private static final Logger log = 
      LoggerFactory.getLogger(MyRequestHandler.class);

  @Override
  public Object handleRequest(KinesisEvent input, Context context) {
    log.trace("Get {} messages.", input.getRecords().size());
    
    for (KinesisEventRecord record : input.getRecords()) {
      byte[] byteArray = record.getKinesis().getData().array();
      
      if (byteArray == null || byteArray.length < 1) {
        log.error("No content is found from event ID '{}'.", record.getEventID());
      }
      
      log.trace("Record #{}: {}", 
          record.getEventID(), 
          new String(byteArray, StandardCharsets.UTF_8));
    }
    return null;
  }
}

上述程式碼中,設定接收的事件是來自 Kinesis 的訊息,也就是當 Kinesis 有訊息送來時,就觸發 handleRequest() 方法。
而 handleRequest() 方法實際做的事情,就是把訊息的內容取出來、轉成字串、印到 log 上。

不過稍微需要注意的是,因為 Lambda 的 log 都是輸出到 CloudWatch,因此 log 的設定應該是要輸出到 Console
否則如果設定輸出成檔案之類的話,因為我們無法直接進入執行 Lambda 的 Container,就會因此看不到任何 Log。

在 AWS Console 建立 Lambda 函式

因為 AWS Console 不定時會變更樣式,所以這裡就不貼圖了~。
我上上週看到的介面,就跟今天看到的介面長得完全不一樣 @@。

不過流程大體就是先去 AWS Lambda 的介面,記得區域要選擇跟 Kinesis 相同區域
然後建立 Lambda Function 時,樣板(blueprint)基本上要挑自定義的樣版,因為內建樣版沒有 Java 用的
自定義樣版就可以自己設定 Lambda。

設定時,來源當然就選擇 Kinesis,然後它可以自動抓出這個區域的 Kinesis Stream,所以直接在列表裡選就好。
而上傳 Lambda 程式時,稍微需要注意的地方是要手動輸入 Handler,也就是當 Kinesis 有新訊息時,要觸發哪個 method。
以上面的範例來說,因為 package 是 com.example.test.lambda_test,因此輸入的 Handler 是 com.example.test.lambda_test.MyRequestHandler::handleRequest

建立完成以後,只要啟用 Trigger,然後等待 Kinesis 那邊有新訊息,就可以去 CloudWatch 那邊看 log 了。
PS. CloudWatch 的 log 會延遲幾分鐘左右,所以一開始在 CloudWatch 找不到 Log,就多等一會兒吧!

參考資料
  1. Programming Model for Authoring Lambda Functions in Java
  2. Creating a .jar Deployment Package Using Maven without any IDE (Java)
  3. Lambda Function Handler (Java)
  4. AWS Lambda Java Events Library

沒有留言: