如何利用gatling创建一个jmeter性能测试实例例

从0到1构建美团压测工具 - 文章 - 伯乐在线
& 从0到1构建美团压测工具
美团内部的RPC服务大多构建在Thrift之上,在日常开发服务的过程中,需要针对这些服务进行压力测试(以下简称压测)来发现潜在问题。常用的方法有:
使用一些脚本语言如:Python、Ruby等,读取线上日志构建请求,用多线程模拟用户请求进行压测
使用开源工具进行压测
然而,无论采取哪种方法,压测都是一个十分耗时而又繁琐的过程,主要痛点有:
需要写很多代码解析日志,还原请求,对于比较复杂的请求,解析很容易出错
需要搭建脚本或者工具的运行环境,通常这一过程比较耗时
由于打压方法没有统一,导致打压的结果指标比较混乱,有的结果甚至以终端输出的方式展示,非常不直观
对一个应用的打压测试,由于环境、代码的问题,导致组内同学很难共享
针对上述问题,提供一个简单好用的压测工具是十分有必要的。
是否有必要重复造轮子
在构建压测工具之前,对于一些现有的开源工具进行了调研。现在主流的压测工具主要有以下几个:
是一个比较老牌的压测工具,主要针对HTTP服务进行打压,该工具在以下方面并不满足美团内部的压测需求:
默认不支持Thrift的打压测试
需要本地安装,并且配置复杂
对于用户操作并不友好
twitter/iago
是一个由Twitter开源的压测工具,支持对HTTP、Thrift等服务进行压测,其主要问题如下:
对每个压测应用都需要创建一个项目
压测结果并不直观
流量重放依赖本地文件
项目依赖于一个较老版本的Scala,搭建不便
相关文档比较少
除此之外,当时还考察了、、 等一些常见的压测工具,都因为适用场景和美团的需求有些出入而排除了。
综上,针对当前压测工具的一些现状,构建一个简单易用的压测工具还是很有必要的。
针对之前提到的痛点,新的压测工具主要提供以下功能:
线上流量拷贝
简单易用的操作界面(接入压测的时间应该控制在1小时以内)
清晰的图表能反映压测应用的各项指标
满足包括Thrift、HTTP等服务的压测需求
目标已经明确,怎么实现呢?首先是抽象压测的过程。
一个典型的压测过程如图所示,首先在init方法里面,进行一些初始化的工作,比如连接数据库,创建客户端等。接下来,在run方法里面发出压测请求,为了保证能够对服务产生足够的压力,这里通常采用多线程并发访问,同时记录每次请求的发起时间和结束时间,这两个时间的简单相减就能够得到每次请求的响应时间,利用该结果就可以计算出TP90、平均响应时间、最大响应时间等指标,等压测结束后,通过destroy方法进行资源回收等工作。
以上过程可以用接口表示,无论是压测Thrift服务还是HTTP服务,本质上都是这三个方法实现的不同。考虑到压测工具的灵活性和通用性,压测工具可以将这个接口交给打压测试的同学实现,而压测工具则重点实现多线程打压,打压结果的聚合等比较耗时的工作。
interface Runner {
def init(Test app) // 初始化压测
def run(Test app, String log) // 每次打压请求,传入log方便构建请求
def destroy(Test app) // 压测完毕后,回收资源
<div class="crayon-num" data-line="crayon-5a04de<div class="crayon-num crayon-striped-num" data-line="crayon-5a04de<div class="crayon-num" data-line="crayon-5a04de<div class="crayon-num crayon-striped-num" data-line="crayon-5a04de<div class="crayon-num" data-line="crayon-5a04de
interface Runner {&&&&def init(Test app) // 初始化压测&&&&def run(Test app, String log) // 每次打压请求,传入log方便构建请求&&&&def destroy(Test app) // 压测完毕后,回收资源}
Thrift服务打压的难点之一就是如何简单地拷贝线上真实流量用来构建打压请求。一些大型的Thrift服务数据结构非常复杂,写打压脚本的时候需要很多代码来解析日志,而且容易出错。 因此提供一个简单好用的拷贝流量方法是十分有必要的。
在这里压测工具提供了一个叫VCR(录像机)的工具来拷贝流量。VCR能够将线上的请求序列化后写到Redis里面。
考虑到用户需要查看具体请求和易用性等需求,最终选取了JSON格式作为序列化和反序列化的协议。同时需要部署在生产环境,为了降低对线上服务的影响,这里采取了单线程异步写的方式来拷贝流量。
应用打压完成后,需要一些指标来评估压测结果,常见的指标有:
最大响应时间
平均响应时间
压测工具采用了
来完成数据的聚合工作。
以TP90为例子,仅需要一行查询就能实现需求。
SELECT PERCENTILE(response_time, 90) FROM test_series GROUP BY time(10s)
<div class="crayon-num" data-line="crayon-5a04de
SELECT PERCENTILE(response_time, 90) FROM test_series GROUP BY time(10s)
整体而言,整个打压过程如下:
美团内部的服务大多使用Java来构建,VCR以Maven Package的方式提供给用户。
对用户来说只需要2行代码可以拷贝流量。
为了不影响线上服务,通常选取单台机器进行流量拷贝工作。
public class TestAppRPC implements TestApp.Iface {
private Vcr _vcr = new Vcr("testapp"); // 指定拷贝流量的key
public TestResponse echo(TestRequest req) throws TException {
_vcr.copy(req); // 拷贝操作
long start = System.currentTimeMillis();
TestResponse response = new TestResponse();
<div class="crayon-num" data-line="crayon-5a04de<div class="crayon-num crayon-striped-num" data-line="crayon-5a04de<div class="crayon-num" data-line="crayon-5a04de<div class="crayon-num crayon-striped-num" data-line="crayon-5a04de<div class="crayon-num" data-line="crayon-5a04de<div class="crayon-num crayon-striped-num" data-line="crayon-5a04de<div class="crayon-num" data-line="crayon-5a04de<div class="crayon-num crayon-striped-num" data-line="crayon-5a04de<div class="crayon-num" data-line="crayon-5a04de<div class="crayon-num crayon-striped-num" data-line="crayon-5a04de<div class="crayon-num" data-line="crayon-5a04de<div class="crayon-num crayon-striped-num" data-line="crayon-5a04de
public class TestAppRPC implements TestApp.Iface {&&&&&private Vcr _vcr = new Vcr("testapp"); // 指定拷贝流量的key&&&&&@Override&&&&public TestResponse echo(TestRequest req) throws TException {&&&&&&&&_vcr.copy(req); // 拷贝操作&&&&&&&&long start = System.currentTimeMillis();&&&&&&&&TestResponse response = new TestResponse();&&&&&&&&return response;&&&&}}
一旦流量拷贝完成后,通过Web界面,用户能够查看日志的收集情况和单条日志的详情。
压测逻辑实现
压测工具采用Groovy来进行编写。对每个应用来说,只需要实现runner接口就可以实现对应用的打压。
interface Runner {
def init(Test app)
def run(Test app, String log)
def destroy(Test app)
interface Runner {&&&&def init(Test app)&&&&def run(Test app, String log)&&&&def destroy(Test app)}
以Thrift服务为例:
class TestServiceRunner implements Runner {
RPCService.Client _client
TTransport _
def init(Test app) {
def conf = app.config // 读取应用配置
_transport = new TFramedTransport(new TSocket(conf.get("thrift_service_host") as String, conf.get("thrift_service_port") as int))
TProtocol protocol = new TBinaryProtocol(_transport)
_client = new RPCService.Client(protocol)
_transport.open()
def run(Test app, String log) {
TestRequest req = Vcr.deSerialize(log, TestRequest.class) // 将拷贝流量反序列化
_client.echo(req) // 发送请求
def destroy(Test app) {
_transport.close() // 关闭服务
12345678910111213141516171819202122232425
class TestServiceRunner implements Runner {&&&&&RPCService.Client _client&&&&TTransport _transport;&&&&&@Override&&&&def init(Test app) {&&&&&&&&def conf = app.config // 读取应用配置&&&&&&&&_transport = new TFramedTransport(new TSocket(conf.get("thrift_service_host") as String, conf.get("thrift_service_port") as int))&&&&&&&&TProtocol protocol = new TBinaryProtocol(_transport)&&&&&&&&_client = new RPCService.Client(protocol)&&&&&&&&_transport.open()&&&&}&&&&&@Override&&&&def run(Test app, String log) {&&&&&&&&TestRequest req = Vcr.deSerialize(log, TestRequest.class) // 将拷贝流量反序列化&&&&&&&&_client.echo(req) // 发送请求&&&&}&&&&&@Override&&&&def destroy(Test app) {&&&&&&&&_transport.close() // 关闭服务&&&&}}
实现以上接口后,就可以对应用进行打压了。
用户可以通过Web界面创建应用,除了必填配置以外,用户可以按照应用灵活配置。
用户可以通过直观的图表来查看应用的各种性能指标。
压测工具上线以来,已经接入了20多个应用,完成数百次打压实验,现在应用的接入时间仅需要15~30分钟。保证了美团服务的稳定和节省了开发同学的时间,使大家告别了以往繁琐冗长的打压测试。
欢迎对这方面有兴趣的同学一起讨论。
可能感兴趣的话题
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
&#8211; 好的话题、有启发的回复、值得信赖的圈子
&#8211; 分享和发现有价值的内容与观点
&#8211; 为IT单身男女服务的征婚传播平台
&#8211; 优秀的工具资源导航
&#8211; 翻译传播优秀的外文文章
&#8211; 国内外的精选文章
&#8211; UI,网页,交互和用户体验
&#8211; 专注iOS技术分享
&#8211; 专注Android技术分享
&#8211; JavaScript, HTML5, CSS
&#8211; 专注Java技术分享
&#8211; 专注Python技术分享
& 2017 伯乐在线热门搜索:
京 东 价:
[定价:¥]
PLUS会员专享价
您购买此商品可享受专属价
增值业务:
重&#x3000;&#x3000;量:
搭配赠品:
所 在 地:北京 朝阳区
服务支持:
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
零成本实现Web性能测试——基于Apache JMeter和Gatling
商品介绍加载中...
扫一扫,精彩好书免费看
服务承诺:
京东平台卖家销售并发货的商品,由平台卖家提供发票和相应的售后服务。请您放心购买!
注:因厂家会在没有任何提前通知的情况下更改产品包装、产地或者一些附件,本司不能确保客户收到的货物与商城图片、产地、附件说明完全一致。只能确保为原厂正货!并且保证与当时市场上同样主流新品一致。若本商城没有及时更新,请大家谅解!
权利声明:京东上的所有商品信息、客户评价、商品咨询、网友讨论等内容,是京东重要的经营资源,未经许可,禁止非法转载使用。
注:本站商品信息均来自于合作方,其真实性、准确性和合法性由信息拥有者(合作方)负责。本站不提供任何保证,并不承担任何法律责任。
印刷版次不同,印刷时间和版次以实物为准。
价格说明:
京东价:京东价为商品的销售价,是您最终决定是否购买商品的依据。
划线价:商品展示的划横线价格为参考价,该价格可能是品牌专柜标价、商品吊牌价或由品牌供应商提供的正品零售价(如厂商指导价、建议零售价等)或该商品在京东平台上曾经展示过的销售价;由于地区、时间的差异性和市场行情波动,品牌专柜标价、商品吊牌价等可能会与您购物时展示的不一致,该价格仅供您参考。
折扣:如无特殊说明,折扣指销售商在原价、或划线价(如品牌专柜标价、商品吊牌价、厂商指导价、厂商建议零售价)等某一价格基础上计算出的优惠比例或优惠金额;如有疑问,您可在购买前联系销售商进行咨询。
异常问题:商品促销信息以商品详情页“促销”栏中的信息为准;商品的具体售价以订单结算页价格为准;如您发现活动商品售价或促销信息有异常,建议购买前先联系销售商咨询。
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
浏览了该商品的用户还浏览了
加载中,请稍候...
价&#x3000;格: 到
&#x3000;&#x3000;&#x3000;
iframe(src='///ns.html?id=GTM-T947SH', height='0', width='0', style='display: visibility:')运用Kubernetes进行分布式负载测试
发表于 22:07|
来源Google Cloud Platform|
作者Google
摘要:谷歌云平台是使用容器进行分布式负载测试的极优环境,其使用的谷歌容器引擎以开源容器集群管理器Kubernetes为动力,将容器作为一级对象对其提供支持。使用容器引擎可以快速搭建容器基础设施,及管理应用部署与资源。
负载测试是开发后台基础架构的重要一环,它不但能够演示系统在真实需求面前的性能表现,还可以通过模拟用户与设备行为,在应用程序部署到生产环境前,找出并了解任何可能的系统瓶颈。
但是,专用的测试基础设施可能非常昂贵且难以维护,且此类设备一般是针对特定性能状况的一次性投资,初期投资后想要再对负载测试进行扩展就十分困难,还可能限制实验,从而导致开发团队的工作效率变低,应用在部署到生产环境前也无法得到充分有效的测试。
解决方案综述
分布式负载测试采用云计算手段,在各种测试场景中这种方案都很有吸引力。云平台使得基础设施平台的弹性得到高度扩展,想要通过大量模拟可产生流量的客户端进行应用和服务测试都十分容易。此外,云计算的定价模式与负载测试的弹性特质非常相符。
无需再运行完整的虚拟机实例了,容器提供的轻量级选择与虚拟客户端的快速扩展完美匹配。由于其轻量级、易于部署、快速可用并适合单一任务等特质,容器是取代运行测试客户端的优秀替代方案。
谷歌云平台是使用容器进行分布式负载测试的极优环境,该平台使用的谷歌容器引擎(Google&Container&Engine)以开源容器集群管理器Kubernetes为动力,将容器作为一级对象对其提供支持。使用容器引擎可以快速搭建容器基础设施,并可用来管理应用部署与资源。
该解决方案演示了使用容器引擎部署分布式负载测试框架的方式。此框架使用多个容器,搭建了一个应用于简易REST-based&API的负载测试通讯。尽管这是用来测试简单Web应用的,但同样的模式可用于创建更为复杂的负载测试场景中,比如游戏或物联网应用中。该方案讨论了基于容器负载测试框架的通用架构。请至本文末尾查看
,逐步学习样例框架的构建。
本方案着重通过容器引擎来创建负载测试通讯,被测系统是一个简单的Web应用,使用了REST的API。借助现有的负载测试框架,塑造出下文中详细描述到的API互动模型。并在完成被测系统的部署后,运用容器引擎来部署分布式负载测试任务。
在软件测试术语中,被测系统(System&Under&Test)指的是该测试设计时所针对的待评估系统。在该方案中,被测系统是一个部署到Google&App&Engine的小型Web应用,该应用通过发布基本REST样式的端点来捕获接收的HTTP&POST请求(接收数据并不连续)。在真实场景中,Web应用可能会很复杂,并包含大量的附加组件及服务,如caching、messaging和persistence,此方案不考虑这些复杂情况。更多在谷歌云平台上构建可扩展的Web应用相关信息,请查看
样例应用的源代码请见文末教程。
示例workload
许多物联网的部署中有类似示例应用模型中所使用的后端服务组件——设备首次注册服务后,开始报告指标或检测器读数,并定期重新进行服务的注册。
下图展示了一个常见的后端服务组件的交互。
该交互可以用这种基于Python的分布式负载测试工具来建模,Locust可以向多个目标路径分发请求,比如向/login和/metrics目标路径分别发送请求;还有很多负载生成软件包也可根据项目需求选择使用,包括、,
而workload将会取决于上面所说到的交互,在Locust会以一组
的模型出现。为了尽量模拟真正的客户端,比如同时有上千个客户端请求接入的情况,每个Locust&task需经过加权。
基于容器的计算
从架构角度来看,部署该分布式负载测试方案有两个主要的组件:Locust容器image,还有容器编排及管理机制。
Locust的容器image是包含Locust软件的
&image,Dockerfile可以在相关的Github库中找到(见教程),而Dockerfile使用了基于Python的image,并使用一些脚本文件来启动Locust服务,执行task。
该方案利用谷歌容器引擎用作容器编排与管理机制。容器引擎是基于开源框架Kubernetes,集合了谷歌多年在容器部署方面运行、编排与管理的经验。基于容器的计算允许开发人员专注于应用本身,无需将精力浪费在繁琐的托管环境部署与集成上。容器同时也使得负载测试更为轻便,通过容器整合后的应用可以在多个云环境中运行。容器引擎与Kubernetes引入了针对容器编排与管理的若干概念。
一个容器集群包含一组云计算引擎(Compute&Engine)实例,为整个应用提供基础。在容器引擎及Kubernetes的文档中,这些实例被称为节点。一个集群包含一个master节点和一到多个worker节点。master节点与worker节点都运行在Kubernetes上,因此容器集群有时也被称为Kubernetes集群。更多集群相关信息请查看
Pod是一组应当被集中部署的紧密耦合容器,一些pod只包含单个容器,例如该案例中,每个Locust容器都运行在自己的pod中。但是通常情况下,pod会包含多个集中执行的容器,例如该案例中,Kubernetes使用了一个包含三个容器的pod提供DNS服务。
在一个容器中,
提供DNS服务功能。SkyDNS依赖于一个名叫etcd的键值存储,而它又被封装在另一个容器中。在pod的第三个容器中,
担任了Kubernetes与SkyDNS之间的桥梁。
复制控制器
一个复制控制器确保特定数量的pod“副本”能够随时运行。如果数量过多,复制控制器会关掉其中一些;如果数量过少,复制控制器会启动一些新的。该方案有三个复制控制器:一个确保DNS&server的单个pod存续;另一个维持Locust的单个master&pod;第三个则保证同时正好有10个Locust的worker&pod运行。
特定pod可能会因为各种原因而消失,包括节点失效或因更新维护而主动进行的节点中断。也就是说,pod的IP地址没有为其提供可靠的接口。更为有效的办法是使用该接口的抽象表示,即使底层pod消失,新的pod产生,IP地址发生变化,该抽象表示不会改变。容器引擎服务通过定义一组逻辑pod及访问相关策略,提供这种类型的抽象接口。在该方案中,有一些代表pod或成组pod的服务。例如,一个服务代表DNS&server,另一个代表Locust&master&pod,还有一个代表那10个worker&pod。
下图展示了master节点与worker节点所包含的内容:
部署被测系统
该方案使用谷歌应用引擎来运行被测系统。部署被测系统需注册可用的谷歌云平台帐号,以安装运行谷歌云平台SDK,之后通过一个命令就可以部署这个样例Web应用了,所需的源代码在文末教程中可以找到。
部署负载测试任务
部署负载测试任务,首先需要部署负载测试master,然后是一组10个的负载测试worker。有了这些工作负载测试,就可以根据测试目的来创建大量通讯了,但需要铭记:与外部系统产生过多通讯与拒绝服务攻击相类似,请务必回顾谷歌云平台的
和谷歌云平台的
负载测试master
部署的第一个组件就是Locust的master,它是执行负载测试任务的入口。部署时将Locust&master部署为只含单个副本的复制控制器,因为我们只需要一个master。一个复制控制器甚至在部署单个pod时都是有效的,因为它能确保高可用性。
复制控制器的配置指定了几个元素,包括控制器的名字(locust-master)、标签(name:&locust,&role:&master)、容器所需要发布的端口(Web接口用8089,与worker通讯用)。这些信息稍后会被用来配置Locust的worker控制器。下面的信息中包含端口的配置:
- name: locust-master-web
containerPort: 8089
protocol: TCP
- name: locust-master-port-1
containerPort: 5557
protocol: TCP
- name: locust-master-port-2
containerPort: 5558
protocol: TCP
下一步,我们会部署一个Service,以确保发布的端口可以通过hostname访问其它pod:集群中的端口以及通过描述性端口名称的referenceable。通过使用服务,即便在master失效,复制控制器又生成了新pod的情况下,我们也可以很容易地找到Locust的worker,并可与master通讯。Locust的master服务也包含在集群层面创建外部转发规则的指令,提供访问集群资源的外部通讯能力。注意:还需创建防火墙规则,以提供访问目标样例的完整入口。
在部署Locust&master之后,就可以通过符合外部转发规则的公开IP地址来访问Web接口了。部署Locust&worker之后,可开启模拟器并通过Locust&Web接口来查看汇总统计。
负载测试worker
下一步部署的组件是Locust&worker,用来执行负载压力测试。Locust&worker是通过能生成10个pod的单个复制控制器来部署的。这些pod分布在Kubernetes的集群中。每个pod通过环境变量来控制重要的配置信息,像是被测系统的hostname和Locust&master的hostname。worker的复制控制器配置方式请查看下面的教程。配置包含控制器的名称、locust-worker、标签(name:&locust,&role:&worker),还有前面描述的环境变量。下面代码包含配置的名称、标签、副本数:
kind: ReplicationController apiVersion: v1 metadata: name: locust-worker
labels: name: locust role: worker spec: replicas: 10 selector: name: locust
role: worker ...
对于Locust&worker来说就无需部署额外服务了,因为worker&pod自身不需支持任何入站通讯——它们直接与Locust&master&pod相连。下图展示了Locust&master与Locust&worker之间的关系。
在复制控制器部署Locust&worker之后,就可以返回Locust&master的Web接口来查看worker部署数量相应的slave数。
执行负载测试任务
开启负载测试
Locust的主Web接口允许执行针对被测系统的负载测试任务,见下图:
开启时指定模拟的用户数、用户应当产生的速率。下一步,点击Start开始模拟。随着时间流逝、用户产生,可以看到统计数据开始按模拟指数进行聚合,像是请求数、每秒请求数,如下图:
停止模拟只需点击Stop,测试就会终止。完整结果可以下载表格查看。
扩展客户端
按比例增加模拟用户会导致Locust&worker&pod数随之增长。在Locust的worker控制器中有详细说明,复制控制器部署10个Locust的worker&pod。通过复制控制器增加pod的数量,Kubernetes提供了不需重新部署即可调整控制器大小的能力。例如,通过kubectl命令行工具可以调整worker&pod的数量。下面的命令可以将Locust的worker&pod数量增加到20:
$ kubectl scale --replicas=20 replicationcontrollers locust-worker
在发出扩容命令后,等待几分钟,所有pod在此时间段内完成部署并启动。所有pod启动后,回到Locust&master的Web接口,重启负载测试。
资源与成本
这个解决方案使用了四个容器引擎节点,每个都受云计算引擎VM标准n1-standard-1类型的支持。你可以使用谷歌云平台的定价计算器估算运行容器集群的月开销。上文提到过,可以按需定制容器集群的大小。定价计算器可以协助你自定义集群特点,借此评估开销的增减。
现在可以查看如何使用容器引擎来创建简单Web应用的负载测试框架了。容器引擎允许你指定建立负载测试框架容器所需的节点数量。容器引擎还允许你将负载测试工作节点合并到pod中,并制定容器引擎运行时想要保持的pod数量。
使用同样的模式来创建不同环境变量与应用的负载测试框架。例如,使用该模式创建信息系统、数据流管理系统与数据库系统的负载测试框架。创建新的Locust任务,甚至是不同的负载测试框架。
扩展框架的另一办法是自定义收集到的指数。例如,你可能想要测量每秒的请求数,或者监听负载增加后的响应延迟情况,或是查看响应失败率与错误类型。有多种可选的监控方式,包括
(Google&Cloud&Monitoring)。
完整教程包含使用说明与源代码,请点击
查看。英文原文:
(译者/孙薇 审校/朱正贵 责编/仲浩)
将于7月26-27日在北京友谊宾馆召开。机器学习与模式识别、大数据的机遇与挑战、人工智能与认知科学、智能机器人四个主题专家云集。人工智能产品库将同步上线,预约咨询:QQ:。欢迎关注。
获取更多Container技术资讯,请扫描下方二维码关注我们
推荐阅读相关主题:
CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
相关热门文章孔夫子旧书网该图书“零成本实现Web性能测试-基于Apache Jmeter和Gatling”已经找不到了, 为您推荐一些相同图书。装订:平装开本:版次:65.20十品装订:平装开本:版次:67.90十品装订:平装开本:版次:30.00十品装订:平装开本:版次:140.00九品装订:平装开本:版次:125.00八五品装订:平装开本:版次:13.00八五品装订:平装开本:版次:163.20十品Copyright(C)
孔夫子旧书网
京ICP证041501号
海淀分局备案编号

我要回帖

更多关于 性能测试怎么设计案例 的文章

 

随机推荐