前言

人工智能场景中的性能测试与我们在互联网中创建到的有很大的不同,因为它需要模拟更复杂的情况。当然它也有相似的地方,只不过今天我们主要介绍它们不同的地方。

产品分类

首先我们需要澄清一下, 从 AI 产品的类型来划分的话,我们可以分成两个大的类别:

在这个背景下,我们可以知道人工智能的平台类产品测试是更有难度的,它不是只对着一个模型,而是涉及到了 AI 的整个生命周期。 而我们第一个性能测试场景,就是针对模型训练的。

结构化数据模拟

在大数据与人工智能领域中的性能测试, 往往要涉及到数据模拟。 因为人工智能的基础是大数据, 只有海量的高质量数据才能支持人工智能。 所以我们在做性能测试的时候,也需要构造大量的数据来支撑模型的训练。 主要是为了衡量在标准数据下,算法需要多长时间才能把模型训练出来。 所以需要构建各种不同的标准数据。比如:

接下来要仔细的解释一下这几种数据场景:

造数工具

造数工具的重点是可以模拟出上面说这些不同的数据类型, 以及性能要符合预期。 毕竟我们在建模的时候动不动就要模拟几千万甚至几亿的,所以性能一定不能太差, 要是造个数据就要等好几天的也是无法接受的。 所以我们一般也决定使用分布式计算技术来造数, 而我这里选择的就是 spark。

模拟设备规模

在计算机视觉场景中,我们可能往往要面对大量的设备模拟工作。 因为在系统中我们的数据都是从各个摄像设备中采集到视频流,并进行解码抽帧等工作得到图片的。 而在这种情况下的性能测试,我们需模拟大量的设备来制造系统压力(上面的场景是模拟结构化数据来制造压力, 而这个就是模拟视频流来制造压力)。当然我们不能真的去架设海量的摄像头。所以我们不需要模拟设备,而是模拟视频流就可以了(设备和产品的通信一般都是通过视频流的,比如 rtsp 的实时视频流协议)。所以我们的解决方案是下面这个样子的:

假设我们需要模拟 1000 路摄像头,那就可以用 ffmpeg + easydarwin 生成 1000 个 rtsp 流地址并配置到被测的系统中。 当然模拟这么大规模的视频流数据,需要给流媒体服务器相当的资源才可以。

模拟节点规模

在上面的模拟设备规模的场景中,我们往往也需要面对边缘计算系统的情况。 因为我们的视频设备往往都假设在靠近用户的地方(原理中心集群),比如路边的摄像头,抓拍机等等。 由于我们要面对数据安全,网络带宽和性能等问题,不能把数据传回中心集群处理。所以需要用到边缘计算

它的业务流程可能是下面这个样子的:

我喜欢把这种架构简称为云边端,分别代表了在边缘计算中这 3 种不同的角色。在计算机视觉场景中,客户的设备可能存在于园区/工厂/学校/社区的摄像头或其他照相设备。这些设备更靠近终端的人而非中心集群,它们收集到图像数据后再经过一系列的处理(解码,抽帧,过滤等)再传输到边缘机房部署的模型服务中进行识别,比如识别人体的行为(打架,离岗,跌倒,野泳等),人体的属性(安全帽,手套,安全带等),又或者识别烟雾火灾人脸对比等等。

完成这样的架构是非常困难的,从物理设施角度看,通常终端设备分布在非常多的地域中,企业需要在这些地域架设大量的机房。而从软件角度看,产品的架构要能够完成所有边缘节点的统一管理,完成机器的区域划分,服务的统一下发以及网络的分发策略等等。 为了支持这种部署架构,人们寻求了很多的解决方案,尤其对当前最主流的云平台技术--K8S 中开发相关功能的呼声尤其强烈。 目前国内比较主流的边缘计算项目有 superedge、OpenYurt 和 KubeEdge,他们都是基于 k8s 扩展而来。 事实上人工智能产品需要的调度需求会更多,比如对于 GPU、FPGA、TPU 的调度,对于大型存储设备的调度,对于网络流量的调度等等。

所以我们除了要模拟大规模路数的视频流之外, 还需要模拟大规模的节点数量来验证集群的性能是否达标。

如果按照传统的思路,测试团队需要申请大量的虚拟机来组建 K8S 集群,但这无疑是成本非常高的选择,因为在实际项目中我们往往需要测试数百甚至数千台机器规模的集群。所以换一种思路,看一下 K8S 的集群架构:

