AWS Lambda是一种无服务器的计算服务,可以在无需预置或管理服务器即可运行代码。
我们只需编写代码,并以zip文件或者容器镜像的形式上传到Lambda,即可用任何Web或移动应用程序或者AWS服务或者Saas应用触发。
今天通过阅读aws-lambda-go了解下Lambda是怎么把go的function的代码跑起来的。
入口在lambda/entry.go
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
举个例子
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代码
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方法
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方法
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逻辑就理清楚了。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
    





















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
