写点什么

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:051857

评论

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

Android包体积优化(常规、进阶、极致)

yechaoa

android 性能优化 包大小 6月月更 包体积优化

Java中java.util.Arrays参考指南

okokabcd

Java

8年打磨,《游戏设计梦工厂》发布史诗级更新!

博文视点Broadview

linux检测系统是否被入侵(下)

入门小站

Linux

要想Linux命令行玩的溜,还得apropos!此文运维必看!

wljslmz

Linux 运维 6月月更

悬赏平台并没有WEB端开发,在原生开发和混合开发中哪种合适?

开源直播系统源码

软件开发 app源码 原生开发 混合开发 悬赏平台源码

HashMap分析-基础属性与结构

zarmnosaj

6月月更

融云通信解决方案 破解企业沟通痛点

融云 RongCloud

最新版CorelDRAW Technical Suite2022

茶色酒

cdr2022

【Go实现】实践GoF的23种设计模式:装饰者模式

元闰子

Go 设计模式 装饰器 装饰者模式

带链接跳转的微信红包封面制作教程和使用指南

boshi

小程序 微信红包封面 微信红包

油猴脚本学习

Sher10ck

脚本 油猴

Ubuntu环境下载OpenJDK11源码

程序员欣宸

Java Openjdk 6月月更

深入浅出总结Flink运行时架构

百思不得小赵

大数据 flink 6月月更

rxjs Observable 设计原理背后的 Pull 和 Push 思路

Jerry Wang

typescript 响应式编程 angular RXJS 6月月更

自媒体行业内卷严重:企业自媒体应该何去何从

石头IT视角

这玩意叫跳表?

慕枫技术笔记

数据结构 算法 6月月更

在线SQL转HTMLTable工具

入门小站

工具

微博评论架构设计

泋清

#架构训练营

高效远程办公的基石:有效沟通 |社区征文

wljslmz

远程办公 初夏征文

稳!上千微服务如何快速接入 Zadig(Helm Chart 篇)

Zadig

DevOps 微服务架构 持续交付 自动化运维 Zadig

00 后云原生工程师:用开源 Zadig 为思创科技(广州公交)研发开源节流

Zadig

DevOps 研发效能 工程师 自动化运维

Go Web 编程入门:HTTP 自定义路由

宇宙之一粟

Go 语言 6月月更

设计电商秒杀系统

凯博无线

架构实战营|模块5

KDA

#架构实战营

CTO专访:合见工软深化产品布局 加速国产EDA技术革新

科技热闻

在线文本过滤小于指定长度工具

入门小站

工具

Prometheus 2.36.0 新特性

耳东@Erdong

release Prometheus 6月月更

中科方德技术专家直播:如何基于 OpenStack、Ceph 构建私有云平台? | 第 27 期

OpenAnolis小助手

Ceph 龙蜥大讲堂 中科方德 OpenStack 私有云平台

笔记

IT蜗壳-Tango

6月月更

数据生态第三弹 | RocketMQ OpenMLDB Connector,实时数据到特征工程的高速传输

第四范式开发者社区

人工智能 云原生 实时数据 特征平台 特征工程

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