這是我對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頁面檢視到如下:
由于腳本中做了優化,不會過多建立消費方,可以放心食用。