Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the api for wasm functions; 设计 FaaS 编程 API #611

Closed
Tracked by #776
zhenjunMa opened this issue May 26, 2022 · 17 comments
Closed
Tracked by #776

Update the api for wasm functions; 设计 FaaS 编程 API #611

zhenjunMa opened this issue May 26, 2022 · 17 comments
Assignees

Comments

@zhenjunMa
Copy link
Contributor

1. Background

At present, the API that Layotto interacts with wasm functions is a set of specifications defined by proxy-wasm. The official description of the project is shown in the following figure:

image

The project itself designed for network proxies, such as envoy, and the goal is to support extending the functionality of envoy through wasm.

If a function developed based on this API wants to receive an http request, it needs to implement the following three interfaces:

  1. OnHttpRequestHeaders
  2. OnHttpRequestBody
  3. OnHttpRequestTrailers

This obviously does not meet the "simple" principle.

2. What we need to do

Maybe we can redefine a set of ABIs suitable for functions. The development form can refer to proxy-wasm. For example, create a new repository function-wasm to save the definition, and then use function-wasm-xx to save the specific implementation.

For the definition of function interfaces, please refer to:

  1. Ali Cloud:https://help.aliyun.com/document_detail/74757.html
  2. AWS:https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html
  3. Google:https://cloud.google.com/functions/docs/functions-framework

It is not easy to define a set of APIs suitable for functions, but we do not need to be perfect at one time. What is more important is to start simple and follow the basic principle of "making functions simple enough for users".

中文

一、背景

目前 Layotto 跟 wasm 函数交互的 API 是使用 proxy-wasm 定义的一套规范,该项目的官方描述如下图所示:

image

它本身是面向网络代理设计的,目标是支持通过 wasm 扩展 envoy 的功能。

基于这套 API 开发的函数,如果想要接收一个 http request, 它需要实现如下三个接口:

  1. OnHttpRequestHeaders
  2. OnHttpRequestBody
  3. OnHttpRequestTrailers

这显然不符合“简单”的原则。

二、需要做什么

或许我们可以重新定义一套适用于函数的 ABI,开发形式可以参考 proxy-wasm, 比如新建一个仓库 function-wasm 来保存定义,然后通过 function-wasm-xx 来保存具体实现。

具体接口的定义可以参考:

  1. 阿里云文档:https://help.aliyun.com/document_detail/74757.html
  2. AWS文档:https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html
  3. Google文档:https://cloud.google.com/functions/docs/functions-framework

想要定义一套适用于函数的 API 本身并不容易,但我们不需要大而全,更重要的是从简单开始,遵守函数场景下的基本原则"让用户开发函数足够简单"。

@zhenjunMa zhenjunMa added help wanted Extra attention is needed wasm WebAssembly technology labels May 26, 2022
@kevinten10
Copy link
Member

I understand it like defining an adaptation layer to decouple business logic from wasm implementation (proxy-wasm). What might be done is:

  1. Define a simple interface, which is abstract
  2. Write business logic for this interface
  3. Adapt this business logic to the wasm implementation. If proxy-wasm is used, it needs to be adapted to the following three interfaces:
    1. OnHttpRequestHeaders
    2. OnHttpRequestBody
    3. OnHttpRequestTrailers

Maybe like spring-cloud-function, make faas functions written once and can run on different faas clouds:

  • Decouple the development lifecycle of business logic from any specific runtime goals, so that the same code can run as a web endpoint, stream processor, or task.
  • Support for a unified programming model across serverless providers, and the ability to run independently (on-premises or in a PaaS).

中文

我理解像是定义一个适配层接口,解耦业务逻辑与wasm实现(如proxy-wasm)。要做的可能是:

  1. 定义一个简单的接口,这个接口是抽象的(与proxy-wasm无关的)
  2. 针对这个接口书写业务逻辑
  3. 将这段业务逻辑适配到wasm实现上,如采用proxy-wasm的话,需要适配到以下三个接口上:
    1. OnHttpRequestHeaders
    2. OnHttpRequestBody
    3. OnHttpRequestTrailers

可能就像 spring-cloud-function 做的一样,使faas函数一次编写,可以运行在不同的faas云上:

  • 将业务逻辑的开发生命周期与任何特定的运行时目标分离,以便相同的代码可以作为Web端点、流处理器或任务运行。
  • 支持跨无服务器提供商的统一编程模型,以及独立运行(本地或在 PaaS 中)的能力。

@zhenjunMa
Copy link
Contributor Author

zhenjunMa commented Jun 6, 2022

Yes! I also prefer to keep using proxy-wasm if it can reduce our workload, the only thing I worried about is it's a little more complicated.

@seeflood seeflood added good first issue Good for newcomers kind/hard labels Jun 15, 2022
@seeflood seeflood changed the title Update the api for wasm functions Update the api for wasm functions; 设计 FaaS 编程 API Jun 15, 2022
@leemos-xx
Copy link
Member

hello,我继续尝试下跟进这个问题

