前言:在 Serverless 架构下,虽然更多精力是关注业务代码,但是实际上对一些配置和成本也是需要关注的,并且必要的时候还需要根据配置与成本对 Serverless 应用进行配置和代码优化。

Serverless 应用开发观念的转变

Serverless 架构带来的除了一种新的架构、一种新的编程范式,还包括思路上的转变,尤其是开发过程中的一些思路转变。有人说要把 Serverless 架构看成一种天然的分布式架构,需要用分布式架构的思路去开发 Serverless 应用。诚然,这种说法是正确的。但是在一些情况下,Serverless 还有一些特性,所以要转变开发观念。

1、文件上传方法

在传统 Web 框架中,上传文件是非常简单和便捷的,例如 Python 的 Flask 框架:

f = request.files['file']
f.save('my_file_path')

但是在 Serverless 架构下,文件却不能直接上传,原因如下:

  • 一般情况下,一些云平台的API网关触发器会将二进制文件转换成字符串,不便直接获取和存储;
  • 一般情况下,API 网关与 FaaS 平台之间传递的数据包有大小限制,很多平台限制数据包大小为 6MB 以内;
  • FaaS 平台大多是无状态的,即使存储到当前实例中,也会随着实例释放而使文件丢失。

所以,传统 Web 框架中常用的上传文件方案不太适合在 Serverless 架构中直接使用。在 Serverless 架构中,上传文件的方法通常有两种:一种是转换为 Base64 格式后上传,将文件持久化到对象存储或者 NAS 中,但 API 网关与 FaaS 平台之间传递的数据包有大小限制,所以此方法通常适用于上传头像等小文件的业务场景。
 
另一种上传方法是通过对象存储等平台来上传,因为客户端直接通过密钥等来将文件直传到对象存储是有一定风险的,所以通常是客户端发起上传请求,函数计算根据请求内容进行预签名操作,并将预签名地址返给客户端,客户端再使用指定的方法上传,上传完成之后,通过对象存储触发器等来对上传结果进行更新等,如下图所示。


在 Serverless 架构下文件上传文件示例
 
以阿里云函数计算为例,针对上述两种常见的上传方法通过 Bottle 来实现。在函数计算中,先初始化对象存储相关的对象等:

AccessKey = {"id": '',"secret": ''
}
OSSConf = {'endPoint': 'oss-cn-hangzhou.aliyuncs.com','bucketName': 'bucketName','objectSignUrlTimeOut': 60
}
#获取/上传文件到OSS的临时地址
auth = oss2.Auth(AccessKey['id'], AccessKey['secret'])
bucket = oss2.Bucket(auth, OSSConf['endPoint'], OSSConf['bucketName'])
#对象存储操作
getUrl = lambda object, method: bucket.sign_url(method, object, OSSConf['objectSignUrlTimeOut'])
getSignUrl = lambda object: getUrl(object, "GET")
putSignUrl = lambda object: getUrl(object, "PUT")#获取随机字符串
randomStr = lambda len: "".join(random.sample('abcdefghijklqrstuvwxyz123456789ABCDEFGZSA' * 100, len))

第一种上传方法,通过 Base64 上传之后,将文件持久化到对象存储:

#文件上传
# URI: /file/upload
# Method: POST
@bottle.route('/file/upload', "POST")
def postFileUpload():try:pictureBase64 = bottle.request.GET.get('picture', '').split("base64,")[1]object = randomStr(100)with open('/tmp/%s' % object, 'wb') as f:f.write(base64.b64decode(pictureBase64))bucket.put_object_from_file(object, '/tmp/%s' % object)return response({"status": 'ok',})except Exception as e:print("Error: ", e)return response(ERROR['SystemError'], 'SystemError')

第二种上传方法,获取预签名的对象存储地址,再在客户端发起上传请求,直传到对象存储:

#获取文件上传地址
# URI: /file/upload/url
# Method: GET
@bottle.route('/file/upload/url', "GET")
def getFileUploadUrl():try:object = randomStr(100)return response({        "upload": putSignUrl(object),    "download": 'https://download.xshu.cn/%s' % (object)    })    except Exception as e:  print("Error: ", e)     return response(ERROR['SystemError'], 'SystemError')

