AWS Lambda for Go源碼閱讀:Lambda的調用邏輯

我的博客html

AWS Lambda是一種無服務器的計算服務,能夠在無需預置或管理服務器便可運行代碼。 咱們只需編寫代碼,並以zip文件或者容器鏡像的形式上傳到Lambda,便可用任何Web或移動應用程序或者AWS服務或者Saas應用觸發。git

今天經過閱讀aws-lambda-go瞭解下Lambda是怎麼把go的function的代碼跑起來的。github

入口在lambda/entry.gogolang

func Start(handler interface{}) {
	StartWithContext(context.Background(), handler)
}
複製代碼

Start方法有如下規則,handler就是咱們定義的function:json

  • handler必須是個function
  • handler支持0-2個參數
  • 若是handler有兩個參數,第一個必定是context.Context
  • hander會返回0-2個返回值
  • 若是handler返回兩個返回值,第二個必定是error
  • 若是handler只有一個返回值,那這個必定是error

舉個例子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,再去調用FunctionInvoke方法

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,咱們看裏面實現了的lambdaHandlerInvoke, 這邊去調用了最開始的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邏輯就理清楚了。

相關文章
相關標籤/搜索