我的博客html
AWS Lambda是一種無服務器的計算服務,能夠在無需預置或管理服務器便可運行代碼。 咱們只需編寫代碼,並以zip文件或者容器鏡像的形式上傳到Lambda,便可用任何Web或移動應用程序或者AWS服務或者Saas應用觸發。git
今天經過閱讀aws-lambda-go瞭解下Lambda是怎麼把go的function的代碼跑起來的。github
入口在lambda/entry.go
golang
func Start(handler interface{}) {
StartWithContext(context.Background(), handler)
}
複製代碼
Start
方法有如下規則,handler就是咱們定義的function:json
context.Context
舉個例子api
func handleRequest(ctx context.Context, event events.SQSEvent) (string, error) { ... } 複製代碼
接下來看StartWithContext
方法,裏面調用了StartHandlerWithContext
服務器
func StartWithContext(ctx context.Context, handler interface{}) {
StartHandlerWithContext(ctx, NewHandler(handler))
}
複製代碼
咱們先看下NewHandler
方法,有點長,簡單講就是作一些校驗並經過handler建立一個基礎的lambda function,後面會調用這個lambda function來真正調用到咱們上傳的function代碼markdown
func NewHandler(handlerFunc interface{}) Handler {
// 前面是一些校驗
if handlerFunc == nil {
return errorHandler(fmt.Errorf("handler is nil"))
}
handler := reflect.ValueOf(handlerFunc)
handlerType := reflect.TypeOf(handlerFunc)
if handlerType.Kind() != reflect.Func {
return errorHandler(fmt.Errorf("handler kind %s is not %s", handlerType.Kind(), reflect.Func))
}
// validateArguments校驗handler的入參
// 當入參大於2個,或者入參爲2個但第一個不是context.Context的狀況,返回false,error
// 當入參爲1個,但不是context.Context類型時,返回false,nil
takesContext, err := validateArguments(handlerType)
if err != nil {
return errorHandler(err)
}
// 校驗返回值
if err := validateReturns(handlerType); err != nil {
return errorHandler(err)
}
//返回一個基礎的lambda function
return lambdaHandler(func(ctx context.Context, payload []byte) (interface{}, error) {
trace := handlertrace.FromContext(ctx)
// construct arguments
var args []reflect.Value
if takesContext {
// 第一個入參爲context.Context,加到args裏
args = append(args, reflect.ValueOf(ctx))
}
// 若是handler參數只有一個且不是context.Context 或者 入參爲2個, 把參數裏的event解析並放入args
if (handlerType.NumIn() == 1 && !takesContext) || handlerType.NumIn() == 2 {
eventType := handlerType.In(handlerType.NumIn() - 1)
event := reflect.New(eventType)
if err := json.Unmarshal(payload, event.Interface()); err != nil {
return nil, err
}
if nil != trace.RequestEvent {
trace.RequestEvent(ctx, event.Elem().Interface())
}
args = append(args, event.Elem())
}
// 將參數傳入handler並調用
response := handler.Call(args)
// convert return values into (interface{}, error)
var err error
if len(response) > 0 {
if errVal, ok := response[len(response)-1].Interface().(error); ok {
err = errVal
}
}
var val interface{}
if len(response) > 1 {
val = response[0].Interface()
if nil != trace.ResponseEvent {
trace.ResponseEvent(ctx, val)
}
}
return val, err
})
}
複製代碼
再看StartHandlerWithContext
,其中去調用了startRuntimeAPILoop
方法app
func StartHandlerWithContext(ctx context.Context, handler Handler) {
var keys []string
for _, start := range startFunctions {
// strart.env是個字符串"AWS_LAMBDA_RUNTIME_API"
config := os.Getenv(start.env)
if config != "" {
// in normal operation, the start function never returns
// if it does, exit!, this triggers a restart of the lambda function
// start.f是個func,lambda/invoke_loop.go:func startRuntimeAPILoop(ctx context.Context, api string, handler Handler) error
err := start.f(ctx, config, handler)
logFatalf("%v", err)
}
keys = append(keys, start.env)
}
logFatalf("expected AWS Lambda environment variables %s are not defined", keys)
}
複製代碼
接下來看startRuntimeAPILoop
方法,主要是等待觸發器的event請求,再去調用handleInvoke
方法oop
func startRuntimeAPILoop(ctx context.Context, api string, handler Handler) error {
// 這個api就是剛剛的環境變量AWS_LAMBDA_RUNTIME_API,在這邊建立了個http的client,url是/runtime/invocation/
client := newRuntimeAPIClient(api)
// Function是個封裝了handler的結構體,定義了Ping、Invoke等方法
function := NewFunction(handler).withContext(ctx)
for {
// next方法是在剛剛的url後面加了"next",即"/runtime/invocation/next",這邊會去發起這個http請求調用,這個api是用來等待一個新的invoke請求, 反回了一個invoke的結構體,裏面有觸發器事件的[]byte
invoke, err := client.next()
if err != nil {
return err
}
err = handleInvoke(invoke, function)
if err != nil {
return err
}
}
}
複製代碼
再詳細看下handleInvoke
方法,主要是將invoke
轉爲InvokeRequest
,再去調用Function
的Invoke
方法
func handleInvoke(invoke *invoke, function *Function) error {
// 將invoke轉成了*messages.InvokeRequest格式, 裏面有剛剛請求header裏的Lambda-Runtime-Cognito-Identity、Lambda-Runtime-Trace-Id、Lambda-Runtime-Invoked-Function-Arn、之類的東西,見[AWS Lambda Runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html)
functionRequest, err := convertInvokeRequest(invoke)
if err != nil {
return fmt.Errorf("unexpected error occurred when parsing the invoke: %v", err)
}
functionResponse := &messages.InvokeResponse{}
// 這邊傳入請求去調Function的Invoke方法, Invoke方法裏主要是ctx操做,以及最終真正去調用的handler的Invoke方法
if err := function.Invoke(functionRequest, functionResponse); err != nil {
return fmt.Errorf("unexpected error occurred when invoking the handler: %v", err)
}
if functionResponse.Error != nil {
payload := safeMarshal(functionResponse.Error)
if err := invoke.failure(payload, contentTypeJSON); err != nil {
return fmt.Errorf("unexpected error occurred when sending the function error to the API: %v", err)
}
if functionResponse.Error.ShouldExit {
return fmt.Errorf("calling the handler function resulted in a panic, the process should exit")
}
return nil
}
// 將function的執行結果經過/runtime/invocation/response返回給調用者
if err := invoke.success(functionResponse.Payload, contentTypeJSON); err != nil {
return fmt.Errorf("unexpected error occurred when sending the function functionResponse to the API: %v", err)
}
return nil
}
複製代碼
咱們直接看handler的Invoke
方法, Handler
是個interface,咱們看裏面實現了的lambdaHandler
的Invoke
, 這邊去調用了最開始的handler,並序列化response
// lambda/handler.go
func (handler lambdaHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) {
// handler就是上文中NewHandler返回的基礎的lambda function, payload就是以前觸發器的event事件
response, err := handler(ctx, payload)
if err != nil {
return nil, err
}
responseBytes, err := json.Marshal(response)
if err != nil {
return nil, err
}
return responseBytes, nil
}
複製代碼
至此, AWS Lambda的調用function邏輯就理清楚了。