“AI 技术+人才”如何成为企业增长新引擎?戳此了解>>> 了解详情
写点什么

Serverless 架构下还需要评估函数资源吗?

  • 2020-08-09
  • 本文字数:5664 字

    阅读完需:约 19 分钟

Serverless架构下还需要评估函数资源吗?

Serverless 布道师在讲述 Serverless 架构和云主机区别的时候,常会有这样的描述:


传统业务开发想要上线,需要先评估资源使用,并根据资源评估结果购买云主机,之后还要根据业务发展不断对主机等资源进行升级维护。而 Serverless 架构不需要这样复杂的流程,将函数部署到线上后,一切后端服务交给运营商来处理,哪怕是瞬时高并发,也有云厂商来自动扩缩。


但在实际生产生活中,Serverless 真的可以做到无需对资源评估吗?还是说在 Serverless 架构下,资源评估的内容或对象发生了变化,或者进行了简化?

探索 Serverless 下的资源评估

以国内某云厂商为例,在其云函数中,我们创建一个云函数之后,设置页面会出现可设置的选项:



这两个设置范围分别是从 64M-1536M 和 1-900S,这样的配置其实就涉及到资源评估了。


首先是超时时间,一个项目、函数或 Action 都有执行时间,如果超过某个时间没执行完就可以评估其为发生了“意外”,可以被“干掉“了,这个就是超时时间。例如一个获取用户信息的请求,在 10S 内没有返回,证明其不能满足业务需求,那么,我们就可以把超时设置为 10S。当一个运行速度很慢的业务,至少要 50S 才能执行完,那么这个值设置的时候就要大于 50,否则程序可能因为超时被强行停止。


然后是内存,内存是一个有趣的东西,可能衍生两个关联点。


关联点 1: 程序本身需要一定的内存,这个内存要大于程序本身的内存,以 Python 语言为例:


# -*- coding: utf8 -*-import jieba

def main_handler(event, context): jieba.load_userdict("./jieba/dict.txt") seg_list = jieba.cut("我来到北京清华大学", cut_all=True) print("Full Mode: " + "/ ".join(seg_list)) # 全模式
seg_list = jieba.cut("我来到北京清华大学", cut_all=False) print("Default Mode: " + "/ ".join(seg_list)) # 精确模式
seg_list = jieba.cut("他来到了网易杭研大厦") # 默认是精确模式 print(", ".join(seg_list))
seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造") # 搜索引擎模式 print(", ".join(seg_list))

复制代码


对程序代码的说明:为了让结果更加直观,差距更加大,所以这里每次都重新导入了自带 dict,这个操作本身就是相对浪费时间和内存的。在实际使用中 jieba 自带缓存,并且无需手动导入本身的 dict


当导入一个自定义的 dict 到 jieba 中,如果此时函数内存设置的默认 128M 内存限制+3S 超时限制就会这样:



此时可以看到,由于在导入自定义 dict 时内存消耗过大, 默认的 128 不足以满足需求,所以需要将其修改成最大:



这时又再次提醒超时,还需要修改超时时间为适当的数值(此处设定为 10S):



总而言之,在关注程序本身的前提下,要将内存设置在一个合理范围内,这个范围是>=程序本身需要的内存数值。


关联点 2: 计费相关,在云函数的文档中,我们可以看到:


云函数 SCF 按照实际使用付费,采用后付费小时结,以 为单位进行结算。

SCF 账单由以下三部分组成,每部分根据自身统计结果和计算方式进行费用计算,结果以 为单位,并保留小数点后两位。

资源使用费用

调用次数费用

外网出流量费用


调用次数和出网流量都是与程序或者使用量相关的,无需格外关注,但在资源使用费用有一些注意点:


资源使用量 = 函数配置内存 × 运行时长

用户资源使用量,由函数配置内存,乘以函数运行时的计费时长得出。其中配置内存转换为 GB

单位,计费时长由毫秒(ms)转换为秒(s)单位,因此,资源使用量的计算单位为 GBs (GB-秒)。

例如,配置为 256MB 的函数,单次运行了 1760 ms,计费时长为 1760

ms,则单次运行的资源使用量为(256/1024)×(1760/1000) = 0.44 GBs。

针对函数的每次运行,均会计算资源使用量,并按小时汇总求和,作为该小时的资源使用量。


