一种搭建分布式测试环境和批量性能测试的思路

背景

  在搜索引擎的测试过程中,经常会遇到以下两个问题:

  ● 需要搭建和更新分布式测试环境

  ● 在性能测试时,我们需要测试不同集群规模和配置下的环境时,如何自动更新测试环境和批量进行性能测试

  因此,我们需要设计一个脚本,这个脚本可以帮我来完成这些事。

  在这里,我推荐使用Python,理由有:

  ● 写起来比较快(测试时间本来就比较紧张),不可能用C或者Java了

  ● 语法比较清晰,Shell、Perl这些维护起来太乱

  ● 自带的库、第三方的库比较丰富

  ● 另外,我个人比较喜欢Python的mako模版引擎和paramikossh2库。

  其实不用paramiko也可以,只要把机器ssh打通就可以。但我个人不太喜欢这种方式,觉得耦合性太强(只能在Linux下运行了)。

  设计

  批量性能测试的设计

  我很喜欢采用YAML格式,YAML格式的一大好处就是可以很方便的定义List、Map等类型

  1. tasks: 
  2.  # 第一个测试用例,我可能需要测试单线程的情况 
  3.  - 
  4.     id:1# ID的作用是你在脚本中可以拿id作为结果存放的目录 
  5.     parallelNum:1# 并发数 
  6.     seconds:1800# 压半个小时 
  7.     targetHost:10.20.137.22 # 目标主机 
  8.     targetPort:9999 
  9.     queryFilePath:/home/admin/access-log/add-600w.query  # 请求放在这儿 
  10.  # 第2个测试用例,我可能需要测试2线程的情况,这时我就只要再写一个,然后parallelNum: 2就可以了 
  11.  - 
  12.     id:1 
  13.     parallelNum:2 
  14.     seconds:1800 
  15.     targetHost:10.20.137.22 
  16.     targetPort:9999 
  17.     queryFilePath:/home/admin/access-log/add-600w.query

  在阿里的搜索平台这边,我们大多使用abench作为性能测试工具,它是一个命令行工具,只要命令+参数就可以了,比起JMeter要写JMeter脚本简单。因此,我再在配置文件中设计一下abench的命令格式。

  因为在运行命令中,有很多参数需要替换成上述测试用例设定的参数,因此需要采用模版引擎的方式。Python的模版引擎很多,我个人比较推荐mako。

  1. abenchPath:/opt/usr/bin/abench  # abench在哪儿? 
  2. abenchCommand:"${abenchPath} -p ${parallelNum} -s ${seconds} -k --http -o /dev/null ${targetHost} ${targetPort} ${queryFilePath}"

 配置文件设计好了,下面我们来写我们的Python脚本了(这里仅仅给出一些主要代码,大致明白意思就可以了)

  1. import subprocess 
  2. from mako.template import Template 
  3. import yaml 
  4.  
  5. # 运行一个测试任务 
  6. def runTask(config, task): 
  7.     runAbench(config, task) 
  8.  
  9. def runAbench(config, task): 
  10.      # 得到完成的abench运行命令 
  11.      command = Template(config["abenchCommand"]).render( 
  12.          abenchPath=config["abenchPath"], 
  13.          parallelNum=task["parallelNum"], 
  14.          seconds=task["seconds"], 
  15.          targetHost=task["targetHost"], 
  16.          targetPort=task["targetPort"], 
  17.          queryFilePath=task["queryFilePath"], 
  18.          ) 
  19.      pipe = subprocess.Popen(command, 
  20.          stdin=subprocess.PIPE, 
  21.          stdout=subprocess.PIPE, 
  22.          stderr=subprocess.PIPE, 
  23.          shell=True 
  24.          ) 
  25.      # 读取abench的运行结果,因为可能得保存下来吧 
  26.      result = pipe.stdout.read() 
  27.      # 下面可能是保存结果什么的 
  28.  
  29. if __name__ == "__main__": 
  30.     config = yaml.load(file(configFile)) 
  31.     for task in config["tasks"]: 
  32.        runTask(config, task)

  自动更新测试环境

  在我实际测试过程中,因为要更新的环境其实相当复杂,最多的时侯需要去10几台机器上做更新环境、停止/启动进程的操作。但我这里主要介绍思路,多一些机器和进程其实都一样。

  接着刚才的配置文件,我们只是在每一个task中设计了加压任务,但在加压前需要更新哪些环境没有涉及,按照阿里巴巴的ISearch架构,我 就启动一个一行两列的Searcher环境,2列Searcher上有一个Merger,然后再有一个clustermap来监控。

  1. abenchPath: /opt/usr/bin/abench  # abench在哪儿? 
  2. abenchCommand: "${abenchPath} -p ${parallelNum} -s ${seconds} -k --http -o /dev/null ${targetHost} ${targetPort} ${queryFilePath}" 
  3. # 关于Searcher的一些通用配置 
  4. searcher: 
  5.     templateConfigFile: /home/admin/access-log/searcher_server.cfg  # 因为启动时的监听端口等信息需要从下面的运行任务中读取,因此这个也设计成一个模版文件 
  6.     templateLogConfigFile: /home/admin/access-log/searcher_log.cfg 
  7.     # 在Search机器上操作的命令 
  8.     commands: 
  9.         - "${searchRoot}/bin/is_searcher_server -c ${configFile} -l ${logConfigFile} -k stop > /dev/null 2>&1" 
  10.         - "${searchRoot}/bin/is_searcher_server -c ${configFile} -l ${logConfigFile} -k start -d > /dev/null 2>&1" 
  11. # 关于Merger的一些通用配置,和Searcher差不多,就不写了 
  12.  
  13. tasks: 
  14.  # 第一个测试用例,我可能需要测试单线程的情况 
  15.  - 
  16.     id: 1 # ID的作用是你在脚本中可以拿id作为结果存放的目录 
  17.     parallelNum: 1 # 并发数 
  18.     seconds: 1800 # 压半个小时 
  19.     targetHost: 10.20.137.22 # 目标主机 
  20.     targetPort: 9999 
  21.     queryFilePath: /home/admin/access-log/add-600w.query  # 请求放在这儿 
  22.  
  23.     # 两台Search机器,定义一个List 
  24.     searchers: 
  25.        - 
  26.          host: 10.20.150.61 
  27.          port: 6322 # 监听的端口 
  28.          username: test # 因为需要通过ssh协议登录上去操作,因此需要用户名密码。如果你已经把机器ssh都打通了,那就不需要了 
  29.          password: 12345 
  30.          configFile: "${searchRoot}/scripts/conf/searcher_server.cfg" # 启动时运行的配置文件 
  31.          logConfigFile: "${searchRoot}/scripts/conf/searcher_log.cfg" # 启动时运行的日志文件 
  32.        - 
  33.          host: 10.20.150.60 
  34.          port: 6322 
  35.          username: test 
  36.          password: 12345 
  37.          configFile: "${searchRoot}/scripts/conf/searcher_server.cfg" 
  38.          logConfigFile: "${searchRoot}/scripts/conf/searcher_log.cfg" 
  39.  
  40.     # 我这边只有一台merger,如果merger也是有多台的话,也可以把这个设计成一个List 
  41.     merger: 
  42.        host: 10.20.137.22 
  43.        port: 6088 
  44.        username: test 
  45.        password: 12345 
  46.        configFile: "${searchRoot}/scripts/conf/merger_server.cfg"

