这是我对Dubbo服务开展性能测试所做准备的第二篇学习笔记,编写了一对Dubbo-Demo实例。主要参照的原帖:http://www.cnblogs.com/miaomiaokaixin/p/6129733.html
由于原作者这篇指导文章写作的时间是2016年,使用的依赖包版本较旧;在学习的过程中,改用了较新版本的Maven依赖,同时遇到一些错误。本文的写作目的在于及时的记录下学习过程、应对问题的解决等。
本文讲解jmeter测试dubbo接口的实现方式,文章以一个dubbo的接口为例子进行讲解,该dubbo接口实现的功能为:
如果您想搭建Dubbo学习环境,请看另一篇文档《搭建Dubbo开发学习环境》。
一、服务端的工程
完成后的代码架构如下:
1. 创建一个Maven工程,取名:demo_dubbo_provider
POM文件为:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ustc.demo.provider</groupId> <artifactId>demo_dubbo_provider</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>dubbo-provider</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/dubbo --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.1</version> </dependency> <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient --> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.12</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>unpack</id> <phase>package</phase> <goals> <goal>unpack</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.1</version> <includes>META-INF/assembly/**</includes> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptor>src/main/assembly/assembly.xml</descriptor> <encoding>UTF-8</encoding> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
2. 在src/main下新建文件夹assembly,然后在assembly文件夹下新建assembly.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <assembly> <id>assembly</id> <formats> <format>tar.gz</format> </formats> <includeBaseDirectory>true</includeBaseDirectory> <fileSets> <fileSet> <directory>${project.build.directory}/dubbo/META-INF/assembly/bin</directory> <outputDirectory>bin</outputDirectory> <fileMode>0755</fileMode> </fileSet> <fileSet> <directory>src/main/assembly/conf</directory> <outputDirectory>conf</outputDirectory> <fileMode>0644</fileMode> </fileSet> </fileSets> <dependencySets> <dependencySet> <outputDirectory>lib</outputDirectory> </dependencySet> </dependencySets> </assembly>
3. 在src/main/assembly文件夹下新建conf文件夹,然后在conf文件夹下新建dubbo.properties文件。文件中zookeeper的地址根据实际进行修改。
dubbo.container=log4j,spring dubbo.application.name=demo-dubbo-provider dubbo.application.owner=jason #dubbo.registry.address=multicast://127.0.0.1:1234 dubbo.registry.address=zookeeper://192.168.8.241:2181 #dubbo.registry.address=redis://127.0.0.1:6379 #dubbo.registry.address=dubbo://127.0.0.1:9090 dubbo.monitor.protocol=registry dubbo.protocol.name=dubbo dubbo.protocol.port=20880 #dubbo.service.loadbalance=roundrobin #dubbo.log4j.file=logs/dubbo-demo-provider.log #dubbo.log4j.level=WARN
4. 在src/test/resources包路径下,新建dubbo.properties文件,内容和上面3的dubbo.properties文件内容相同。
5. 在src/test/resources路径下,新建log4j.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- Appenders --> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <param name="Target" value="System.out" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p: %c - %m%n" /> </layout> </appender> <appender name="myFile" class="org.apache.log4j.RollingFileAppender"> <param name="File" value="logs/output.log" /><!-- 设置日志输出文件名 --> <!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 --> <param name="Append" value="true" /> <param name="MaxBackupIndex" value="10" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%p (%c:%L)- %m%n" /> </layout> </appender> <!-- Application Loggers --> <logger name="com.ustcinfo.ishare.eip.si"> <level value="error" /> </logger> <!-- 3rdparty Loggers --> <logger name="org.springframework.core"> <level value="warn" /> </logger> <logger name="org.springframework.beans"> <level value="info" /> </logger> <logger name="org.springframework.context"> <level value="info" /> </logger> <logger name="org.springframework.web"> <level value="info" /> </logger> <logger name="org.mongodb.driver"> <level value="warn" /> </logger> <!-- Root Logger --> <root> <priority value="error" /> <appender-ref ref="console" /> </root> </log4j:configuration>
下面开始编写provider的主方法:
6. 编写provider的接口sayHello,新建DemoService.java类:
package com.ustc.demo.provider; public interface DemoService { public String sayHello(String name); }
7. 编写sayHello接口的实现类,新建DemoServiceImpl.java类:
package com.ustc.demo.provider; import java.text.SimpleDateFormat; import java.util.Date; public class DemoServiceImpl implements DemoService { @Override public String sayHello(String name) { // TODO Auto-generated method stub String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); System.out.println("From consumer: " + name); return "The current time is: " + time; } }
8. 编写spring的配置文件,在META-INF/spring文件夹下的demo-provider.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <bean id="demoService" class="com.ustc.demo.provider.DemoServiceImpl" /> <dubbo:registry address="zookeeper://192.168.8.241:2181" /> <dubbo:protocol name="dubbo" host="192.168.9.107" port="20880" /> <dubbo:service interface="com.ustc.demo.provider.DemoService" ref="demoService"/> </beans>
dubbo:registry address:服务注册于zookeeper所在的服务器;
dubbo:protocol host:服务提供方provider运行的服务器,添加Host字段可以避免其他机器上的消费方无法访问提供方;和同事实验过,缺少Host时仅我本地的消费方可以调用成功。
9. 编写测试main方法,新建DemoServiceMain.java类:
package com.ustc.demo.provider; public class DemoServiceMain { public static void main(String[] args) { com.alibaba.dubbo.container.Main.main(args); } }
这样服务端的代码就写好了,实现的功能是:当消费者来询问当前时间是几点的时候,返回当前时间。
二、消费端的工程
完成后的代码架构如下:
1. 创建一个Maven工程,取名:demo_dubbo_consumer
POM文件为:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ustc.demo.consumer</groupId> <artifactId>demo_dubbo_comsumer</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>consumer</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/dubbo --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.1</version> </dependency> <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient --> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.0.5.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>unpack</id> <phase>package</phase> <goals> <goal>unpack</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.1</version> <includes>META-INF/assembly/**</includes> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptor>src/main/assembly/assembly.xml</descriptor> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
2. 在src/main下新建文件夹assembly,然后在assembly文件夹下新建assembly.xml文件。文件内容和Provider工程中的同名文件相同。
3. 在src/main/assembly文件夹下新建conf文件夹,然后在conf文件夹下新建dubbo.properties文件。文件内容和Provider工程中的同名文件相同。
4. 在src/test/resources包路径下,新建dubbo.properties文件,内容和上面的3中dubbo.properties文件内容相同。
下面编写(拷贝)provider的接口sayHello:
5. 新建DemoService.java类:
package com.ustc.demo.provider; public interface DemoService { public String sayHello(String name); }
6. 编写消费端请求类调用sayHello方法,新建DemoAction.java类:
package com.ustc.demo.consumer; import com.ustc.demo.provider.DemoService; public class DemoAction { private DemoService demoService; public void setDemoService(DemoService demoService) { this.demoService = demoService; } public void start() throws Exception { for (int i = 0; i < Integer.MAX_VALUE; i++) { try { String hello = demoService.sayHello("Hello, how much is the current time?"); System.out.println("From provider: " + hello); } catch (Exception e) { e.printStackTrace(); } Thread.sleep(2000); } } }
7. 编写spring的配置文件,在META-INF/spring文件夹下的dubbo-demo-action.xml文件:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <bean class="com.ustc.demo.consumer.DemoAction" init-method="start"> <property name="demoService" ref="demoService" /> </bean> </beans>
8. 编写spring的配置文件,在META-INF/spring文件夹下的dubbo-demo-consumer.xml文件:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <dubbo:reference id="demoService" interface="com.ustc.demo.provider.DemoService" /> </beans>
9. 编写测试main方法,新建DemoConsumerMain.java类:
package com.ustc.demo.consumer; public class DemoConsumerMain { public static void main(String[] args) { com.alibaba.dubbo.container.Main.main(args); } }
10. 编写log4j2的配置文件,在src/main/resources目录下创建log4j2.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="warn"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%m%n" /> </Console> </Appenders> <Loggers> <Root level="INFO"> <AppenderRef ref="Console" /> </Root> </Loggers> </Configuration>
这样我们就完成了本地消费者代码,在编写符合jmeter格式的代码前,我们首先在本地开发工具中运行看看效果。
先启动服务提供方的main方法,然后启动服务消费方的main方法:
服务提供方控制台:
服务消费方控制台:
这样调试发现消费端向服务端发送:“How much is the current time?”;
然后服务端返回当前的时间,该dubbo接口的功能正常实现。
三、用JMeter模拟消费方
我们现在想对dubbo接口进行性能测试,可以用jmeter模拟服务消费方并发调用服务提供方。因为jmeter支持java请求,故我们可以将服务提供方打包部署到服务器上运行;本例没有部署,而是在Eclipse端保持运行。
然后我们将服务消费方打成jar包、放到jmeter的/lib/ext文件夹中,这样就能实现jmeter模拟消费方去请求服务端,进行性能测试。
现在我们来讲解如何将上面的服务消费端的代码、编写成可以打包放到jmeter中代码——即Java Sampler。
只需要对上面的消费者代码进行3处修改即可:
1. POM.xml文件
pom.xml文件中添加对jmeter的支持,在<dependencies></dependencies>之间添加如下代码:
<!-- java jmeter依赖jar包 --> <dependency> <groupId>org.apache.jmeter</groupId> <artifactId>ApacheJMeter_core</artifactId> <version>3.3</version> </dependency> <dependency> <groupId>org.apache.jmeter</groupId> <artifactId>ApacheJMeter_java</artifactId> <version>3.3</version> </dependency>
2. 在src/main/resources下新建applicationConsumer.xml文件,zookeeper地址根据需要进行修改:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd "> <!-- consumer application name --> <dubbo:application name="consumer-jmeter" /> <!-- registry address, used for consumer to discover services --> <dubbo:registry address="zookeeper://192.168.8.241:2181" /> <!-- which service to consume? --> <dubbo:reference id="demoService" interface="com.ustc.demo.provider.DemoService" /> </beans>
3. 在com.ustc.demo.consumer包下新建JmeterDemoAction.java类:
package com.ustc.demo.consumer; import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; import org.apache.jmeter.samplers.SampleResult; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.ustc.demo.provider.DemoService; public class JmeterDemoAction extends AbstractJavaSamplerClient { ApplicationContext context = null; public DemoService demoService = null; public void setupTest(JavaSamplerContext arg0) { context = new ClassPathXmlApplicationContext("applicationConsumer.xml"); ((AbstractApplicationContext) context).start(); demoService = (DemoService) context.getBean("demoService"); } @Override public SampleResult runTest(JavaSamplerContext arg0) { SampleResult sr = new SampleResult(); try { sr.sampleStart(); // 将Request内容输出到Jmeter-查看结果树的请求页面 sr.setSamplerData("From consumer: Hello, how much is the current time?"); sr.setDataType(SampleResult.TEXT); // 调用provider的demoService.sayHello方法 String response = demoService.sayHello("Hello, how much is the current time?"); // 填写Jmeter-查看结果树的取样器结果 sr.setResponseCodeOK(); sr.setResponseMessage("Java Sampler Response is done. "); // 将Response内容输出到Jmeter-查看结果树的响应数据页面 sr.setResponseData("From provider: " + response, "utf-8"); sr.setDataType(SampleResult.TEXT); // 标记成功,并停止采样 sr.setSuccessful(true); sr.sampleEnd(); } catch (Exception e) { e.printStackTrace(); } return sr; } @SuppressWarnings("deprecation") public void teardownTest(JavaSamplerContext arg0) { if(null != context){ ((AbstractApplicationContext) context).destroy(); } } /* // 供调试使用;打包前记得注释掉 main 方法 public static void main(String[] args) { JmeterDemoAction test = new JmeterDemoAction(); test.setupTest(null); test.runTest(null); test.teardownTest(null); System.exit(0); } */ }
这样就完成了jmeter的消费端代码编写。
四、Maven Install打包消费端工程
在执行Maven Install的时候,可能会提示有多个版本的 log4j 依赖包,产生了冲突。于是修改POM.xml文件:
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient --> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> <exclusions> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.12</version> <exclusions> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency>
如果提示我们正在使用JRE而非JDK进行编译,则在POM.xml中添加JDK的路径,要到 javac:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <verbose>true</verbose> <fork>true</fork> <executable>C:\Program Files\Java\jdk1.8.0_172\bin\javac</executable> </configuration> </plugin>
打包完成后可以看到消费端的target下生成了两个文件:
一个demo_dubbo_comsumer-0.0.1-SNAPSHOT.jar;
一个demo_dubbo_comsumer-0.0.1-SNAPSHOT-assembly.tar.gz。
- 创建一个新的dependency-demo_dubbo_consumer文件夹;复制consumer-0.0.1-SNAPSHOT-assembly.tar.gz中的lib文件夹下所有的jar包文件,
解压到dependency-demo_dubbo_consumer文件夹下,然后将此文件夹拷贝到jmeter的lib目录下。
- 将consumer-0.0.1-SNAPSHOT.jar拷贝到jmeter的lib/ext目录下。
由于在这个Demo中,我没有打包服务提供方的工程,因此Jmeter模拟消费方的执行过程中,在Eclipse中Provider工程的main方法要保持运行。
在Dubbo-Admin页面查看的情况如下:
五、创建Jmeter模拟的消费方测试脚本
1. 引用测试依赖的jar文件
测试计划 – Add directory or jar to classpath - 点击浏览、定位到dependency-demo_dubbo_consumer文件夹,之后按Enter键就可以全部引入到classpath中。
2. 创建一个Java请求
添加一个线程组、添加Sampler – Java Sampler;选中刚刚编写的consumer.JmeterDemoAction:
3. 添加结果查看器
添加监听器:结果查看器、聚合报告。
4. 简单的执行
修改线程组的设置:
希望以20个线程启动执行,循环10次:
5. 查看执行结果
查看聚合报告:
查看结果树:
在Dubbo-Admin页面查看到如下:
由于脚本中做了优化,不会过多创建消费方,可以放心食用。