首页 > 代码库 > 性能测试从0到1的过程(二)

性能测试从0到1的过程(二)

  本人,从毕业开始接触测试,但是性能方面一无所知。之前在第一份工作,测过安卓客户端,当时写过一个非常简单的shell脚本,push到手机系统内,用于手机硬件信息。但是在服务端的性能方面,真的是一只菜鸟。现在 本鸟把自己最近一个月的性能测试经验分享一下吧。

   一、环境准备,这个熟门熟路。之前的帖子已经写了,装zk/kafka这些中间件需要,再是部署应用服务。重点是部署的过程中,首先你要准备好profile要与测试、生产环境都隔离,再者,用于性能测试的环境,最好是模拟线上去配置,机器最好是申请高级一点,配置参数参考线上,哪些参数呢,比如分配的内存、线程数等。具体ps一下看看

-Djava.awt.headless=true -server -Xms2048m -Xmx2048m -XX:MaxPermSize=256m 

  上面的那一段 具体是分析的内存大小,这些是可以在jvm配置参数上写的。还有一些参数,比如gc日志文件,有些时候打出来是这幅样子

46224.076: [GC (Allocation Failure) [PSYoungGen: 697520K->688K(697856K)] 797758K->100934K(2096128K), 0.0067511 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 

  很明显格式不规范,时间戳有问题,我们需要转换一下,那么需要配置参数为

-Xloggc:/home/ewallet/loan-test/elephant-xn/tomcat-loan-risk-auto-Ins1/logs/gc.log -XX:+PrintGCDateStamps

  这一串意思就是把gc日志打印方式改成datastamps的形式展示出来,这样我们就能很好的查看gc日志了。环境准备大概是这么些,一般压测也会放在执行机上去发起,也就是说我们本地的配置太弱,会影响并发的效率,所以最好拿一台机器作为执行机,配置和应用服务器差不多就行了。对了,这里提一点,执行机上最好看一下jdk版本,我们的测试脚本都是1.8基础上写的,如果执行机jdk太低,注意要提前准备好~

  二、测试脚本编写

  我这边要测的一个是dubbo接口,一个是消息监听类,而我们性能测试脚本一般是python写的,用grinder去支持。无奈,python无法编写dubbo接口测试,所以咯,这里的思路是先编写dubbo接口测试类,再打成一个jar包,放在python工程下,此时再去写代码调用里面的测试接口,就能成功发起请求啦。说来容易,首先我们的dubbo接口工程真的太庞大,打包后真的非常非常臃肿,所以我又重新开始写测试工程,只写用到的接口。我们这边用到的测试工程是spring+testng,写起来倒是挺快的,一共是3个类。写完调通后,开始打包。这里我又遇到问题了,之前我的打包方式都是代码和依赖分开打包,至少在jmeter测试的时候是分开的,但是这里要合并!就是要合成一个完整的jar包!哎,应该是我这个idea版本不支持,这方面折腾了很久,下面列出pom里打包用到的pluguin,因为我后面用了eclipse进行打包测试。

  1、用jemer测试性能的话,需要代码和依赖分开打包,用到的build配置为。这样子在target下会有两个jar包生成。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/lib</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

<dependency>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.0.2</version>
</dependency>

  2、用grinder测试的话 需要把整个java测试工程包含依赖全部打包在同一个包内。依赖为

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.4.2</version>
    <configuration>
        <skipTests>true</skipTests>
    </configuration>
</plugin>
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> 
    <executions> <execution> <id>copy</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> 
    </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> 
    </configuration> </execution> </executions> </plugin> -->
<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <span style="color:#FF0000;">
            <archive>
                <manifest>
                    <mainClass>com.allen.capturewebdata.Main</mainClass>
                </manifest>
            </archive>
        </span>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
    </configuration>
</plugin>
</plugins>

后来遇到了一个报错,那么需要再新增一个插件配置

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>

这样子成功打出了两个包 分别为maventest-0.0.1-SNAPSHOT.jar 和 maventest-0.0.1-SNAPSHOT-jar-with-dependencies.jar 当然我需要的是后者。

   3、好了,java代码搞定,那么需要一个python测试工程了,Grinder3的脚本引擎是Jython,它是Python在Java中的完全实现。Python学习参考 script gallery and  Richard Perks‘ tutorial。环境配置方面,需要准备grinder和jython,还有一个pvdev的配置,用到的开发工具是eclipse。然后新建一个测试工程,类型选择pvdev project ,其他的,就是编写python脚本,再把jar包的依赖放入lib文件,在properties里增加依赖。这里列出一个http接口测试类的编写,主要实现过程是参数从外部文件中读取,发起请求是通过httpclient去实现。

# -*- encoding:utf-8 -*-


from net.grinder.script import Test
from net.grinder.script.Grinder import grinder
from java.util import HashMap
import random
import time
from java.net import URLEncoder
from net.sf.json import JSONObject
from java.util import UUID

url = "http://xxxxx/rest/"

logfile = open("err.log", "w")
isopen = AtomicInteger(int(grinder.getProperties().get(‘grinder.threads‘)))
ISOTIMEFORMAT = ‘%Y-%m-%d %X‘
#random = random()

#全局变量,获取grinder.properties中定义的进程数和线程数
process_num = int(grinder.getProperties().get(‘grinder.processes‘))
thread_num = int(grinder.getProperties().get(‘grinder.threads‘))

infile = open("out.txt",‘r‘)
bank_account_ids=[]
quick_pay_ids = []
m_user_ids=[]
identity_nos = []
for line in infile.readlines():
    if(line!=None):
        tmp = line.strip().split(‘,‘)
        bank_account_ids.append(tmp[0])
        quick_pay_ids.append(tmp[1])
        m_user_ids.append(tmp[2])
        identity_nos.append(tmp[3])
infile.close()

nums = len(quick_pay_ids)
print "nums:", nums


class TestRunner:
    def getParam(self,bank_account_id,quick_pay_id,m_user_id,identity_no):
        # requestId每次需要变
        #request_id = "12345678-1234-1234-1234-123456789101"
                
        uuid = UUID.randomUUID();
        request_id=uuid.toString().upper()
        insure_serial=random.randint(1000000000, 90000000000)
        
        paramsMap = HashMap()
        paramsMap.put("version","1.0")
        paramsMap.put("request_id",request_id)
        

        data = JSONObject()
        data.put("product","pc_test")
        data.put("m_user",m_user_id)
        
        
        
        print "ready data:",data
        paramsMap.put("data", data.toString())

        
        print "ready params:",paramsMap
        return paramsMap
        
        
    def DoTest(self,param):
        result = HttpUtils.httpPost(url,param)
        print "result", result
        
        if(result.getContent() == None):
            print "response is null"
            grinder.getStatistics().getForCurrentTest().setSuccess(False)
            logfile.write(time.strftime(ISOTIMEFORMAT, time.localtime( time.time())) + ‘\n‘)
        if(result.getCode() != 200 ):
            print "response status !=200"
            grinder.getStatistics().getForCurrentTest().setSuccess(False)
            logfile.write(time.strftime(ISOTIMEFORMAT, time.localtime( time.time())) + ‘\n‘)
    
    request = Test(1, ‘DoTest‘).wrap(DoTest)
    
    def initialSleep( self):
        sleepTime = grinder.threadNumber * 100  # per thread
        grinder.sleep(sleepTime, 0)
        
    def __call__(self):
        """This method is called for every run performed by the worker thread."""
        if grinder.runNumber == 0: 
            self.initialSleep()
         # 按顺序取数据
        id = (process_num * thread_num * grinder.getRunNumber() + thread_num * grinder.getProcessNumber() + grinder.getThreadNumber()) % nums 
        param = self.getParam(bank_account_ids[id],quick_pay_ids[id],m_user_ids[id],identity_nos[id])     
        self.request(param)

    def __del__(self):
        if isopen.decrementAndGet() == 0:
            logfile.close()

注意

  • 脚本必须定义一个名为TestRunner的类 

  • TestRunner实例必须是可调用的

  • 测试脚本可通过grinder对象访问

   我这边还编写了其他如dubbo接口、消息监听接口测试类,这里暂时不一一列出了。

 

  三、到了激动人心的时刻,开始测试。注意,测试脚本最好在本地调通,然后上传到平台去执行。这里涉及测试数据的准备,一般人是会用shell或者其他去写,最好建议自动化掉。执行的过程我是关注以下几个指标。包含基础模块,也就是

  • 系统load:先看load15 按cpu核数来对比,对于1核的机器,【0.7,1)的状态是比较好的,达到1的话就是即将拥堵,需要排查,上限一般是根据机器核数来定
  • cpu占比
  • 磁盘util
  • javamethod统计
  • sql使用
  • jvm

差不多就上面这些指标。

 

 

 

 

  

性能测试从0到1的过程(二)