FaaS应用开发
如何提取公共的服务能力?
开发微服务的时候,如果是一个复杂一点的功能,则需要多人协作或者复用一些别人开发好的类库。那么,函数计算中是否也有类似的特性呢?层的提出就是为了解决这个问题。你可以将函数依赖的公共库提炼到层,以减少部署、更新时的代码包体积。
大部分的云厂商都支持各种语言的层,如 Java、Python、Node.js、PHP 等。对于支持层功能的运行时,函数计算会将特定的目录(如 /opt)添加到运行时语言的依赖包搜索路径中。你可以自定义层或者使用云厂商开发好的公共层,这些公共层包含了一些常用的依赖包。对于自定义的层,你通常需要将所有内容打包到一个 ZIP 文件包中,并上传到函数计算平台。函数计算运行时会将层的内容解压并部署在特定的目录,如 /opt 目录下。
使用了层的功能之后,有这样 3 个好处:
函数程序包更小,只关注核心代码,使开发变得更加轻松方便;
使用层也可以避免在制作函数 zip 包和依赖项过程中出现未知的错误;
由于可以在多个函数中引入使用,可以减少不必要的存储资源浪费。
如何快速地开发和交付?
函数计算平台会支持用户以自定义镜像的方式上传应用来运行。
在开发函数具体的逻辑之前,一般还需要确认开发的是事件函数还是 HTTP 函数,通常来说,平台服务后端对这两种调用方式实现的逻辑不一样。
第一,在创建函数之前,需要基于自身业务来实现一个 HTTP Server,并且将其打包成镜像,上传到镜像仓库,供函数计算使用。
各个云厂商的实现方式不同,主要体现在请求和响应接口上的协议适配以及服务运行方式上。针对 HTTP Server 的实现,这里需要强调 4 个点:
建议基于 HTTP 和事件的触发方式,实现相应的接口功能;
需要根据接口协议规范,实现请求 Header 和 Body 中值的解析和处理;
上传镜像时,需要指定容器的启动命令 Command、参数 Args 及监听端口 Port;
推荐实现一个 Healthz 函数,用于健康监测。
第二,创建一个函数,此时,就只需要设置函数运行相关的一些基本属性,如运行超时时间、内存大小、并发度等,再关联第一步中构建的镜像即可。
第三,根据自身的业务场景,选择合适的触发方式来请求函数计算平台,可以通过创建和设置一个 HTTP 触发器来链接事件源。
第四,通过HTTP 触发器进行请求。由于自定义镜像是 HTTP Server 的形式存在,通常会设定监听的IP、端口(如 0.0.0.0:9000)、超时时间、HTTP 参数(如 Keep-alive)等。
这里需要说明一下,如果是首次启动,函数计算平台会从镜像仓库拉取自定义的镜像,并启动容器。
第五,函数计算服务控制器会将接收到的请求,调度到对应的 HTTP Server,由 HTTP Server 接管后续的服务处理。这样,就通过自定义镜像的功能,达到了快速开发和交付的能力,对于老的服务改造迁移,成本也非常低了。
如何切换流量?
函数功能开发完成后,如何灰度发布,怎么保证流量无损切换,如何让小流量实验后再全面转正呢?
版本和别名,函数计算支持为发布的函数版本创建别名。别名可以理解为指向特定版本的一个指针。可以利用别名来轻松实现流量切换、版本回滚以及灰度发布等功能。
以 HTTP 触发器为例,如果没有别名,每次新版本上线,你都需要手动修改 HTTP 触发器关联的版本号。
在实际业务开发中,一般会基于最新版本 $LATEST 的代码来开发。针对开发好的函数,可以发布一个版本出来,比如 Public Version1,函数计算平台会记录发布的版本信息,通过配置一个别名指向该版本,就可以用来处理对外的请求了。
而且,还可以在 $LATEST 版本上继续开发。同样,开发完成之后,只需要再次发布新的版本 Public Version2 即可。如果此时,需要将流量切换到新发布的版本上,只需要切换别名的指向即可,而用户在这个过程是无感知的,也不会有任何的流量损失。
如果对新的功能版本不是那么放心,还可以设置主版本和新版本的流量比例。比如设置 95% 的流量指向主版本,5% 的指向最新版本。观察没问题后,再全流量切换到新版本。如果出现问题,需要进行回滚,只需将指针指向原来的版本或者关闭新版本的测试流量即可。这样就可以优雅地完成版本的迭代升级了。别名无法脱离服务或版本单独存在,使用别名访问服务或函数时,函数计算会将别名解析为其指向的版本,调用方无需了解别名指向的具体版本。
有了版本控制和别名,就可以在线上服务不受影响的基础上,完成优雅的流量变更。
如何削峰和容灾?
微服务中,主要是通过异步消息队列的方式来应对流量峰值,当然,这只是针对延时不敏感的服务场景来说,如日志的 ETL、批量处理、事件处理等。针对这种情况,在函数计算的 Serverless 平台中,也往往会通过引入消息队列的方式,先将突增的流量引入进来,然后再通过后端的调度系统调度给云函数处理。这样的做法,既可以避免突增流量使得后端资源池来不及扩容,导致出现失败的情况,又让函数计算平台可以快速地响应请求方,避免等待,由函数计算平台来保证请求可靠地执行。
既然谈到万一资源池扩容没跟上请求速度,或者像公有云这种资源池出现调度问题的情况了,那么函数计算平台是否有容灾的能力呢?从平台的角度来说,云厂商都是做了同步重试、异步延时重试的逻辑的。针对异步的信息,甚至引入了递增重试机制和异步策略到用户自己的容灾队列、容灾处理函数中去处理,来进一步保障消息的可靠传输和处理。
从使用者的角度来看,针对延时不敏感的场景,建议选择异步方式来处理函数的计算,同时配置容灾队列或者云函数的链式调用方法来解决处理异常的情况。