一次 Serverless 架构改造实践:基因样本比对

云计算
本文将从代码的角度,通过改造一个 Python 应用来帮助读者从侧面理解 Serverless,让应用继承 Serverless 架构的优点。

Serverless 是一种新兴的无服务器架构,使用它,开发者只需专注于代码,无需关心运维、资源交付或者部署。

本文将从代码的角度,通过改造一个 Python 应用来帮助读者从侧面理解 Serverless,让应用继承 Serverless 架构的优点。

[[230487]]

现有资源:

  •  一个成熟的基因对比算法(Python实现,运行一次的时间花费为 2 秒)
  •  2020 个基因样本文件(每个文件的大小为 2M,可以直接作为算法的输入)
  • 一台 8 核心云主机

基因检测服务

我们使用上面的资源来对比两个人的基因样本并 print 对比结果(如:有直系血缘关系的概率)

我们构造目录结构如下:

 

  1. ├── relation.py 
  2. └── samples 
  3.     ├── one.sample 
  4.     └── two.sample 

relations.py 代码如下:

 

  1. import sys 
  2.  
  3. def relationship_algorithm(human_sample_one, human_sample_two): 
  4.     # it's a secret 
  5.     return result 
  6.  
  7. if __name__ == "__main__": 
  8.     length = len(sys.argv) 
  9.     # sys.argv is a list, the first element always be the script's name 
  10.     if length != 3: 
  11.         sys.stderr.write("Need two samples") 
  12.     else
  13.         # read the first sample 
  14.         with open(sys.argv[1], "r") as sample_one: 
  15.             sample_one_list = sample_one.readlines() 
  16.         # read the second sample 
  17.         with open(sys.argv[2], "r") as sample_two: 
  18.             sample_two_list = sample_two.readlines() 
  19.         # run the algorithm 
  20.         print relationship_algirithm(sample_one_list, sample_two_list) 

使用方法如下:

 

  1. ➜ python relation.py ./samples/one.sample ./samples/two.sample 
  2. ➜ 0.054 

流程比较简单,从本地磁盘读取两个代表基因序列的文件,经过算法计算,***返回结果

我们接到了如下业务需求

假设有 2000 人寻找自己的孩子,20 人寻找自己的父亲

首先收集唾液样本经过专业仪器分析后,然后生成样本文件并上传到我们的主机上,一共 2020 个样本文件,***我们需要运行上面的算法

  • 2000 * 20 = 40000(次)

才可完成需求,我们计算一下总花费的时间:

  • 40000(次) * 2(秒)= 80000 (秒)
  • 80000(秒)/ 60.0 / 60.0 ≈ 22.2(小时)

串行需要花费 22 小时才能算完,太慢了,不过我们的机器是 8 核心的,开 8 个进程一起算:

  • 22.2 / 8 ≈ 2.76(小时)

也要快 3 个小时,还是太慢,假设 8 核算力已经到极限了,接下来如何优化呢?

1.1.1 介绍一种 Serverless 产品:UGC

UCloud General Compute

一次 Serverless 架构改造实践:基因样本比对