HTML 部分:

<div style="width: 70%">  <div style="text-align: center">   <h3>Web端上传文件</h3>  </div>  <hr>  <div>  <p>      方案1:上传到函数计算进行处理再转存到对象存储,这种方法比较直观,问题是 FaaS 平台与 API 网关处有数据包大小上限,而且对二进制文件处理并不好。      </p>  <input type="file" name="file" id="fileFc"/>   <input type="button" onclick="UpladFileFC()" value="上传"/>  </div>   <hr>  <div>    <p>            方案2:直接上传到对象存储。流程是先从函数计算获得临时地址并进行数据存储(例如将文件信息存到 Redis 等),然后再从客户端将文件上传到对象存储,之后通过对象存储触发器触发函数,从存储系统(例如已经存储到Redis)读取到信息,再对图像进行处理。  </p>     <input type="file" name="file" id="fileOss"/>  <input type="button" onclick="UpladFileOSS()" value="上传"/> </div>
</div>

通过 Base64 上传的客户端 JavaScript 实现:

function UpladFileFC() {  const oFReader = new FileReader();  oFReader.readAsDataURL(document.getElementById("fileFc").files[0]);   oFReader.onload = function (oFREvent) {  const xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new        ActiveXObject("Microsoft.XMLHTTP"))   xmlhttp.onreadystatechange = function () {    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {            alert(xmlhttp.responseText)}      }        const url = "https://domain.com/file/upload"   xmlhttp.open("POST", url, true);      xmlhttp.setRequestHeader("Content-type", "application/json");       xmlhttp.send(JSON.stringify({       picture: oFREvent.target.result     })); }
}

客户端通过预签名地址,直传到对象存储的客户端 JavaScript 实现:

function doUpload(bodyUrl) {  const xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new Active       XObject("Microsoft.XMLHTTP"));    xmlhttp.open("PUT", bodyUrl, true);  xmlhttp.onload = function () {   alert(xmlhttp.responseText)  };   xmlhttp.send(document.getElementById("fileOss").files[0]);}function UpladFileOSS() {const xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new ActiveXObject("Microsoft.XMLHTTP"))xmlhttp.onreadystatechange = function () {if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {            const body = JSON.parse(xmlhttp.responseText)         if (body['url']) {  doUpload(body['url'])   }}    }    const getUploadUrl = 'https://domain.com/file/upload/url'   xmlhttp.open("POST", getUploadUrl, true); xmlhttp.setRequestHeader("Content-type", "application/json"); xmlhttp.send();
}

整体效果如图中所示。
 

Serverless 架构下文件上传实验 Web 端效果
 
此时,我们可以在当前页面进行不同类型的文件上传方案实验。

2、文件读写与持久化方法

应用在执行过程中,可能会涉及文件的读写操作,或者是一些文件的持久化操作。在传统的云主机模式下,可以直接读写文件,或者将文件在某个目录下持久化,但是在 Serverless 架构下并不是这样的。
 
由于 FaaS 平台是无状态的,并且用过之后会被销毁,因此文件并不能直接持久化在实例中,但可以持久化到其他的服务中,例如对象存储、NAS 等。
 
同时,在不配置 NAS 的情况下,FaaS 平台通常情况下只具备 /tmp 目录可写权限,所以部分临时文件可以缓存在 /tmp 文件夹下。

3、慎用部分 Web 框架的特性

(1) 异步
 
函数计算是请求级别的隔离,所以可以认为这个请求结束了,实例就有可能进入一个静默状态。而在函数计算中,API 网关触发器通常是同步调用(以阿里云函数计算为例,通常只在定时触发器、OSS 事件触发器、MNS 主题触发器和 IoT 触发器等几种情况下是异步触发)。
 