这里有一个非常重要的公式,那就是函数配置内存*运行时长。函数配置内存就是我们为程序选择的内存大小,运行时长就是运行程序之后得到的结果:



以这个程序为例,使用的是 1536MB,则使用量为(1536/1024) * (3200/1000) = 4.8GBs


当然,如果此时是 250MB,那程序也可以运行:



此时的资源使用量为(256/1024) * (3400/1000) = 0.85GBs


对比上一次,程序执行时间增加了 0.2S,但是资源使用量降低了将近 6 倍!


产品单价是:



虽然 GBs 的单价很低,但是业务量上来之后,这个数字也是要值得注意的。刚才的只是一个单次请求,如果每天有 1000 单次请求,那费用就会出现差距(仅计算资源使用量费用,而不计算调用次数/外网流量):


1536MB: 4.810000.00011108 = 0.5 元


256MB:0.8510000.00011108 = 0.09442 元


如果不是 1000 次调用,而是 10 万次调用,则就是 50 元和 9 元的区别,随着流量越大,差距越大。


当然,多数情况函数执行时间不会这么久,以下面函数为例:



计费时间均是 100ms,每日调用量在 6000 次左右:



如果按照 64M 内存来计算,单资源费用只要 76 元一年,而如果内存都设置为 1536,则一年要 1824 元!这个费用相当于:



所以,超时时间需要对代码和业务场景进行评估来进行设置,可能关系到程序运行的稳定和功能的完整性;内存则不仅仅在程序使用层面有着不同的需求,在费用成本等方面也占有极大的比重,所以内存设置需要对程序进行一个评估。那么问题来了,内存设置多大比较划算?同样是之前的代码,在本地进行简单的脚本编写:


from tencentcloud.common import credentialfrom tencentcloud.common.profile.client_profile import ClientProfilefrom tencentcloud.common.profile.http_profile import HttpProfilefrom tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKExceptionfrom tencentcloud.scf.v20180416 import scf_client, models
import jsonimport numpyimport matplotlib.pyplot as plt
try: cred = credential.Credential("", "") httpProfile = HttpProfile() httpProfile.endpoint = "scf.tencentcloudapi.com"
clientProfile = ClientProfile() clientProfile.httpProfile = httpProfile client = scf_client.ScfClient(cred, "ap-shanghai", clientProfile)
req = models.InvokeRequest() params = '{"FunctionName":"hello_world_2"}' req.from_json_string(params)
billTimeList = [] timeList = [] for i in range(1, 50): print("times: ", i) resp = json.loads(client.Invoke(req).to_json_string()) billTimeList.append(resp['Result']['BillDuration']) timeList.append(resp['Result']['Duration'])
print("计费最大时间", int(max(billTimeList))) print("计费最小时间", int(min(billTimeList))) print("计费平均时间", int(numpy.mean(billTimeList)))
print("运行最大时间", int(max(timeList))) print("运行最小时间", int(min(timeList))) print("运行平均时间", int(numpy.mean(timeList)))
plt.figure() plt.subplot(4, 1, 1) x_data = range(0, len(billTimeList)) plt.plot(x_data, billTimeList) plt.subplot(4, 1, 2) plt.hist(billTimeList, bins=20) plt.subplot(4, 1, 3) x_data = range(0, len(timeList)) plt.plot(x_data, timeList) plt.subplot(4, 1, 4) plt.hist(timeList, bins=20) plt.show()
except TencentCloudSDKException as err: print(err)
复制代码


运行之后会输出一个简单的图像:



从上到下分别是不同次数计费时间图、计费时间分布图以及不同次数运行时间图和运行时间分布图。256M 起步,1536M 终止,步长 128M,每个内存大小串行靠用 50 次,统计表:



注:为了让统计结果更加清晰,差异性比较明显,在程序代码中进行了部分无用操作用来增加程序执行时间。正常使用 jieba 的速度基本都是毫秒级的:



通过表统计可以看到在满足程序内存消耗的前提下,内存大小对程序执行时间的影响并不是很大,反而是对计费影响很大。


当然上面是两个重要指标,除此之外还有一个参数需要用户来评估:函数并发量,在项目上线之后,需要对项目可能产生的并发量进行评估,当评估的并发量超过默认的并发量,要及时联系售后同学或者提交工单进行最大并发量数值的提升。