与 AWS 的 lambda 不同,UGC 允许你将计算密集型算法封装为 Docker Image (后文统称为「算法镜像」),只需将算法镜像 push 到指定的算法仓库中,UGC 会将算法镜像预先 pull 到一部分计算节点上,当你使用以下两种形式:

  • 算法镜像的名字和一些验证信息通过 querystring 的形式 (例如:http://api.ugc.service.ucloud.cn?ImageName=relation&Token=!Q@W#E)
  • 算法镜像所需的数据通过 HTTP body 的形式

特别构造的 HTTP 请求发送到 UGC 的 API 服务时,「任务调度器」会帮你挑选已经 pull 成功算法镜像的节点,并将请求调度过去,然后启动此算法镜像「容器」将此请求的 HTTP body 以标准输入 stdin 的形式传到容器中,经过算法计算,再把算法的标准输出 stdout 和标准错误 stderr 打成一个 tar 包,以 HTTP body 的形式返回给你,你只需要把返回的 body 当做 tar 包来解压即可得到本次算法运行的结果。

讲了这么多,这个产品使你可以把密集的计算放到了数万的计算节点上,而不是我们小小的 8 核心机器,有数万核心可供使用,那么如何使用如此海量的计算资源呢,程序需要小小的改造一下

1.1.2 针对此 Serverless 架构的改造

两部分:

  •  改造算法中元数据从「文件输入」改为「标准输入」,输出改为「标准输出」
  •  开发客户端构造 HTTP 请求,并提高并发

1. 改造算法输入输出

① 改造输入为 stdin

  1. cat ./samples/one.sample ./samples/two.sample | python relation.py 

这样把内容通过管道交给 relation.py 的 stdin,然后在 relation.py 中通过以下方式拿到:

 

  1. import sys 
  2.  
  3. mystdin = sys.stdin.read() 
  4. # 这里的 mystdin 包含 ./samples/one.sample ./samples/two.sample 的全部内容,无分隔,实际使用可以自己设定分隔符来拆分 

② 将算法的输出数据写入 stdout

 

  1. # 把标准输入拆分为两个 sample 
  2. sample_one, sample_two = separate(mystdin) 
  3.  
  4. # 改造算法的输出为 STDOUT 
  5. def relationship_algorithm(sample_one, sample_two) 
  6.     # 改造前 
  7.     return result 
  8.     # 改造后 
  9.     sys.stdout.write(result) 

到此就改造完了,很快吧

2. 客户端与并发

刚才我们改造了算法镜像的逻辑(任务的执行),现在我们来看一下任务的提交:

构造 HTTP 请求并读取返回结果

 

  1. imageName = "cn-bj2.ugchub.service.ucloud.cn/testbucket/relationship:0.1" 
  2. token = tokenManager.getToken() # SDK 有现成的 
  3.  
  4. # summitTask 构造 HTTP 请求并将镜像的 STDOUT 打成 tar 包返回 
  5. response = submitTask(imageName, token, data) 

它也支持异步请求

之前提到,此 Serverless 产品会将算法的标准输出打成 tar 包放到 HTTP body 中返回给客户端,所以我们准备此解包函数:

 

  1. import tarfile 
  2. import io 
  3.  
  4. def untar(data): 
  5.     tar = tarfile.open(fileobj=io.BytesIO(data)) 
  6.     for member in tar.getmembers(): 
  7.         f = tar.extractfile(member) 
  8.         with open('result.txt','a') as resultf: 
  9.             strs = f.read() 
  10.             resultf.write(strs) 

解开 tar 包,并将结果写入 result.txt 文件

假设我们 2200 个样本文件的绝对路径列表可以通过 get_sample_list 方法拿到

sample_2000_list, sample_20_list = get_sample_list()

计算 2000 个样本与 20 个样本的笛卡尔积,我们可以直接使用 itertools.product

 

  1. import itertools  
  2. all = list(itertools.product(sample_2000_list, sample_20_list))  
  3. assert len(all) == 40000 

结合上面的代码段,我们封装一个方法:

 

  1. def worker(two_file_tuple): 
  2.     sample_one_dir, sample_two_dir = two_file_tuple 
  3.  
  4.     with open(sample_one_dir) as onef: 
  5.         one_data = onef.read() 
  6.     with open(samle_two_dir) as twof: 
  7.         two_data = twof.read() 
  8.      
  9.     data = one_data + two_data 
  10.     response = summitTask(imageName, token, data) 
  11.     untar(response) 

因为构造 HTTP 请求提交是 I/O 密集型而非计算密集型,所以我们使用协程池处理是非常高效的:

 

  1. import gevent.pool 
  2. import gevent.monkey 
  3. gevent.monkey.patch_all() # 猴子补丁 
  4.  
  5. pool = gevent.pool.Pool(200) 
  6. pool.map(worker, all

只是提交任务 200 并发很轻松

全部改造完成,我们来简单分析一下:

之前是 8 个进程跑计算密集型算法,现在我们把计算密集型算法放到了 Serverless 产品中,因为客户端是 I/O 密集型的,单机使用协程可以开很高的并发,我们不贪心,按 200 并发来算:

一次 Serverless 架构改造实践:基因样本比对

进阶阅读:上面这种情况下带宽反而有可能成为瓶颈,我们可以使用 gzip 来压缩 HTTP body,这是一个计算比较密集的操作,为了 8 核心算力的高效利用,可以将样本数据分为 8 份,启动 8 个进程,进程中再使用协程去提交任务就好了。

  • 40000 * 2 = 80000(秒)
  • 80000 / 200 = 400(秒)

也就是说进行

一组检测只需要 400 秒,从之前的 7 天提高到 400 秒,成果斐然,图表更直观:

一次 Serverless 架构改造实践:基因样本比对

 

而且算力瓶颈还远未达到,任务提交的并发数还可以提升,再给我们一台机器提交任务,便可以缩短到 200 秒,4 台 100 秒,8 台 50 秒...

最重要的是改造后的架构还继承了 Serverless 架构的优点:

  • 免运维
  • 高可用
  • 按需付费
  • 发布简单

1. 免运维 -- 因为你没有服务器了...

2. 高可用 -- Serverless 服务一般依托云计算的强大基础设施,任何模块都不会只有单点,都尽可能做到跨可用区,或者跨交换机容灾,而且本次使用的服务有一个有趣的机制:同一个任务,你提交一次,会被多个节点执行,如果一个计算节点挂了,其他节点还可以正常返回,哪个先执行完,先返回哪个。

3. 按需付费 -- 文中说每个算法执行一次花费单核心 CPU 时间 2 秒,我们直接算一下花费

  • 2000 * 20 * 2 = 80000(秒)
  • 80000 / 60 / 60 = 22.22(小时)
  • 22.22(小时) * 0.09(元) * 1(核心) ~= 2(元)
  • 每核时 0.09 元(即单核心 CPU 时间 1 小时,计费 9 分钱)

4. 发布简单 -- 因为使用 Docker 作为载体,所以它是语言无关的,而且发布也很快,代码写好直接上传镜像就好了,至于灰度,客户端 imageName 指定不同版本即可区分不同代码了

不同的 Serverless 产品可能有不同的改造方法,作为工程师,我比较喜欢这种方式,改造成本低,灵活性高,你觉得呢?如果对 Serverless 架构或者 UGC 感兴趣的话可以添加 u_nknow 微信拉你进交流群哦

责任编辑:未丽燕 来源: 51CTO.com
相关推荐

2017-12-07 12:47:48

Serverless架构基因

2017-06-12 11:09:56

计数架构数据库

2017-05-29 08:18:11

Serverless架构软件系统

2018-05-20 15:32:56

2020-07-14 15:10:21

Redis架构代码

2017-08-08 12:50:51

Serverless云端数据库

2023-11-06 07:45:42

单据图片处理

2010-04-01 22:16:21

2022-03-23 15:43:26

Android客户端架构

2019-04-18 14:06:35

MySQL分库分表数据库

2019-01-21 11:17:13

CPU优化定位

2013-04-02 14:27:02

架构架构评审

2020-10-15 14:05:30

PostgreSQL升级开发

2011-06-28 10:41:50

DBA

2020-10-24 13:50:59

Python编程语言

2021-12-27 10:08:16

Python编程语言

2015-07-14 10:34:42

ViewModel代码高效

2020-12-29 09:23:40

分库分表订单

2021-09-13 18:09:59

腾讯文档业务云计算

2015-10-26 16:38:17

点赞
收藏

51CTO技术栈公众号