前言
现在要支持 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/
Mac 安装还是比较方便的直接通过 brew 的方式安装
brew install thrift
安装成功之后看下版本信息,出现版本即为成功。
thrift -version
![](/uploads/photo/2023/5b76161a-6760-41c5-9a1c-1b05c8a21db7.png!large)
## 2.3 IDEA中编写Thrift 脚本
1. 首先需要安装支持Thrift IDL语言的插件。(Thrift Support)
2. 这个插件对idea的版本有限制,安装的时候需要注意下。
3. 当然也可以直接在任何文本编辑器中进行编写,保存文件的时候保存为.thrift为结尾的就行。
插件地址:[https://plugins.jetbrains.com/plugin/7331-thrift-support](https://plugins.jetbrains.com/plugin/7331-thrift-support)
![](/uploads/photo/2023/f4c831ec-52ee-4103-aa79-3e49aa176798.png!large)
3. 下载完之后就可以编写IDL脚本了。
### 2.3.1 Thrift IDL 语法对应如下:
- 基本类型:
bool:布尔值,true 或 false,对应 Java 的 boolean
byte:8 位有符号整数,对应 Java 的 byte
i16:16 位有符号整数,对应 Java 的 short
i32:32 位有符号整数,对应 Java 的 int
i64:64 位有符号整数,对应 Java 的 long
double:64 位浮点数,对应 Java 的 double
string:utf-8 编码的字符串,对应 Java 的 String
- 结构体类型:
struct:定义公共的对象,类似于 C 语言中的结构体定义,在 Java 中是一个 JavaBean
- 容器类型:
list:对应 Java 的 ArrayList
set:对应 Java 的 HashSet
map:对应 Java 的 HashMap
- 异常类型:
exception:对应 Java 的 Exception
- 服务类型:
service:对应服务的类。
### 2.3.2 服务端编码基本步骤
1. 实现服务处理接口 impl
2. 创建 TProcessor
3. 创建 TServerTransport
4. 创建 TProtocol
5. 创建 TServer
6. 启动 Server
### 2.3.3 客户端编码基本步骤
1. 创建 Transport
2. 创建 TProtocol
3. 基于 TTransport 和 TProtocol 创建 Client
4. 调用 Client 的相应方法
### 2.3.4 数据传输协议
1. TBinaryProtocol 二进制格式
2. TCompactProtocol 压缩格式
3. TJSONProtocol JSON 格式
4. TSimpleJSONProtocol 提供 JSON 只写协议,生成的文件很容易通过脚本语言解析
**提示**: 客户端和服务端的协议要一致
### 2.3.5 编写Thrift脚本
1. 创建一个maven或者java 项目都可以
![](/uploads/photo/2023/79af314a-531b-43fa-a9e5-a400b3fe5a91.png!large)
2. 编写Thrift脚本
```csharp
namespace java com.thrift.demo // 使用java 语言定义 package
service ThriftHelloService{ // 定义一个service 相当于java当中的接口
string sayHello(1:string username) // 输入一个参数,返回string类型参数。
}
编译成功之后会在当前项目里面看到.
可以看到报错不用管,因为这个项目就是我们用来使用生成 java 类的项目。可以看到这个类中,有很多代码,
简单分析一下这个类:生成的类主要有 5 个部分
自动生成的接口有两个,一个是同步调用的 Iface,一个是异步调用的 AsyncIface。异步调用的接口多了一个回调参数。
详细的源码分析参考:https://www.kancloud.cn/digest/thrift/118986
<dependencies>
<!-- thrift 集成-->
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.16.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<!-- jmeter 集成-->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>5.5</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_java</artifactId>
<version>5.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!-- 完整依赖打包 集成-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
写接口的实现类
public class ThriftHelloServiceImpl implements ThriftHelloService.Iface {
@Override
public String sayHello(String username) throws TException {
return "Hello Thrift :" + username;
}
}
写服务端 main 方法
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,说明选择成功。
然后再 build 一下
build 完之后会看到当前项目当中有一个 out 的文件夹
找到
这个也就是我们打的 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 服务端,即启动成功。
万事俱备了,只欠最后一哆嗦。