除了上面的简单评估,有兴趣的同学也可以进行多进程/多线程与函数执行时间/内存关系的评估,但是考虑到很多时候云函数的业务都不涉及到多进程/多线程,所以这里不仅行单独测试。

应该如何设置函数的内存与超时时间

上一部分主要说了 Serverless 架构与资源评估:性能与成本探索​。探索之后,就不得不引出一个新的问题:在使用 Serverless 架构时如何来设置运行内存和超时时间呢?


评估方法有很多,我的做法是先将函数上线,选择一个稍大的内存执行一次。



得到上图结果,再函数设置为 128M 或者 256M,超时时间设置成 3S,接着运行一段时间,例如接口每天触发次数大约为 4000+次:



将函数日志写成脚本,重新做统计:


import json, time, numpy, base64import matplotlib.pyplot as pltfrom matplotlib import font_managerfrom tencentcloud.common import credentialfrom tencentcloud.common.profile.client_profile import ClientProfilefrom tencentcloud.common.profile.http_profile import HttpProfilefrom tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKExceptionfrom tencentcloud.scf.v20180416 import scf_client, models
secretId = ""secretKey = ""region = "ap-guangzhou"namespace = "default"functionName = "course"
font = font_manager.FontProperties(fname="./01.ttf")
try: cred = credential.Credential(secretId, secretKey) httpProfile = HttpProfile() httpProfile.endpoint = "scf.tencentcloudapi.com"
clientProfile = ClientProfile() clientProfile.httpProfile = httpProfile client = scf_client.ScfClient(cred, region, clientProfile)
req = models.GetFunctionLogsRequest()
strTimeNow = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))) strTimeLast = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()) - 86400)) params = { "FunctionName": functionName, "Limit": 500, "StartTime": strTimeLast, "EndTime": strTimeNow, "Namespace": namespace } req.from_json_string(json.dumps(params))
resp = client.GetFunctionLogs(req)
durationList = [] memUsageList = []
for eveItem in json.loads(resp.to_json_string())["Data"]: durationList.append(eveItem['Duration']) memUsageList.append(eveItem['MemUsage'] / 1024 / 1024)
durationDict = { "min": min(durationList), # 运行最小时间 "max": max(durationList), # 运行最大时间 "mean": numpy.mean(durationList) # 运行平均时间 } memUsageDict = { "min": min(memUsageList), # 内存最小使用 "max": max(memUsageList), # 内存最大使用 "mean": numpy.mean(memUsageList) # 内存平均使用 }
plt.figure(figsize=(10, 15)) plt.subplot(4, 1, 1) plt.title('运行次数与运行时间图', fontproperties=font) x_data = range(0, len(durationList)) plt.plot(x_data, durationList) plt.subplot(4, 1, 2) plt.title('运行时间直方分布图', fontproperties=font) plt.hist(durationList, bins=20) plt.subplot(4, 1, 3) plt.title('运行次数与内存使用图', fontproperties=font) x_data = range(0, len(memUsageList)) plt.plot(x_data, memUsageList) plt.subplot(4, 1, 4) plt.title('内存使用直方分布图', fontproperties=font) plt.hist(memUsageList, bins=20)
# with open("/tmp/result.png", "rb") as f: # base64_data = base64.b64encode(f.read())
print("-" * 10 + "运行时间相关数据" + "-" * 10) print("运行最小时间:\t", durationDict["min"], "ms") print("运行最大时间:\t", durationDict["max"], "ms") print("运行平均时间:\t", durationDict["mean"], "ms")
print("\n")
print("-" * 10 + "内存使用相关数据" + "-" * 10) print("内存最小使用:\t", memUsageDict["min"], "MB") print("内存最大使用:\t", memUsageDict["max"], "MB") print("内存平均使用:\t", memUsageDict["mean"], "MB")
print("\n")
plt.show(dpi=200)


except TencentCloudSDKException as err: print(err)
复制代码


运行结果:


----------运行时间相关数据----------运行最小时间:   1 ms运行最大时间:   291 ms运行平均时间:   63.45 ms

----------内存使用相关数据----------内存最小使用: 21.20703125 MB内存最大使用: 29.66015625 MB内存平均使用: 24.8478125 MB
复制代码



通过上图可以看出,近 500 次,每次函数的时间消耗和内存使用。时间消耗基本在 1S 以下,所以此处超时时间设置成 1S 是合理的,而内存使用基本是 64M 以下,所以此时内存设置成 64M 就可以。当然,通过这个图可以看出云函数在执行时可能会有一定的波动,所以无论是内存使用还是超时时间,都可能会出现一定的波动,可以根据自身的业务需求来做一些舍弃,将资源使用量压到最低,节约成本。

总结

综上所述,Serverless 架构也是需要资源评估的,而且资源评估同样是和成本直接挂钩的,只不过这个资源评估的对象逐渐发生了变化,相对之前的评估维度、难度而言,都是大幅度缩小或者降低的。


上线函数之前,进行资源评估的做法基本是分为两步走:


  • 简单运行两次,评估一下基础资源使用量,然后设置一个稍微偏高的值;

  • 函数运行一段时间,得到一定的样本值,在进行数据可视化和基本的数据分析,得到一个相对稳定权威的数据;


2020-08-09 22:051845

评论

发布
暂无评论
发现更多内容

PageHelper原理深度剖析(集成+源码)

阿Q说代码

ThreadLocal 分页 PageHelper 8月日更 mybatis的拦截器

智能运维系列直播间开讲啦,就在今天!

浪潮云

传统到敏捷的转型中,谁更适合做Scrum Master?

华为云开发者联盟

Scrum 敏捷 团队 项目经理 Scrum Master

零基础入门:基于开源WebRTC,从0到1实现实时音视频聊天功能

JackJiang

音视频 WebRTC 即时通讯 IM

从lowcode看下一代前端应用框架

百度Geek说

大前端 lowcode

NameServer 核心原理解析

leonsh

RocketMQ 消息队列 NameServer

云小课 | 详解华为云独享型负载均衡如何计费

华为云开发者联盟

负载均衡 华为云 弹性负载均衡 独享型ELB实例 独享型负载均衡

国产接口调试工具ApiPost中的内置变量

Proud lion

大前端 测试 后端 Postman 开发工具

Go- 函数参数和返回值

HelloBug

函数 参数 返回值 Go 语言

Android模块化开发实践

vivo互联网技术

android 架构 开发 项目实战 模块

Java NIO在接口自动化中应用

FunTester

Java nio 接口测试 测试开发

后Kubernetes时代的虚拟机管理技术之kubevirt篇

谐云

虚拟机 #Kubernetes#

一分钟学会使用ApiPost中的全局参数和目录参数

CodeNongXiaoW

大前端 测试 后端 接口工具

在?进来看看新一季周边到底做点啥?【话题讨论】

气气

话题讨论

使用mock.js给前端生成需要的数据

与风逐梦

大前端 后端 开发工具

Golang:再谈生产者消费者模型

Regan Yue

协程 Go 语言 8月日更

多样数字人民币钱包来袭,阻力与动力并存

CECBC

来了!《中国移动2021智能硬件质量报告》正式发布

🏆「作者推荐」Java技术专题-JDK/JVM的新储君—GraalVM和Quarkus

洛神灬殇

Java JVM GraalVM 8月日更

最小二乘法,了解一下?

华为云开发者联盟

数据 数据处理 计算 最小二乘法 数学工具

6种常用Bean拷贝工具一览

码农参上

8月日更 对象拷贝

protocol buffer的高效编码方式

程序那些事

Java protobuf 程序那些事

手撸二叉树之二叉树的坡度

HelloWorld杰少

8月日更

带头撸抽奖系统,DDD + RPC 开发分布式架构!

小傅哥

DDD 小傅哥 架构设计 springboot 抽奖系统

KubeCube开源:魔方六面,降阶Kubernetes落地应用

网易数帆

开源 Kubernetes 容器 KubeCube

区块链+物联网设备,能产生什么反应?

CECBC

web技术分析| 一篇前端图像处理秘籍

anyRTC开发者

大前端 音视频 WebRTC web技术分享

以区块链为基础 通证经济是下一代互联网的数字经济

CECBC

带你梳理Jetty自定义ProxyServlet实现反向代理服务

华为云开发者联盟

容器 k8s jetty Servlet引擎 ProxyServlet

打造数字人民币的大运应用场景

CECBC

GraphQL设计思想

Ryan Zheng

graphql

Serverless架构下还需要评估函数资源吗?_服务革新_刘宇_InfoQ精选文章