这就意味着当 API 网关将结果返给客户端的时候,整个函数就会进入静默状态,或者被销毁,而不是继续执行完异步方法。所以通常情况下像 Tornado 等框架就很难在 Serverless 架构下发挥其异步的作用。当然,如果使用者需要异步能力,可以参考云厂商所提供的异步方法。

以阿里云函数计算为例,阿里云函数计算为用户提供了一种异步调用能力。当函数的异步调用被触发后,函数计算会将触发事件放入内部队列,并返回请求 ID,而不会返回具体的调用情况及函数执行状态。如果用户希望获得异步调用的结果,可以通过配置异步调用目标来实现,如图所示。
 

函数异步功能原理简图
 
(2) 定时任务

在 Serverless 架构下,应用一旦完成当前请求,就会进入静默状态,甚至实例会被销毁,这就导致一些自带定时任务的框架没有办法正常执行定时任务。函数计算通常是由事件触发,不会自主定时启动。例如 Egg 项目中设定了一个定时任务,但是在实际的函数计算中如果没有通过触发器触发该函数,该函数不会被触发,也不会从内部自动启动来执行定时任务,此时可以使用定时触发器,通过定时触发器触发指定方法来替代定时任务。

4、要注意应用组成结构

(1) 静态资源与业务逻辑
 
在 Serverless 架构下,静态资源更应该在对象存储与 CDN 的加持下对外提供服务,否则所有的资源都在函数中。通过函数计算对外暴露,不仅会让函数的业务逻辑并发度降低,也会造成更多的成本。尤其是将一些已有的程序迁移到 Serverless 架构上,例如 Wordpress 等,更要注意将静态资源与业务逻辑进行拆分,否则在高并发情况下,性能与成本都将会受到比较严峻的考验。
 
(2) 业务逻辑的拆分
 
在众多云厂商中,函数的收费标准都是依靠运行时间、配置的内存以及产生的流量收费的。如果一个函数的内存设置不合理,会导致成本成倍增加。想要保证内存设置合理,更要保证业务逻辑结构的可靠性。
 
以阿里云函数计算为例,一个应用有两个对外接口,其中有一个接口的内存消耗在 128MB 以下,另一个接口的内存消耗稳定在 3000MB 左右。这两个接口平均每天会被触发 10000 次,并且时间消耗均在 100 毫秒。如果两个接口写到一个函数中,那么这个函数可能需要将内存设置在 3072MB,同时用户请求内存消耗较少的接口在冷启动情况下难以得到较好的性能;如果两个接口分别写到函数中,则两个函数内存分别设置成 128MB 以及 3072MB 即可,如表所示。
 

 
通过上表可以明确看出合理、适当地拆分业务会在一定程度上节约成本。上面例子的成本节约近 50%。
 
_关于作者:刘宇(江昱)国防科技大学电子信息专业在读博士,阿里云 Serverless 产品经理,阿里云 Serverless 云布道师,CIO 学院特聘讲师。

新书推荐


 
本书会通过多个开源项目、多个云厂商的多款云产品,以及多种途径向读者介绍什么是 Serverless 架构、如何上手 Serverless 架构、不同领域中 Serverless 架构的应用以及如何从零开发一个 Serverless 应用等。本书可以帮助读者将 Serverless 架构融入到自己所在的领域,把 Serverless 项目真实落地,获得 Serverless 架构带来的技术红利。