@seeflood
Copy link
Member

@leemos-xx Ok. Thanks!

@leemos-xx
Copy link
Member

我上周比较忙没有跟进,这两天尝试理解了一下,有些疑问,麻烦有时间了帮忙解答下:

我大致看了下proxy-wasm/spec,这里面主要包括Host(proxy-wasm-go-host) + SDK(proxy-wasm-go-sdk)两部分声明,目前用户侧在开发wasm function的时候,是否主要就依赖SDK部分?比如wasm/filter.go中在接收到http请求后,在解析到对应部分时,分别会去调用:

  • ProxyOnRequestHeaders
  • ProxyOnRequestBody
  • ProxyOnRequestTrailers

最终会触发SDK中的方法:

  • OnHttpRequestHeaders
  • OnHttpRequestBody
  • OnHttpRequestTrailers

用户在处理HTTP请求时,需要同时实现三个接口,这也就是上面讨论时提到的使用proxy/wasm规范与用户易用性的冲突,所以需要一层适配层来解决易用性问题。

我参看了一下阿里云、腾讯云以及谷歌云中“云函数”相关的文档,其中用户在开发函数时需要关注的主要概念包括:

  • Context:该对象包含有关调用、服务、函数、链路追踪和执行环境等信息;
  • HTTP Handler:可以比较方便的处理HTTP请求,适用于Web服务的场景,其方法类似:HandleHttpRequest(ctx context.Context, w http.ResponseWriter, req *http.Request) error,用户实现该方法,在方法内部可以直接操作原始的reqresp
  • Event Handler:可以比较方便的处理Event,其方法类似:HandleRequest(ctx context.Context, event StructEvent),与HTTP Handler的主要区别在于其触发器不同,Event Handler可以通过定时任务、API网关、各种MQ来触发执行;

可以看到云厂商的“云函数”在对HTTP Handler的定义上比wasm sdk更简单,更符合用户的开发习惯。但由于我前期没有参与Faas相关的讨论,所以我担心我的理解有偏差,主要疑问在于:

  1. proxy-wasm的SDK提供的能力主要包含context lifecycle、configuration、L4、L7等。云厂商实现的“云函数”中,我看到的用户侧的接口主要包括HTTP Handler & Event Handler,其中HTTP Handler可以通过proxy-wasm的L7相关接口实现,而对于Event Handler,layotto中目前有适用场景吗?
  2. 在上面的讨论中,我看到较多都是关于HTTP Handler的讨论,是否有其它的API是在设计时需要考虑的?

@seeflood
Copy link
Member

seeflood commented Jul 4, 2022

收到, @zhenjunMa 马老师有空帮忙看看哈

@zhenjunMa
Copy link
Contributor Author

@leemos-xx
这个提案想要做的基本就是你说的这些。

  • 关于你说的Http跟Event,我们可以归类为函数的触发方式,也就是function trigger,一般来说可以有HTTP、RPC、消息等类型,这块我建议是从支持HTTP开始做,如果这种触发方式成熟了,那我们后面可以考虑增加更多的触发方式。

  • 这个API你是指的函数暴露给用户用于开发的API吗?一般来说我们认为函数的逻辑比较简单,也就是说比如只需要实现一个handle方法即可。但如果暴露一些提升用户开发体验的API那也是可以的,比如提供init跟destroy两个方法,一个用于函数加载完成时回调,一个用于函数销毁前回调,或许可以让用户执行一些初始化或销毁的操作

@leemos-xx
Copy link
Member

FaaS API

考虑函数逻辑相对简单,目前先提供init, destoryhandleHttpRequest三个方法。

1. init

在函数加载完成时调用一次,可以供用户完成一些初始化的操作。示例如下:

func init(context fc.Context) {
    context.GetDefaultLogger().Info("init...")
}

1.1 context

当函数初始化时,会将context传递到init方法中,context中可以包含一些如requestIdlogger等信息,后续可以视情况增加。

字段 说明
requestId 本次请求的唯一id,当出现问题时方便排查。
logger 日志对象,用于格式化输出日志,提供不同的日志级别。

2. handleHttpRequest

当接收到HTTP请求时,触发该方法,用户在该方法中实现具体的业务逻辑。示例如下:

func handleHttpRequest(context fc.Context, req fc.HttpRequestEntity, resp fc.HttpResponseEntity) {
    context.GetDefaultLogger().Debugf("receive header[Content-Type]: %s", req.getHeader(fc.ContentType));
    context.GetDefaultLogger().Debugf("receive body: %s", req.getBody());
    
    resp.setHeader(fc.ContentType, "text/plain");
    resp.setBody([]byte("<h1>Hello, world!</h1>"));
}

2.1 request

HTTP请求结构体。

字段 类型 说明
method string HTTP请求方法,如GET、POST、PUT、DELETE等。
path string HTTP请求路径。
headers fc.HttpHeaders 存放HTTP请求的请求头的键值对。
body fc.HttpBody HTTP请求体。

2.2 response

