乐趣区

关于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 平台之间传递的数据包有大小限度,所以此办法通常实用于上传头像等小文件的业务场景。
 
另一种上传办法是通过对象存储等平台来上传,因为客户端间接通过密钥等来将文件直传到对象存储是有肯定危险的,所以通常是客户端发动上传申请,函数计算依据申请内容进行预签名操作,并将预签名地址返给客户端,客户端再应用指定的办法上传,上传实现之后,通过对象存储触发器等来对上传后果进行更新等,如下图所示。
 


在 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();}

 
整体成果如图中所示。
 

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 架构带来的技术红利。

退出移动版