时间: 2020-09-03 00:08:26 人气: 2290 评论: 0
作者: 腾讯云架构平台部技术专家 陈杰
近年来,互联网服务从一开始的物理服务器托管,虚拟机,容器,发展到现在的云函数,逐步无服务器化,如下表所示。程序员逐步聚焦于最核心的业务逻辑开发,解放了生产力,显著提升了服务上线效率。
分类 | 物理机托管 | 虚拟机 | 容器 | 云函数 |
|---|---|---|---|---|
托管 | 机房运维 | 物理机运维 | OS运维 | 服务运维 |
收益 | 无人工值守 | 调度自动化 | 交付标准化 | 计算服务化 |
效率 | 开发(天)/部署(小时) | 开发(天)/部署(分钟) | 开发(天)/部署(秒) | 开发(分钟)/部署(毫秒) |
云函数带来了真正的计算服务,如下表所示,类比腾讯云COS对象存储,SCF以函数为单位封装计算,按需调度执行,无须关心函数的自动扩缩容,故障容灾等,无任何闲置成本。
分类 | cos对象存储 | 无服务器云函数 |
|---|---|---|
目标 | 数据托管 | 计算托管 |
单位 | 文件 | 函数 |
接口 | 上传/下载/修改/删除文件 | 上传/执行/修改/删除/函数 |
计费 | 文件容量大小/存储时长/下载上传流量 | 函数资源大小/执行时长/计算流量 |
云函数给用户带来的价值主要3点:
我们团队正在做弹性计算相关的事情,业务需求多,平台自身也需持续优化来支撑不断扩大的运营规模,现在5人左右的小团队要支撑100w核级别的计算运营,云函数的出现,正好解了我们在人力上的燃眉之急,在这里分享一下,希望能对大家有所启发。
我们有一些低频调用的http服务,比如buffer池空闲机器借还,上架等,这类服务用户调用出错时处理代价较大,要确保用户调用时服务正常,需要有主动拨测的机制,先于用户发现并修复问题,在云函数出现之前,需要开发拨测工具,实现定时调用,并实现工具本身的故障容灾能力,且要申请2台以上的虚拟机或容器发布部署,既耗费人力,也耗费资源。应用云函数后,我们只需简单的3步便可实现:
2. 配置该函数为定时触发,比如5分钟触发一次,如下图所示,配置完成后服务即刻启用。
3.可以在日志页面查看函数运行状态,当检测到异常时,会调用告警工具发送告警微信。
在主动拨测工具这个场景,我们从云函数获得的收益主要是快速成形,且无需运营维护,达到了既定目标同时,没有额外增加运营成本。SCF无服务器云函数为每个用户设置了免费额度,该应用场景几乎肯定能包含在免费额度之内。
我们之前用python开发了大量统计脚本,用来展现平台的运营概况,可用性,质量,趋势等,由crontab驱动每日定期执行,随着时间累积及人员的更替,这些脚本部署管理逐步混乱,比如想要修复某个数据时,可能不知道脚本部署在哪,或者某天服务器故障,恢复统计脚本的正常运营比较麻烦,针对这些问题,我们利用云函数简单包装便可解决,比如下面是一个统计运营中母机数的函数,直接import原脚本,在入口函数内调用即可。
# -*- coding: utf-8 -*- from ctypes import * import os import base64 import json import calculate_biz_host_num def main_handler(event, context): return calculate_biz_host_num.main() if __name__ == '__main__': '''just for test''' event = {} lambda_handler(event, 4)
在规整运营统计脚本这个场景,我们从云函数获得的主要收益是快速帮助我们把散落到各台服务器的脚本规整起来统一维护,且再也不用担心统计脚本运营与服务器故障问题。
我们有一个图片压缩服务,上传时压缩图片以降低存储容量及下载带宽消耗,压缩的效果要达到图片质量与压缩比的均衡,在某些场景,比如微信朋友圈,存在一些广告图片,用户一般不会关注其细节,故可以提高压缩比,牺牲质量以进一步的降低运营成本,而图片类型的识别计算复杂度高,无法在逻辑svr本地完成,传统的办法是实现一个图片类型识别服务,但实现该服务需要开发工作量较大,比如需要写接入,逻辑server,实现容灾分布,负载均衡等,且由于图片上传有明显的波峰波谷效应,还需要实现自动扩缩容,不仅如此,部署也较为复杂,难以满足快速试错的需求。
应用云函数后,我们只需创建并实现一个类型识别函数,如下所示,在函数里调用算法工程师实现的C++图片识别程序即可,无须关心容灾分布,负载均衡,自动扩缩容及服务的部署与运维等。
# -*- coding: utf-8 -*- from ctypes import * import os import pictype import base64 import json def main_handler(event, context): str = pictype.cppmain(event["pic_data"]) jso = json.loads(str) print jso["QRCode"] return str if __name__ == '__main__': '''just for test''' event = {} imageFile = open("2qrcode.jpg","rb") event["pic_data"] = base64.b64encode(imageFile.read()) imageFile.close() lambda_handler(event, 4)
在嵌入图片类型识别功能这个场景,我们从云函数获得的主要收益是使用极小的成本便快速扩展了现有平台的能力,短时间内便试错验证了依据图片类型选择不同压缩比在运营成本上的收益。
尝到甜头后,我们越来越有信心使用云函数来实现更复杂的需求,正好当前在支持游戏AI团队做一些计算,典型的AI计算过程如下图所示,模型训练前的数据预处理耗费了大量的时间与计算资源。
以王者荣耀的AI为例,如下图所示,数据预处理一般分为两步:
我们应用云函数实现该预处理,只需实现mapper/reducer计算函数,并配置合适的计算触发规则即可,比如实现Mapper函数如下所示(省略若工具型函数代码),并配置为cos上传触发,这样当有录像文件上传时,可自动调用mapper函数转化为hdf5文件。
# -*- coding utf-8 -*- import os import sys import datetime import traceback import shutil import commands import cos_sdk def main_handler(event, context): res = map_caller(event, context) if res == 0: return "succ" else: return "fail" def map_caller(event, context): # Note: this is test account, change to own cos appid and secret_id appid = '123443xxxx' secret_id = 'QmFzZTY0IGlzIGEgZ2Vxxxx' secret_key = 'AKIDZfbOA78asKUYBcXFrJD0a1ICvxxxx' host = 'sz.cxxxxxx' addr = '10xxxx' bucket = event['bucket'] cos_input_file = event['input'] cos_output_key = event['output'] cos_file_name = cos_input_file.split('/', 1)[1] print("cos_file_name: ", cos_file_name) # step 1. Download .abs file from cos cos = cos_sdk.CosHandler(appid, bucket, secret_id, secret_key, host, addr ) container_base_path = '/tmp/AITest/mapper' container_input_path = '/tmp/AITest/mapper/input/' container_output_path = '/tmp/AITest/mapper/output/' cos_output_path = 'mapdata/' try: if not os.path.exists(container_base_path): os.makedirs(container_base_path) if not os.path.exists(container_input_path): os.mkdir(container_input_path) if not os.path.exists(container_output_path): os.mkdir(container_output_path) except: traceback.print_exc() return -1 ret = cos.download_file('/', cos_input_file, container_input_path, cos_file_name) if not ret: print("Download file from cos Failed [%s]" % cos_file_name) return -1 print("Download file [%s] succ" % cos_file_name) # step 2. transfer .abs file to .hdf5 ret = transfer_data(container_base_path, container_input_path, cos_file_name, container_output_path) if not ret: print("transfer data fail") return -1 # step 3. upload .hdf5 file to cos output_filename = get_output_file(cos_file_name, container_output_path) if output_filename == "": return -1 print(container_output_path+output_filename) if not os.path.exists(container_output_path+output_filename): print "output file not exist" print(cos_output_path+output_filename) ret = cos.upload_file(container_output_path+output_filename, cos_output_path+output_filename) if not ret: return -1 ## clean up result files shutil.rmtree(container_output_path) return 0 def transfer_data(base_path, file_path, file_name, output_path): try: CurPath = '/var/user' if os.path.exists(base_path + '/transfer_script'): shutil.rmtree(base_path + '/transfer_script') shutil.copytree(CurPath + '/transfer_script', base_path + '/transfer_script') StartPath = base_path + "/transfer_script/5v5_vecmodel_tactics" InputFilePath = file_path + file_name OutputPath = output_pat sgameBinFile = base_path + "/transfer_script/log_transform/bin/transform/sgame_log_transform" os.chmod(sgameBinFile, 755) labelBinFile = StartPath + "/label" featureBinFile = StartPath + "/VecFeatureExtract" os.chmod(labelBinFile, 755) os.chmod(featureBinFile, 755) os.chdir(StartPath) cmd = 'sh start.sh ' + InputFilePath + ' ' + OutputPath (status, output) = commands.getstatusoutput(cmd) print("status: ", status) print("output: ", output) except: traceback.print_exc() return False if status == 0 and output == "pipeline success": return True else: return False
实现Reducer函数如下所示(省略若工具型函数代码),亦可配置cos写文件触发,当上传文件数达到一定数量且符合其他条件时,执行reducer函数的处理功能。
# -*- coding utf-8 -*- import os import sys import traceback import shutil import commands import re import common import cos_sdk try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET def main_handler(event, context): res = reducer_caller(event, context) if res == False: return "fail" else: return "succ" def reducer_caller(event, context): # Note: this is test account, change to own cos appid and secret_id appid = '12344321xxx' secret_id = 'QmFzZTY0IGlzIGEgZ2xxxx' secret_key = 'AKIDZfbOA78asKUYBcXFrJD0axxx' host = 'sz.xxxx' addr = '10.x