前言
现在要支持MS的Thrfit 协议,因为MS是集成的jmeter,所以要开发MS插件其实就是开发Jmeter插件,然后再进行封装。
JMeter 是 Apache 基金会旗下的一款完全基于 Java 的开源软件,主要用作性能测试,通过模拟并发负载来测试并分析应用的性能状况。JMeter 最初被用于测试部署在服务器端的 Web 应用程序,现在发展到了更广泛的领域。目前 JMeter 已成为主流的性能测试工具。
由此可见,JMeter可以支持自定义第三方插件的。
参考链接:
https://www.jianshu.com/p/0e4daecc8122
Thrift 是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk、and OCaml 等等编程语言间无缝结合的、高效的服务。
Thrift 最初由 facebook 开发,07 年四月开放源码,08 年 5 月进入 Apache 孵化器。Thrift 允许你定义一个简单的定义文件中的数据类型和服务接口。以作为输入文件,编译器生成代码用来方便地生成 RPC 客户端和服务器通信的无缝跨编程语言。
官方链接:https://thrift.apache.org/
brew install thrift
thrift -version
插件地址:https://plugins.jetbrains.com/plugin/7331-thrift-support
提示: 客户端和服务端的协议要一致
namespace java com.thrift.demo // 使用java 语言定义 package
service ThriftHelloService{ // 定义一个service 相当于java当中的接口
string sayHello(1:string username) // 输入一个参数,返回string类型参数。
}
3. 可以看到报错不用管,因为这个项目就是我们用来使用生成java类的项目。可以看到这个类中,有很多代码,
简单分析一下这个类:生成的类主要有5个部分
自动生成的接口有两个,一个是同步调用的Iface,一个是异步调用的AsyncIface。异步调用的接口多了一个回调参数。
详细的源码分析参考:https://www.kancloud.cn/digest/thrift/118986
<dependencies>
<dependency>
<groupId>org.apache.thriftgroupId>
<artifactId>libthriftartifactId>
<version>0.16.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.12version>
dependency>
<dependency>
<groupId>org.apache.jmetergroupId>
<artifactId>ApacheJMeter_coreartifactId>
<version>5.5version>
dependency>
<dependency>
<groupId>org.apache.jmetergroupId>
<artifactId>ApacheJMeter_javaartifactId>
<version>5.5version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.3version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>utf-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-assembly-pluginartifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependenciesdescriptorRef>
descriptorRefs>
configuration>
<executions>
<execution>
<id>assemble-allid>
<phase>packagephase>
<goals>
<goal>singlegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
public class ThriftHelloServiceImpl implements ThriftHelloService.Iface {
@Override
public String sayHello(String username) throws TException {
return "Hello Thrift :" + username;
}
}
public static void main(String[] args) throws Exception {
try {
// 实现处理接口impl
ThriftHelloServiceImpl thriftHelloService = new ThriftHelloServiceImpl();
// 创建TProcessor
TProcessor processor = new ThriftHelloService.Processor<>(thriftHelloService);
// 创建TServerTransport,非阻塞式I/O,服务端和客户端需要TFramedTransport 数据传输方式
TNonblockingServerSocket tNonblockingServerSocket = new TNonblockingServerSocket(9099);
// 创建TProtocol
TThreadedSelectorServer.Args args1 = new TThreadedSelectorServer.Args(tNonblockingServerSocket);
args1.transportFactory(new TFramedTransport.Factory());
// 二进制格式反序列化
args1.protocolFactory(new TBinaryProtocol.Factory());
args1.processor(processor);
args1.selectorThreads(16);
args1.workerThreads(32);
System.out.println("computer service server on port:" + 9099);
TThreadedSelectorServer tThreadedSelectorServer = new TThreadedSelectorServer(args1);
System.out.println("启动 Thrift 服务端");
tThreadedSelectorServer.serve();
} catch (Exception e) {
System.out.println("启动 Thrift 服务端失败" + e.getMessage());
}
}
public static void main(String[] args) {
TTransport transport = null;
try {
// 要跟服务器端的传输方式一致
transport = new TFramedTransport(new TSocket("127.0.0.1", 9099, 6000));
TProtocol protocol = new TBinaryProtocol(transport);
ThriftHelloService.Client client = new ThriftHelloService.Client(protocol);
transport.open();
String result = client.sayHello("thrift-1");
System.out.println(result);
} catch (TException e) {
e.printStackTrace();
} finally {
if (null != transport) {
transport.close();
}
}
}
验证成功,说明没有问题。
ThriftClient (封装公用client)
ThriftHelloService.Client client = null;
private TTransport tTransport = null;
public ThriftClient(String ip, int port, int timeout) {
try {
// 注意传输协议要跟服务器端一致
tTransport = new TFramedTransport(new TSocket(ip, port, timeout));
TProtocol tProtocol = new TBinaryProtocol(tTransport);
client = new ThriftHelloService.Client(tProtocol);
tTransport.open();
} catch (TTransportException e) {
e.printStackTrace();
}
}
public String getResponse(String str) {
try {
return client.sayHello(str);
} catch (TException e) {
e.printStackTrace();
return null;
}
}
public void close() {
if (tTransport != null && tTransport.isOpen()) {
tTransport.close();
}
}
private ThriftClient thriftClient;
/**
* 方法为性能测试时的线程运行体;
*
* @param javaSamplerContext
* @return
*/
@Override
public SampleResult runTest(JavaSamplerContext javaSamplerContext) {
SampleResult sampleResult = new SampleResult();
// 开始统计响应时间标记
sampleResult.sampleStart();
try {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
String response = thriftClient.getResponse("哈哈我是性能调试");
stopWatch.stop();
System.out.println(response + "总计花费:" + stopWatch.getTime());
if (StringUtils.isNotBlank(response)) {
sampleResult.setSuccessful(true);
sampleResult.setResponseMessage(response);
} else {
sampleResult.setSuccessful(false);
sampleResult.setResponseMessage("请求失败....");
}
} catch (Exception e) {
sampleResult.setSuccessful(false);
sampleResult.setResponseMessage("请求失败....");
} finally {
// 结束统计响应时间标记
sampleResult.sampleEnd();
}
return sampleResult;
}
/**
* 方法为初始化方法,用于初始化性能测试时的每个线程;
*
* @param context
*/
@Override
public void setupTest(JavaSamplerContext context) {
String ip = context.getParameter("ip");
String port = context.getParameter("port");
String timeout = context.getParameter("timeout");
// 初始化客户端
thriftClient = new ThriftClient(ip, Integer.valueOf(port), Integer.valueOf(timeout));
super.setupTest(context);
}
/**
* 方法为测试结束方法,用于结束性能测试中的每个线程。
*
* @param context
*/
@Override
public void teardownTest(JavaSamplerContext context) {
if (thriftClient != null) {
thriftClient.close();
}
super.teardownTest(context);
}
/**
* 方法主要用于设置传入界面的参数,初始化默认参数
*
* @return
*/
@Override
public Arguments getDefaultParameters() {
Arguments jMeterProperties = new Arguments();
jMeterProperties.addArgument("ip", "127.0.0.1");
jMeterProperties.addArgument("port", "9099");
jMeterProperties.addArgument("timeout", "6000");
return jMeterProperties;
}
服务端启动,即可验证成功。
就是在jmeter当中写一个自己的sampler ,并且有对应ui页面。
继承AbstractSamplerGui 编写gui的页面,跟java当中swing 一样。
package com.thrift.demo.gui;
import com.thrift.demo.jmeterTest.ThriftSampler;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.gui.JLabeledTextField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.*;
/**
* @author fit2cloudzhao
* @date 2022/8/2 18:58
* @description:
*/
public class ThriftSamplerUI extends AbstractSamplerGui {
Logger log = LoggerFactory.getLogger(ThriftSamplerUI.class);
private final JLabeledTextField serverIp = new JLabeledTextField("ServerIp");
private final JLabeledTextField port = new JLabeledTextField("Port");
private final JLabeledTextField param = new JLabeledTextField("Param");
private void init() {
log.info("Initializing the UI.");
setLayout(new BorderLayout());
setBorder(makeBorder());
add(makeTitlePanel(), BorderLayout.NORTH);
JPanel mainPanel = new VerticalPanel();
add(mainPanel, BorderLayout.CENTER);
JPanel DPanel = new JPanel();
DPanel.setLayout(new GridLayout(3, 2));
DPanel.add(serverIp);
DPanel.add(port);
DPanel.add(param);
JPanel ControlPanel = new VerticalPanel();
ControlPanel.add(DPanel);
ControlPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.gray), "Parameters"));
mainPanel.add(ControlPanel);
}
public ThriftSamplerUI() {
super();
this.init();
}
@Override
public String getStaticLabel() {
return "Thrift Sampler";
}
@Override
public String getLabelResource() {
throw new IllegalStateException("This shouldn't be called");
}
/**
* 该方法创建一个新的Sampler,然后将界面中的数据设置到这个新的Sampler实例中。
* @return
*/
@Override
public TestElement createTestElement() {
ThriftSampler sampler = new ThriftSampler();
this.setupSamplerProperties(sampler);
return sampler;
}
private void setupSamplerProperties(ThriftSampler sampler) {
this.configureTestElement(sampler);
sampler.setServerIp(serverIp.getText());
sampler.setPort(Integer.valueOf(port.getText()));
sampler.setParam(param.getText());
}
/**
* 这个方法用于把界面的数据移到Sampler中。
* @param testElement
*/
@Override
public void modifyTestElement(TestElement testElement) {
ThriftSampler sampler = (ThriftSampler) testElement;
this.setupSamplerProperties(sampler);
}
/**
* 界面与Sampler之间的数据交换
* @param element
*/
@Override
public void configure(TestElement element) {
super.configure(element);
ThriftSampler sampler = (ThriftSampler) element;
this.serverIp.setText(sampler.getServerIp());
this.port.setText(sampler.getPort().toString());
this.param.setText(sampler.getParam());
}
/**
* 该方法会在reset新界面的时候调用,这里可以填入界面控件中需要显示的一些缺省的值(就是默认显示值)
*/
@Override
public void clearGui() {
super.clearGui();
this.serverIp.setText("服务端ip");
this.port.setText("9099");
this.param.setText("参数");
}
}
package com.thrift.demo.jmeterTest;
import com.thrift.demo.client.ThriftClient;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestStateListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author fit2cloudzhao
* @date 2022/8/2 21:45
* @description:
*/
public class ThriftSampler extends AbstractSampler implements TestStateListener {
Logger log = LoggerFactory.getLogger(ThriftSampler.class);
private ThriftClient thriftClient;
private static final String SERVER_IP = "server_ip";
private static final String PORT = "port";
private static final String PARAM = "request_param";
public ThriftSampler() {
setName("Thrift sampler");
}
@Override
public SampleResult sample(Entry entry) {
SampleResult sampleResult = new SampleResult();
// 开始统计响应时间标记
sampleResult.sampleStart();
try {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
String param = getParam();
String response = "";
System.out.println("入参:" + param);
log.info("入参:" + param);
thriftClient = getThriftClient();
if (StringUtils.isNotBlank(param)) {
response = thriftClient.getResponse(param);
} else {
response = thriftClient.getResponse("我是空的");
}
System.out.println("response==>" + response);
log.info("response==>" + response);
stopWatch.stop();
System.out.println(response + "总计花费:" + stopWatch.getTime());
log.info(response + "总计花费:" + stopWatch.getTime());
if (StringUtils.isNotBlank(response)) {
sampleResult.setSuccessful(true);
sampleResult.setResponseMessage(response);
sampleResult.setResponseData(("请求成功:"+response).getBytes());
sampleResult.setResponseCode("200");
} else {
sampleResult.setSuccessful(false);
sampleResult.setResponseMessage("请求失败....请求参数:" + param);
sampleResult.setResponseCode("500");
sampleResult.setResponseData("请求失败".getBytes());
}
} catch (Exception e) {
sampleResult.setSuccessful(false);
sampleResult.setResponseMessage("请求失败...." + e.getMessage());
sampleResult.setResponseCode("500");
sampleResult.setResponseData(("请求失败...." + e.getMessage()).getBytes());
} finally {
// 结束统计响应时间标记
sampleResult.sampleEnd();
}
return sampleResult;
}
public ThriftClient getThriftClient() {
if (thriftClient == null) {
thriftClient = new ThriftClient(getServerIp(), getPort(), 10000);
}
return this.thriftClient;
}
public String getServerIp() {
return getPropertyAsString(SERVER_IP);
}
public Integer getPort() {
return getPropertyAsInt(PORT);
}
public void setServerIp(String serverIp) {
setProperty(SERVER_IP, serverIp);
}
public void setPort(Integer port) {
setProperty(PORT, port);
}
public void setParam(String param) {
setProperty(PARAM, param);
}
public String getParam() {
return getPropertyAsString(PARAM);
}
@Override
public void testStarted() {
}
@Override
public void testStarted(String s) {
}
@Override
public void testEnded() {
this.testEnded("local");
}
@Override
public void testEnded(String s) {
}
}
参考链接:
https://wiki.fit2cloud.com/pages/viewpage.action?pageId=67671925
metersphere-plugin-DummySampler
选择resource之后,就会看到下面生成的MANIFEST.MF 文件,打开会看到很多class,说明选择成功。
这个也就是我们打的jar包。到此,jar已经打包完成了,放置到有jdk的环境当中 java -jar 就可以起来了。
我们这边封装成docker 镜像的方式,方便启动。
FROM fabric8/java-alpine-openjdk11-jre:latest // 注意arm/amd 的区别
MAINTAINER FIT2CLOUD <support@fit2cloud.com>
RUN mkdir -p /opt/apps
ADD out/artifacts/ThriftDemo_jar /opt/apps
WORKDIR /opt/apps
EXPOSE 9099
CMD ["java","-jar","ThriftDemo.jar"]
然后执行打成镜像, 并上传到服务器当中。
docker build -t ms-thrift-server:1.0 .
docker save -o ms-thrift-server.tar ms-thrift-server:1.0
上传服务器,加载镜像并启动
docker load -i ms-thrift-server.tar
docker run --name ms-thrift-server -d -p 9099:9099 ms-thrift-server:1.0
docker ps // 查看状态
docker logs -f ms-thrift-server // 出现启动Thrift 服务端,即启动成功。
万事俱备了,只欠最后一哆嗦。