qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          一種搭建分布式測試環境和批量性能測試的思路

           背景

            在搜索引擎的測試過程中,經常會遇到以下兩個問題:

            ● 需要搭建和更新分布式測試環境

            ● 在性能測試時,我們需要測試不同集群規模和配置下的環境時,如何自動更新測試環境和批量進行性能測試

            因此,我們需要設計一個腳本,這個腳本可以幫我來完成這些事。

            在這里,我推薦使用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>

            總結

            上述就是我在測試中,對分布式環境的自動更新和批量性能測試,這樣大大減少了我們來回搗固機器、修改配置的時間。而且對測試結果的自動收集和解析也可以幫助我們來分析測試結果。我覺得這是一個不錯的嘗試,大家可以都可以試試看。

          posted on 2013-01-04 14:27 順其自然EVO 閱讀(917) 評論(0)  編輯  收藏 所屬分類: 性能測試

          <2013年1月>
          303112345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 伽师县| 宜春市| 景泰县| 洞口县| 沈丘县| 策勒县| 巢湖市| 库尔勒市| 塔河县| 东阿县| 昭苏县| 鸡泽县| 桦南县| 浦江县| 康平县| 富川| 马关县| 安阳县| 灵寿县| 台东市| 雅江县| 青岛市| 肥乡县| 荣昌县| 定州市| 会泽县| 建宁县| 哈密市| 长葛市| 康定县| 澎湖县| 浙江省| 溆浦县| 六盘水市| 泽州县| 嘉鱼县| 十堰市| 年辖:市辖区| 濮阳市| 宿松县| 永春县|