Serverless 工程实践 | Serverless 应用开发观念的转变相关推荐

  1. Serverless 工程实践 | Serverless 应用优化与调试秘诀

    作者|刘宇 前言:本文将以阿里云函数计算为例,提供了在线调试.本地调试等多种应用优化与调试方案. Serverless 应用调试秘诀 在应用开发过程中,或者应用开发完成,所执行结果不符合预期时,我们要 ...

  2. Serverless 工程实践 | 快速搭建 Kubeless 平台

    简介:Kubeless 是基于 Kubernetes 的原生无服务器框架.其允许用户部署少量的代码(函数),而无须担心底层架构. 快速搭建 Kubeless 平台 Kubeless 简介 Kubele ...

  3. Serverless 工程实践 | 细数 Serverless 的配套服务

    简介:上文说到云计算的十余年发展让整个互联网行业发生了翻天覆地的变化,Serverless 作为云计算的产物,或者说是云计算在某个时代的表现,被很多人认为是真正意义上的云计算,关于"Serv ...

  4. Serverless 工程实践 | 零基础上手 Knative 应用

    作者|刘宇 前言:Knative 是一款基于 Kubernetes 的 Serverless 框架.其目标是制定云原生.跨平台的 Serverless 编排标准. Knative 介绍 Knative ...

  5. Serverless 工程实践|自建 Apache OpenWhisk 平台

    作者 | 刘宇(江昱) 前言:OpenWhisk 是一个开源.无服务器的云平台,可以在运行时容器中通过执行扩展的代码响应各种事件,而无须用户关心相关的基础设施架构. OpenWhisk 简介 Open ...

  6. Serverless 工程实践 | 自建 Apache OpenWhisk 平台

    简介:OpenWhisk 是一个开源.无服务器的云平台,可以在运行时容器中通过执行扩展的代码响应各种事件,而无须用户关心相关的基础设施架构. OpenWhisk 简介 OpenWhisk 是基于云的分 ...

  7. Serverless时代的微服务开发指南:华为云提出七大实践新标准

    摘要:本文结合华为云在Serverless Microservice方面的实践,总结提炼出七大Serverless Microservice开发 "实践标准",为加速全域Serve ...

  8. 前端新思路:组件即函数和Serverless SSR实践

    导读:在今天,对于 Node.js 运维和高并发依然是很有挑战的,为了提效,将架构演进为页面即服务,可是粒度还不够,借着云原生和 Serverless 大潮,无运维,轻松扩展,对前端是极大的诱惑.那么 ...

  9. 云栖大会|闲鱼Serverless架构实践(精品)

    造梦者 | 王树彬(国有),阿里巴巴闲鱼架构负责人 2014年6月28日,阿里即将赴美上市的这一年,西溪园区的一个茶水间里,28个人日夜赶工了三个月后,上线了一个闲置交易平台--闲鱼.今年5月份,在阿 ...

最新文章

  1. 灰度图像--图像分割 Scharr算子
  2. matplotlib绘制三维轨迹图
  3. linux 修改分辨率lcd_16.Linux-LCD驱动(详解)
  4. FLV封装格式分析器
  5. android是32-bit系统还是64-bit系统
  6. 文件已经上传到服务器翻译,服务器接受上传的优化 翻译+源码分析
  7. 一夜抢空880万!中国最狠印钞机,终于开始收割年轻人了
  8. mysql 什么是审核上线_自动审核及上线系统
  9. pythonscipy教程_Python学习教程(Python学习路线):Python—SciPy精讲
  10. 清华化工系碳纳米管重大突破!道翰天琼认知智能机器人平台API接口大脑为您揭秘
  11. base64、File、Blob、ArrayBuffer互转
  12. 淘宝动态评分绿了怎么办
  13. 计算机之父图灵的 150 封信,多在讨论 AI
  14. 《自控力》 第三章读书笔记
  15. java通过调用鼠标模拟自动添加微信好友
  16. Hadoop系统中的单点故障解决方案总结
  17. linux 内存映射-ioremap和mmap函数
  18. 笔记本电脑硬盘不见了_笔记本电脑找不到硬盘原因及解决方法
  19. apicloud——微信第三方登录、apple登录
  20. 7-66 华氏温度转换为摄氏温度

热门文章

  1. 软件调试学习笔记(五)—— 软件断点内存断点
  2. 020 Android之so文件动态调试
  3. HttpClient发送Get请求(java)【从新浪云搬运】
  4. 【ThinkPHP系列篇】ThinkPHP框架的介绍和搭建(一)
  5. 2、MySQL备份类型
  6. 2.3.3 进程互斥的硬件实现方法
  7. Spring boot添加员工
  8. Keepalived双机热备
  9. Maven配置ali镜像
  10. SQLserver模糊查询