HTTP响应结构体。

字段 类型 说明
statusCode int HTTP响应状态码。
headers fc.HttpHeaders 存放HTTP请求的请求头的键值对。
body fc.HttpBody HTTP请求体。

3. destroy

在函数被销毁时调用一次,可以供用户完成一些资源回收的操作。示例如下:

func destroy(context fc.Context) {
    context.GetDefaultLogger().Info("destroy...")
}

@seeflood
Copy link
Member

seeflood commented Jul 9, 2022

@kevinten10 @zhenjunMa @rayowang 帮忙review 下?

@kevinten10
Copy link
Member

如果是GET请求,请求参数会放在path里面吗

@leemos-xx
Copy link
Member

我想在handleHttpRequest 方法中,用户操作的是fc.HttpRequestEntityfc.HttpResponseEntity,比如对fc.HttpRequestEntity而言,我们可以提供这些方法:

  • getScheme
  • getMethod
  • getPath
  • getQueryString
  • getHeader
  • getParameter
  • getCookie

getParameter方法实现会复杂一些,需要支持对queryString以及http body的解析,其中http body我们可以先支持formjson格式?

此外,对强类型的语言,是不是也可以考虑增加一些对原始参数进行类型转换的方法,方便用户使用,如:

  • getIntParameter 获取int类型数据
  • getFloatParameter 获取float类型数据
  • getBooleanParameter 获取boolean类型数据
  • getStringParameter 获取string类型数据
  • getParameterMap 获取请求参数中包含如user.name="lee"&user.age=18这类map的数据
  • 其它

@zhenjunMa
Copy link
Contributor Author

@leemos-xx
大体上跟我思路差不多,关于handleHttpRequest 这里抛出一个点讨论一下,就是函数是否要跟协议绑定。

  1. 如果绑定的话,按照上面的提案搞问题不大。不过这种方案下用户开发一个函数就是面向协议了,也就是处理HTTP请求要写一个函数,处理MQ请求要写另一个函数。
  2. 如果函数跟协议解耦,比如用户实现的是handleRequest,而访问函数的方式独立成触发器,比如可以是定时任务触发,可以是消息触发,也可以是请求触发(HTTP, gRPC等)。这用方式下,关于请求类型可以放到你提的context中,比如
字段 说明
requestId 本次请求的唯一id,当出现问题时方便排查。
logger 日志对象,用于格式化输出日志,提供不同的日志级别。
triggerType(requestType) 请求类型

不过这种方案最大的问题是函数的入参类型怎么定义,比如名字上可以把你的fc.HttpRequestEntity 改为fc.RequestEntity,更复杂的在于如何获取到里面的实际参数,这里可能还涉及到序列化之类的东西。

这里我只是做一个讨论,你可以参考一下阿里云,aws有没有这方面的设计,如果没有的话我们先只支持 http 协议访问函数也是可以的,你可以着手改一个目前的 FaaS Demo 试试

@kevinten10
Copy link
Member

从长远来看,如果两种都支持呢。

从使用方的角度,如果要写一些通用逻辑,可能倾向于使用handleRequest方式;如果要针对 RPC/MQ 逻辑,可能倾向于使用handleHttpRequest方式。


假如,如果假如未来要两种都支持。

是不是一开始往 handleRequest 方向做会好一些,将来可以拆成各类具体协议的函数。

如果一开始往handleHttpRequest这种绑定的方向做,可能会加很多case by case的特殊逻辑,以致于未来无法合并。

@zhenjunMa
Copy link
Contributor Author

@kevinten10 @leemos-xx

那比如现在阶段把函数实现跟触发器分开,比如函数实现handleRequest接口,入参是一个byte[]类型,然后触发器先只支持HTTP,这中间有一层转换逻辑,比如把上面提到的fc.HttpBody取出来作为byte[]类型传入?

总体来说我还是倾向于小步快跑,不用一次性搞的太复杂,一点点来好一点。

另外今天跟 @Mossaka 聊了一下,他推荐了一个 https://github.com/bytecodealliance/wit-bindgen 我们在Demo改造上可以参考一下这个项目看能不能用上,可以减少我们写转换层的工作量 我这周末也研究一下

@Xunzhuo
Copy link
Member

Xunzhuo commented Mar 6, 2023

/good-first-issue cancel
/help-wanted cancel

@github-actions github-actions bot removed good first issue Good for newcomers help wanted Extra attention is needed labels Mar 6, 2023
@github-actions
Copy link

github-actions bot commented Apr 6, 2023

This issue has been automatically marked as stale because it has not had recent activity in the last 30 days. It will be closed in the next 7 days unless it is tagged (pinned, good first issue or help wanted) or other activity occurs. Thank you for your contributions.

@github-actions github-actions bot added the stale label Apr 6, 2023
@github-actions
Copy link

This issue has been automatically closed because it has not had activity in the last 37 days. If this issue is still valid, please ping a maintainer and ask them to label it as pinned, good first issue or help wanted. Thank you for your contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants