Serverless 工程实践 | Serverless 应用开发观念的转变

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

前言:在 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 平台之间传递的数据包有大小限制,所以此方法通常适用于上传头像等小文件的业务场景。


另一种上传方法是通过对象存储等平台来上传,因为客户端直接通过密钥等来将文件直传到对象存储是有一定风险的,所以通常是客户端发起上传请求,函数计算根据请求内容进行预签名操作,并将预签名地址返给客户端,客户端再使用指定的方法上传,上传完成之后,通过对象存储触发器等来对上传结果进行更新等,如下图所示。


1.png


在 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['object
    SignUrlTimeOut'])
getSignUrl = lambda object: getUrl(object, "GET")
putSignUrl = lambda object: getUrl(object, "PUT")
#获取随机字符串
randomStr = lambda len: "".join(random.sample('abcdefghijklqrstuvwxyz123456789
    ABCDEFGZSA' * 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 Active
        XObject("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();
}


整体效果如图中所示。


2.png


Serverless 架构下文件上传实验 Web 端效果


此时,我们可以在当前页面进行不同类型的文件上传方案实验。


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


应用在执行过程中,可能会涉及文件的读写操作,或者是一些文件的持久化操作。在传统的云主机模式下,可以直接读写文件,或者将文件在某个目录下持久化,但是在 Serverless 架构下并不是这样的。


由于 FaaS 平台是无状态的,并且用过之后会被销毁,因此文件并不能直接持久化在实例中,但可以持久化到其他的服务中,例如对象存储、NAS 等。


同时,在不配置 NAS 的情况下,FaaS 平台通常情况下只具备 /tmp 目录可写权限,所以部分临时文件可以缓存在 /tmp 文件夹下。


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


(1) 异步


函数计算是请求级别的隔离,所以可以认为这个请求结束了,实例就有可能进入一个静默状态。而在函数计算中,API 网关触发器通常是同步调用(以阿里云函数计算为例,通常只在定时触发器、OSS 事件触发器、MNS 主题触发器和 IoT 触发器等几种情况下是异步触发)。


这就意味着当 API 网关将结果返给客户端的时候,整个函数就会进入静默状态,或者被销毁,而不是继续执行完异步方法。所以通常情况下像 Tornado 等框架就很难在 Serverless 架构下发挥其异步的作用。当然,如果使用者需要异步能力,可以参考云厂商所提供的异步方法。


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


3.png


函数异步功能原理简图

(2) 定时任务


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


4、要注意应用组成结构


(1) 静态资源与业务逻辑


在 Serverless 架构下,静态资源更应该在对象存储与 CDN 的加持下对外提供服务,否则所有的资源都在函数中。通过函数计算对外暴露,不仅会让函数的业务逻辑并发度降低,也会造成更多的成本。尤其是将一些已有的程序迁移到 Serverless 架构上,例如 Wordpress 等,更要注意将静态资源与业务逻辑进行拆分,否则在高并发情况下,性能与成本都将会受到比较严峻的考验。


(2) 业务逻辑的拆分


在众多云厂商中,函数的收费标准都是依靠运行时间、配置的内存以及产生的流量收费的。如果一个函数的内存设置不合理,会导致成本成倍增加。想要保证内存设置合理,更要保证业务逻辑结构的可靠性。


以阿里云函数计算为例,一个应用有两个对外接口,其中有一个接口的内存消耗在 128MB 以下,另一个接口的内存消耗稳定在 3000MB 左右。这两个接口平均每天会被触发 10000 次,并且时间消耗均在 100 毫秒。如果两个接口写到一个函数中,那么这个函数可能需要将内存设置在 3072MB,同时用户请求内存消耗较少的接口在冷启动情况下难以得到较好的性能;如果两个接口分别写到函数中,则两个函数内存分别设置成 128MB 以及 3072MB 即可,如表所示。


4.png


通过上表可以明确看出合理、适当地拆分业务会在一定程度上节约成本。上面例子的成本节约近 50%。


关于作者:刘宇(江昱)国防科技大学电子信息专业在读博士,阿里云 Serverless 产品经理,阿里云 Serverless 云布道师,CIO 学院特聘讲师。


新书推荐


image.png


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


Serverless 工程实践系列


Serverless 工程实践 | 从云计算到 Serverless


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


Serverless 工程实践 | 传统 Web 框架迁移


点击下方链接,立即购买!

http://shop0016.cn/Xjuovp

相关实践学习
【玩转ComfyUI】基于函数计算一键部署AI生图平台ComfyUI
本次实验将带大家通过使用阿里云产品函数计算FC,快速使用ComfyUI实现更高质量的图像生成。
从 0 入门函数计算
在函数计算的架构中,开发者只需要编写业务代码,并监控业务运行情况就可以了。这将开发者从繁重的运维工作中解放出来,将精力投入到更有意义的开发任务上。
相关文章
|
11月前
|
运维 监控 Cloud Native
【云故事探索】NO.17:国诚投顾的云原生 Serverless 实践
国诚投顾携手阿里云,依托Serverless架构实现技术全面升级,构建高弹性、智能化技术底座,提升业务稳定性与运行效率。通过云原生API网关、微服务治理与智能监控,实现流量精细化管理与系统可观测性增强,打造安全、敏捷的智能投顾平台,助力行业数字化变革。
【云故事探索】NO.17:国诚投顾的云原生 Serverless 实践
|
11月前
|
运维 监控 Cloud Native
【云故事探索】NO.17:国诚投顾的云原生 Serverless 实践
通过与阿里云深度合作,国诚投顾完成了从传统 ECS 架构向云原生 Serverless 架构的全面转型。新的技术架构不仅解决了原有系统在稳定性、弹性、运维效率等方面的痛点,还在成本控制、API 治理、可观测性、DevOps 自动化等方面实现了全方位升级。
|
存储 运维 Serverless
千万级数据秒级响应!碧桂园基于 EMR Serverless StarRocks 升级存算分离架构实践
碧桂园服务通过引入 EMR Serverless StarRocks 存算分离架构,解决了海量数据处理中的资源利用率低、并发能力不足等问题,显著降低了硬件和运维成本。实时查询性能提升8倍,查询出错率减少30倍,集群数据 SLA 达99.99%。此次技术升级不仅优化了用户体验,还结合AI打造了“一看”和“—问”智能场景助力精准决策与风险预测。
1391 69
|
Cloud Native Serverless 流计算
云原生时代的应用架构演进:从微服务到 Serverless 的阿里云实践
云原生技术正重塑企业数字化转型路径。阿里云作为亚太领先云服务商,提供完整云原生产品矩阵:容器服务ACK优化启动速度与镜像分发效率;MSE微服务引擎保障高可用性;ASM服务网格降低资源消耗;函数计算FC突破冷启动瓶颈;SAE重新定义PaaS边界;PolarDB数据库实现存储计算分离;DataWorks简化数据湖构建;Flink实时计算助力风控系统。这些技术已在多行业落地,推动效率提升与商业模式创新,助力企业在数字化浪潮中占据先机。
809 12
|
人工智能 Serverless API
Serverless 加 AI 让应用开发更简单
本文介绍了 Serverless 技术在 AI 领域的应用及 FunctionAI 平台的功能与优势。首先探讨了 Serverless 解决的核心问题,包括降低成本和提升效率。接着分析了构建 AI 应用的阻碍因素,如技术积累不足、大模型迭代快及算力资源有限。FunctionAI 平台通过按需模型托管、一键部署、流程式开发工具和灵活组装能力,帮助企业降低试错成本并提高开发效率。平台架构分为四层,涵盖模型层、开发层、服务层和部署层。实际应用场景包括图像生成、外部开发者支持及大流量处理等。最后,通过客户案例展示了平台在弹性扩展与成本优化方面的价值。
|
人工智能 运维 Devops
CAP:Serverless + AI 让应用开发更简单
CAP:Serverless + AI 让应用开发更简单
295 9
|
人工智能 运维 架构师
Serverless + AI 让应用开发更简单,加速应用智能化
Serverless + AI 让应用开发更简单,加速应用智能化
369 5
|
Kubernetes 监控 Serverless
基于阿里云Serverless Kubernetes(ASK)的无服务器架构设计与实践
无服务器架构(Serverless Architecture)在云原生技术中备受关注,开发者只需专注于业务逻辑,无需管理服务器。阿里云Serverless Kubernetes(ASK)是基于Kubernetes的托管服务,提供极致弹性和按需付费能力。本文深入探讨如何使用ASK设计和实现无服务器架构,涵盖事件驱动、自动扩展、无状态设计、监控与日志及成本优化等方面,并通过图片处理服务案例展示具体实践,帮助构建高效可靠的无服务器应用。
|
人工智能 运维 Serverless
Serverless + AI 让应用开发更简单
Serverless + AI 让应用开发更简单
593 2

相关产品

  • 函数计算