K8S 集群中每台节点中都会启动一个名为 kubelet 和 kubeproxy 的进程。kubelet 负责与 docker/containerD 通信,维护容器状态并周期性的向 APIServer(K8S 集群中的主要服务)上报节点和容器状态,而 kubeproxy 则负责容器网络规则维护。 而 APIServer 则会负责把用户创建容器的请求以及 kubelet 上报的容器和节点状态存储在 ETCD 中。可以看出来 API Server 是维护 K8S 集群状态最关键的服务,一旦 API Server 出现问题整个集群都会陷入异常状态。而通过上述两点的描述也可以知道随着集群内节点和 Pod 的增多,API Server 承受的压力也会越来越大。除此之外在商业产品中往往会由于各种需要自研许多组件与 API Server 进行交互,这些组件都会给 API Server 带来不小的压力。所以 API Server 的性能数据往往是测试中最关键的指标之一。业界也会推荐在 K8S 集群中的 etcd 要使用高性能固态硬盘进行部署,因为 etcd 的性能也是影响 API Server 最主要的因素。同时还需要注意的是除了 API Server 以外 K8S 还有一些其他组件也是随着 Pod 数量增长而增加压力的(比如调度器和用户自研 operator)。

所以随着节点和容器的增多,对集群的主要压力在于 etcd 和 apiserver 这些组件。只要我们模拟真实的 kubelet 来上报容器状态给 APIServer 增加压力,那其实是不是有一堆真实的节点和容器并不重要。我们构建一些虚假的 kubelet 就可以达到目标了。

kubemark 是一款针对 k8s 的性能测试工具,它能够使用少量的资源模拟出一个大规模 K8S 集群。为了演示这个工具需要一个待测试的 K8S 集群 A 以及一个装载 Kubemark 服务的 K8S 集群 B(也称 Kubemark 集群),当然也可以使用同一个集群装载 Kubemark,只不过在实践过程中我们习惯将待测试集群和 Kubemark 集群区别开来。这时在 B 集群中部署 Kubemark 的 Hollow Pod,这些 Pod 会主动向 A 集群注册并成为该 K8S 集群中的虚拟节点。也就是说在 B 集群中启动的 Hollow Pod 模拟了 K8S 的 kubelet 和 kube-proxy 并与 A 集群的 API Server 进行交互,让 A 集群误以为这些 Hollow Pod 是真实的节点。这些虚拟节点会完全模拟真实节点的行为,比如它会定时向 API Server 上报节点和 Pod 的状态,也会在用户发起创建 Pod 的请求时进行响应,只不过它不会真的去启动容器而已。也就是说虚拟节点除了不会真的启动容器外,其他的大部分功能都与真实节点差不多。这样设计的目的是为了能够模拟真实的节点对 API Server 等组件造成的压力。这样测试人员就可以避免耗费巨额的成本去搭建真实的集群就可以评估 K8S 的性能了。我们可以认为 kubemark 就像是一个复杂的 mock 服务一样,不必启动容器,但可模拟 kubelet 的行为。 如图:

大家感兴趣的可以去搜一下 kubemark 的文档看看它如何使用。

海量小文件的构建

spark 虽然可以控制数据的分片数量, 但它无法构建非结构化数据(图片,视频,音频)也无法构建过于庞大的文件数量(比如数亿个文件)。 所以我们需要另外一种方法来构建这种量级的数据。通常模拟这样的数据是为了做计算机视觉的模型训练的性能测试,或者测试存储系统本身的性能。 构造这样的数据我们要面临的问题:

IO:不管是生成图片,还是文件都会消耗网络和磁盘 io, 数亿张图片对于 IO 的考验是比较大的
CPU:如果按照传统的思路,为了提升造数性能, 会开很多个线程来并发生成图片,计算元数据和数据交互。 但线程开的太多 CPU 的上下文切换也很损耗性能。 尤其我们使用的是 ssd 磁盘,多线程的模式可能是无法最大化利用磁盘 IO 的。

所以我们的主要问题在于如何充分的利用 IO,要尽量的打到 IO 瓶颈。后面经过讨论, 最后的方案是使用 golang 语言, 用协程 + 异步 IO 来进行造数:

原谅我懒了, 上面这个方案的架构图我实在是不想画了, 大家见谅。

尾声

今天就先这样吧, 我了解到的比较典型的,并且比较有技术含量的数据构建场景就这些了。 顺便在推销一下我的星球,我会固定更新手把手的教学资料,都是我一个字一个字吗出来的,不是网络搬运的。


↙↙↙阅读原文可查看相关链接,并与作者交流