然后比如关于Searcher的配置文件,在上面也是一个模版文件阿,我们可以把这个文件设计成:

  1. se_conf_file=${searchRoot}/scripts/conf/se.conf 
  2. simon_conf_path=${searchRoot}/scripts/conf/simon_searcher.xml 
  3. sort_config=${searchRoot}/scripts/conf/searcher_sort.xml 
  4. cache_size=0 
  5. cache_min_doc=0 
  6. conn_queue_limit=500 
  7. [services] 
  8. tcp ${port} # 主要就是为了替换监听的端口,其实要做得通用一点的话,很多配置都可以搞成变量,但就是可能你自己的配置文件变得很复杂。因此我们能不改的就尽量不改。 
  9.  
  10. [clustermap] 
  11. local_config_path=${searchRoot}/scripts/conf/clustermap.xml

  上述就是关于searcher和merger多行多列的配置,下面我们完善一下我们刚才的Python脚本

  1. # 得的一个ssh登录后的client对象,用于调用远程机器上的命令 
  2. def getClient(host, port, username, password): 
  3.     client = paramiko.SSHClient() 
  4.     client.load_system_host_keys() 
  5.     client.set_missing_host_key_policy(paramiko.WarningPolicy() 
  6.     client.connect(hostname, port, username, password) 
  7.     return client 
  8.  
  9. # 得到一个sftp对象,因为需要scp渲染好的配置文件什么的,因此需要sftp对象,它的put方法其实就类似scp 
  10. def getSftp(host, port, username, password): 
  11.     transport = paramiko.Transport((hostname, port)) 
  12.     transport.connect(username=username, password=password) 
  13.     sftp = paramiko.SFTPClient.from_transport(transport) 
  14.     return sftp 
  15.  
  16. # 更新和部署Searchers 
  17. def cleanSearchers(config, searchers): 
  18.     for searcher in searchers: 
  19.         # 得到渲染好的配置文件的内容 
  20.         templateLine = Template(file(config["searcher"]["templateConfigFile"]).read()).render( 
  21.             port=searcher["port"], 
  22.             searchRoot=config["searchRoot"] 
  23.             ) 
  24.         # 将渲染好的配置文件写入一个临时文件 
  25.         tmpConfigFile = tempfile.NamedTemporaryFile(delete=False) 
  26.         tmpConfigFile.file.write(templateLine) 
  27.         tmpConfigFile.file.close() 
  28.         # 将这个临时文件scp拷远程机器上的哪儿 
  29.         targetConfigFile = Template(searcher["configFile"]).render(searchRoot=config["searchRoot"]) 
  30.         sftp = getSftp(searcher["host"], 22, searcher["username"], searcher["password"]) 
  31.         sftp.put(tmpConfigFile.name, targetConfigFile) 
  32.         sftp.close() 
  33.         # 删除掉之前的临时文件 
  34.         os.remove(tmpConfigFile.name) 
  35.         # 运行启动searcher的命令 
  36.         client = getClient(searcher["host"], 22, searcher["username"], searcher["password"]) 
  37.         for command in config["searcher"]["commands"]: 
  38.             command = Template(command).render( 
  39.                 searchRoot=config["searchRoot"], 
  40.                 configFile=targetConfigFile, 
  41.                 logConfigFile=targetLogConfigFile 
  42.                 ) 
  43.             client.exec_command(cmd) 
  44.         client.close()

 关于clustermap的配置

  在阿里巴巴的ISearch架构中,searchers几行几列是由clustermap来配置的,我们这边也稍微简单话一点,不考虑 merger有多台的情况,就设计searchers几行几列的情况。更新一下刚才在task中的配置,加上关于clustermap的配置

  1.      clustermap: 
  2.        host: 10.20.137.22 
  3.        username: test 
  4.        password: 12345 
  5.        configFile: "${searchRoot}/scripts/conf/clustermap.xml" 
  6.        # 一台merge 
  7.        merger: 
  8.          host: 10.20.137.22 
  9.          port: 6088 
  10.        # 关于searcher的配置,其实是一个二维数组。第一个纬度是列,第2个纬度是行。以下这个例子是1列2行 
  11.        searchers: 
  12.          - 
  13.            servers: # 同一列下的机器 
  14.              - 
  15.                host: 10.20.150.61 
  16.                port: 6322 
  17.          - 
  18.            servers: 
  19.              - 
  20.                host: 10.20.150.60 
  21.                port: 6322

  上述是1列2行的例子,如果要配成2行2列就只要在searchers部分配成:

  1.        searchers: 
  2.          - 
  3.            servers: # 同一列下的机器 
  4.              - 
  5.                host: 10.20.150.61 
  6.                port: 6322 
  7.              - 
  8.                host: 10.20.150.59 
  9.                port: 6322 
  10.          - 
  11.            servers: 
  12.              - 
  13.                host: 10.20.150.60 
  14.                port: 6322 
  15.              - 
  16.                host: 10.20.150.62 
  17.                port: 6322

 然后为了迎合clustermap配置的这种设计,在clustermap的模版配置文件也需要改一下:

  1. <?xml version="1.0"?> 
  2. <clustermap> 
  3.     <!-- 关于Merger的配置,这里我暂时不考虑merger多台的情况 --> 
  4.     <merger_list> 
  5.             <merger_cluster name=m1 level=1> 
  6.                 <merger ip=${merger["host"]} port=${merger["port"]} protocol=http/> 
  7.             </merger_cluster> 
  8.     </merger_list> 
  9.  
  10. <!-- 下面是searcher的多行行列的配置,是一个二维数组 --> 
  11. <search_list> 
  12. <% 
  13. id = 1 # 这个值是纪录searcher列的名字 
  14. %> 
  15. <!-- 第一个纬度,同一列的 --> 
  16. % for searcher in searchers: 
  17. <search_cluster name=c${id} docsep=false level=1 partition=0> 
  18.     <!-- 第二个纬度,同一行的 --> 
  19.     % for server in searcher["servers"]: 
  20.     <search ip=${server["host"]} port=${server["port"]} protocol=tcp type=mix /> 
  21.     % endfor 
  22. </search_cluster> 
  23.     <% 
  24.     id += 1 
  25.     %> 
  26. % endfor 
  27. </search_list> 
  28.  
  29.     <merger_cluster_list> 
  30.             <merger_cluster name=m1> 
  31.                 % for i in range(1, id): 
  32.                 <search_cluster name=c${i} /> 
  33.                 % endfor 
  34.             </merger_cluster> 
  35.     </merger_cluster_list> 
  36. </clustermap>

  这样比如1行2列渲染出来成了:

  1. <?xml version="1.0"?> 
  2. <clustermap> 
  3.     <merger_list> 
  4.             <merger_cluster name=m1 level=1> 
  5.                 <merger ip=10.20.137.22 port=6088 protocol=http/> 
  6.             </merger_cluster> 
  7.     </merger_list> 
  8.  
  9. <search_list> 
  10. <search_cluster name=c1 docsep=false level=1 partition=0> 
  11.     <search ip=10.20.150.60 port=6322 protocol=tcp type=mix /> 
  12. </search_cluster> 
  13. <search_cluster name=c1 docsep=false level=1 partition=0> 
  14.     <search ip=10.20.150.61 port=6322 protocol=tcp type=mix /> 
  15. </search_cluster> 
  16. </search_list> 
  17.  
  18.     <merger_cluster_list> 
  19.             <merger_cluster name=m1> 
  20.                 <search_cluster name=1 /> 
  21.             </merger_cluster> 
  22.     </merger_cluster_list> 
  23. </clustermap>

  总结

  上述就是我在测试中,对分布式环境的自动更新和批量性能测试,这样大大减少了我们来回捣固机器、修改配置的时间。而且对测试结果的自动收集和解析也可以帮助我们来分析测试结果。我觉得这是一个不错的尝试,大家可以都可以试试看。

====================================分割线================================

最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-09-10 02:56:37

一种搭建分布式测试环境和批量性能测试的思路的相关文章

docker搭建大规模测试环境的实践

内容来源:2017年4月8日,第四范式资深测试开发工程师孙高飞在"饿了么技术沙龙[第四弹]北京研发中心测试专场"进行<docker搭建大规模测试环境的实践>演讲分享.IT大咖说作为独家视频合作方,经主办方和讲者审阅授权发布. 嘉宾分享视频地址:http://t.cn/R9UCnpq 困境 当今互联网行业发展迅速,产品架构逐渐复杂,导致环境搭建困难. 测试环境不一致. 因为搭建环境困难,环境不多,所以一套环境有多人使用,容易造成环境的互相踩踏问题. 随着业务的发展和时间的积累

java微信公众号开发(搭建本地测试环境)_java

俗话说,工欲善其事,必先利其器.要做微信公众号开发,两样东西不可少,那就是要有一个用来测试的公众号,还有一个用来调式代码的开发环境. 测试公众号 微信公众号有订阅号.服务号.企业号,在注册的时候看到这样的信息,只有订阅号可以个人申请,服务号和企业号要有企业资质才可以.这里所说的微信公众号开发指的是订阅号和服务号. 另外,未认证的个人订阅号有一些接口是没有权限的,并且目前个人订阅号已不支持微信认证,也就是说个人订阅号无法调用一些高级的权限接口,下图就是一个未认证的个人订阅号所具备权限列表,像生成二

搭建eclipse+python+selenium测试环境

经过几天的纠结之后,终于在今天把该环境搭建起来了,在这里要特别感谢深圳-乙醇老师的帮助 搭建环境: 系统环境:Win7 64位; JDK版本:java version "1.6.0_45″ eclipse版本:4.2.0 下面就给大家介绍一下详细的步骤: (1) 下载一个active-python安装软件(该软件已经包含了python2.7和setuptools),默认安装好之后,对应的python目录里面就已经存在Scripts文件夹了,十分方便 (2) 添加Path,比如:C:\Python

分享几款一键搭建php本地测试环境工具

很多朋友平时电脑做服务器都是搭建iis服务器,今天我给大家分享几个一键搭建php本地测试工具!,喜欢做网站论坛的朋友为了使自己的站点能够具有独特的个性和良好的用户体验,总会对自己的站点进行一系列的深度调整,方能充分体现网站特色和唯一性,一般的PHP 本地测试环境[PHP 本地测试.本地服务器.网站制作必备工具] Wordpress 够流行了吧,它是一个用 PHP 编写的强大的博客平台.使用它来架设一个博客平 台相关容易,是新手建站比较方便的工具.但是要真正体会 WordPress(以下简称 WP

性能测试知多少---测试环境搭建

在进行性能则试前,需要完成性能测试的搭建工作,一般包括硬件环境.软件环境及网络环境,可以要求配置和开发工程师协助完成,但是作为一个优秀性能测试工程师,这也是你的必备技能之一.   性能测试环境与功能测试环境的区别                                                  那么性能测试环境与功能测试环境有什么不同呢?性能测试对测试环境的干净.独立性要求更高,更为严格.对于一个相对较规范的公司,都会建立其独立的研发环境.测试环境.线网环境(最终运行软件的环境)

手把手教你搭建智能合约测试环境、开发、编译、部署以及如何通过JS调用合约方法

如何编写智能合约(Smart Contract)?(I) 完整视频教程 https://v.qq.com/x/page/h0552ba9k8h.html 学习目标 了解智能合约 简单环境搭建 能够利用solidity编写Hello World合约 合约部署 和合约互动 使用solidity语言撰写智能合约 Ethereum上的智能合约需要使用solidity语言来撰写.虽然还有其他能用来撰写智能合约的语言如Serpent(类Python).lll(类Fortran),但目前看到所有公开的智能合约

区域链实践第一步——区域链测试环境搭建

区域链光速发展,在许多人的期许下,已经成为了互联网下一个革新点.区块链会成就的未来价值互联网,是新时代的基石. IBM中国研究院开发的超能云(SuperVessel)平台提供了给区块链爱好者.开发者的区块链开发测试环境.通过该平台,用户能够免费.快速创建基于Hyperledger Fabric的多节点区块链.并在自己的链上编写智能合约. 通过IBM的SuperVessel,我们能接触区块链,能自己部署和执行智能合约:通过Github上的开源项目,也就是上面提到的Hyperledger Fabri

基于docker搭建测试环境

layout: post title: 基于docker搭建测试环境 category: 技术 tags: Docker keywords: Docker shipyard jenkins 简介 当web项目开发完毕后,一般会在测试环境上运行一下,供开发部门调错和测试部门测试.对于具有一定业务规模的公司,几十个上百个web服务,每个服务分别占用一个tomcat目录,配置过程繁琐,且无法集中管理.此外,对于公司的新手来讲,需要一定的背景知识才可以上手. 本文主要讲述基于docker搭建测试环境,或

B/S架构测试环境搭建_SQLServer篇(Win32系统)

前言:此篇讲解在Win32系统下SQLServer创建数据库和用户(建立测试环境必需),顺带讲下用户和登录名的区别,不对之处,欢迎拍砖. 一.创建数据库: (1)SQLServer安装过程中有一个需要注意的地方,设置各个系统的账户和密码,见下图: 图1 设置所有的账户和密码 其他的没什么需要留意的了,只需按照提示一步步走完就算安装成功了. (2)SQLServer安装完成后,在"开始"--"程序"--"SQLServer"--"SQLS