AWS Lambda for Go源码阅读:Lambda的调用逻辑 2021-07-08 Faas 暂无评论 2353 次阅读 AWS Lambda是一种无服务器的计算服务,可以在无需预置或管理服务器即可运行代码。 我们只需编写代码,并以zip文件或者容器镜像的形式上传到Lambda,即可用任何Web或移动应用程序或者AWS服务或者Saas应用触发。 今天通过阅读[aws-lambda-go](https://github.com/aws/aws-lambda-go/tree/159d1c69878562cd5409719d4fc4cfa298e2c4bf)了解下Lambda是怎么把go的function的代码跑起来的。 入口在`lambda/entry.go` ```golang func Start(handler interface{}) { StartWithContext(context.Background(), handler) } ``` `Start`方法有以下规则,handler就是我们定义的function: - handler必须是个function - handler支持0-2个参数 - 如果handler有两个参数,第一个一定是`context.Context` - hander会返回0-2个返回值 - 如果handler返回两个返回值,第二个一定是error - 如果handler只有一个返回值,那这个一定是error > 举个例子 > ```golang > func handleRequest(ctx context.Context, event events.SQSEvent) (string, error) { > ... > } > ``` 接下来看`StartWithContext`方法,里面调用了`StartHandlerWithContext` ```golang func StartWithContext(ctx context.Context, handler interface{}) { StartHandlerWithContext(ctx, NewHandler(handler)) } ``` 我们先看下`NewHandler`方法,有点长,简单讲就是做一些校验并通过handler创建一个基础的lambda function,后面会调用这个lambda function来真正调用到我们上传的function代码 ```golang 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`方法 ```golang 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`方法 ```golang 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`方法 ```golang 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 ```golang // 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逻辑就理清楚了。 打赏: 微信, 支付宝 标签: Lambda 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。本站myrat.top所有文章均为原创,转载请注明出处。