性能工具之Jmeter压测Thrift RPC服务
概述
Thrift是一个可互操作和可伸缩服务的框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 等等编程语言间无缝结合的、高效的服务。
Thrift最初由facebook开发,07年四月开放源码,08年5月进入apache孵化器。thrift允许你定义一个简单的定义文件中的数据类型和服务接口(IDL)。以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。
其传输数据采用二进制格式,相对于XML和JSON等序列化方式体积更小,对于高并发、大数据量和多语言的环境更有优势。 Thrift它含有三个主要的组件:protocol,transport和server,其中,protocol定义了消息是怎样序列化的,transport定义了消息是怎样在客户端和服务器端之间通信的,server用于从transport接收序列化的消息,根据protocol反序列化之,调用用户定义的消息处理器,并序列化消息处理器的响应,然后再将它们写回transport。
官网地址:thrift.apache.org
基本概念
架构图

堆栈的顶部是从Thrift定义文件生成的代码。Thrift 服务生成的客户端和处理器代码。这些由图中的棕色框表示。红色框为发送的数据结构(内置类型除外)也会生成代码。协议和传输是Thrift运行时库的一部分。因此使用Thrift可以定义服务,并且可以自由更改协议和传输,而无需重新生成代码。 Thrift还包括一个服务器基础结构,用于将协议和传输绑定在一起。有可用的阻塞,非阻塞,单线程和多线程服务器。 堆栈的“底层I / O”部分根据所开发语言而有所不同。对于Java和Python网络I / O,Thrift库利用内置库,而C ++实现使用自己的自定义实现。
数据类型:
基本类型:
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:未知编码文本或二进制字符串,对应 Java 的 String
结构体类型:
struct:定义公共的对象,类似于 C 语言中的结构体定义,在 Java 中是一个 JavaBean
集合类型:
list:对应 Java 的 ArrayList
set:对应 Java 的 HashSet
map:对应 Java 的 HashMap
异常类型:
exception:对应 Java 的 Exception
服务类型:
service:对应服务的类
数据传输层Transport
TSocket —— 使用阻塞式 I/O 进行传输,是最常见的模式
TFramedTransport —— 使用非阻塞方式,按块的大小进行传输,类似于 Java 中的 NIO,若使用 TFramedTransport 传输层,其服务器必须修改为非阻塞的服务类型
TNonblockingTransport —— 使用非阻塞方式,用于构建异步客户端
数据传输协议Protocol
Thrift 可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本 (text) 和二进制 (binary) 传输协议,为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数,有时还会使用基于文本类型的协议,这需要根据项目 / 产品中的实际需求。
常用协议有以下几种:
TBinaryProtocol : 二进制格式.
TCompactProtocol : 高效率的、密集的二进制压缩格式
TJSONProtocol : JSON格式
TSimpleJSONProtocol : 提供JSON只写协议, 生成的文件很容易通过脚本语言解析
注意:客户端和服务端的协议要一致。
服务器类型Server
TSimpleServer ——单线程服务器端使用标准的阻塞式 I/O,一般用于测试。
TThreadPoolServer —— 多线程服务器端使用标准的阻塞式 I/O,预先创建一组线程处理请求。
TNonblockingServer —— 多线程服务器端使用非阻塞式 I/O,服务端和客户端需要指定 TFramedTransport 数据传输的方式。
THsHaServer —— 半同步半异步的服务端模型,需要指定为: TFramedTransport 数据传输的方式。它使用一个单独的线程来处理网络I/O,一个独立的worker线程池来处理消息。这样,只要有空闲的worker线程,消息就会被立即处理,因此多条消息能被并行处理。
TThreadedSelectorServer —— TThreadedSelectorServer允许你用多个线程来处理网络I/O。它维护了两个线程池,一个用来处理网络I/O,另一个用来进行请求的处理。当网络I/O是瓶颈的时候,TThreadedSelectorServer比THsHaServer的表现要好。
实现逻辑
服务端
实现服务处理接口 impl
创建TProcessor 创建TServerTransport 创建TProtocol 创建TServer 启动Server
客户端
创建Transport 创建TProtocol 基于TTransport和TProtocol创建 Client 调用Client的相应方法
ThriftServerDemo实例
新建 Maven项目,并且添加 thrift依赖包
<dependencies><dependency><groupId>org.apache.thrift</groupId><artifactId>libthrift</artifactId><version>0.9.3</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.12</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.7</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.7</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></plugins></build>
编写 IDL接口并生成接口文件
namespace java thrift.service// 计算类型 - 仅限整数四则运算enum ComputeType {ADD = 0;SUB = 1;MUL = 2;DIV = 3;}// 服务请求struct ComputeRequest {1:required i64 x;2:required i64 y;3:required ComputeType computeType;}// 服务响应struct ComputeResponse {1:required i32 errorNo;2:optional string errorMsg;3:required i64 computeRet;}service ComputeServer {ComputeResponse getComputeResult(1:ComputeRequest request);}
执行编译命令:
thrift-0.11.0.exe -r -gen java computeServer.thrift
拷贝生成的 Service类文件到 IDEA

服务端接口实现
public class ThriftTestImpl implements ComputeServer.Iface {private static final Logger logger = LogManager.getLogger(ThriftTestImpl.class);public ComputeResponse getComputeResult(ComputeRequest request) {ComputeType computeType = request.getComputeType();long x = request.getX();long y = request.getY();logger.info("get compute result begin. [x:{}] [y:{}] [type:{}]", x, y, computeType.toString());long begin = System.currentTimeMillis();ComputeResponse response = new ComputeResponse();response.setErrorNo(0);try {long ret;if (computeType == ComputeType.ADD) {ret = add(x, y);response.setComputeRet(ret);} else if (computeType == ComputeType.SUB) {ret = sub(x, y);response.setComputeRet(ret);} else if (computeType == ComputeType.MUL) {ret = mul(x, y);response.setComputeRet(ret);} else {ret = div(x, y);response.setComputeRet(ret);}} catch (Exception e) {response.setErrorNo(1001);response.setErrorMsg(e.getMessage());logger.error("exception:", e);}long end = System.currentTimeMillis();logger.info("get compute result end. [errno:{}] cost:[{}ms]", response.getErrorNo(), (end - begin));return response;}private long add(long x, long y) {return x + y;}private long sub(long x, long y) {return x - y;}private long mul(long x, long y) {return x * y;}private long div(long x, long y) {return x / y;}}
服务端实现
public class ServerMain {private static final Logger logger = LogManager.getLogger(ServerMain.class);public static void main(String[] args) {try {//实现服务处理接口implThriftTestImpl workImpl = new ThriftTestImpl();//创建TProcessorTProcessor tProcessor = new ComputeServer.Processor<ComputeServer.Iface>(workImpl);//创建TServerTransport,非阻塞式 I/O,服务端和客户端需要指定 TFramedTransport 数据传输的方式final TNonblockingServerTransport transport = new TNonblockingServerSocket(9999);//创建TProtocolTThreadedSelectorServer.Args ttpsArgs = new TThreadedSelectorServer.Args(transport);ttpsArgs.transportFactory(new TFramedTransport.Factory());//二进制格式反序列化ttpsArgs.protocolFactory(new TBinaryProtocol.Factory());ttpsArgs.processor(tProcessor);ttpsArgs.selectorThreads(16);ttpsArgs.workerThreads(32);logger.info("compute service server on port :" + 9999);//创建TServerTServer server = new TThreadedSelectorServer(ttpsArgs);//启动Serverserver.serve();} catch (Exception e) {logger.error(e);}}}
服务端整体代码结构

log4j2.xml配置文件
<?xml version="1.0" encoding="UTF-8"?><!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --><!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--><!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--><configuration status="INFO" monitorInterval="30"><!--先定义所有的appender--><appenders><!--这个输出控制台的配置--><console name="Console" target="SYSTEM_OUT"><!--输出日志的格式--><PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [%l] %m%n}"/></console><RollingFile name="RollingFileInfo" fileName="log/log.log" filePattern="log/log.log.%d{yyyy-MM-dd}"><!-- 只接受level=INFO以上的日志 --><ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/><PatternLayout pattern="[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n"/><Policies><TimeBasedTriggeringPolicy modulate="true" interval="1"/><SizeBasedTriggeringPolicy/></Policies></RollingFile><RollingFile name="RollingFileError" fileName="log/error.log" filePattern="log/error.log.%d{yyyy-MM-dd}"><!-- 只接受level=WARN以上的日志 --><Filters><ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" /></Filters><PatternLayout pattern="[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n"/><Policies><TimeBasedTriggeringPolicy modulate="true" interval="1"/><SizeBasedTriggeringPolicy/></Policies></RollingFile></appenders><!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--><loggers><!--过滤掉spring和mybatis的一些无用的DEBUG信息--><logger name="org.springframework" level="INFO"></logger><logger name="org.mybatis" level="INFO"></logger><root level="all"><appender-ref ref="Console"/><appender-ref ref="RollingFileInfo"/><appender-ref ref="RollingFileError"/></root></loggers></configuration>
Jmeter测试类编写
利用JMeter调用Java测试类去调用对应的后台服务,并记住每次调用并获取反馈值的RT,ERR%,只需要按照单线程的方式去实现测试业务,也无需添加各种埋点收集数据
新建一个 JavaMaven工程,添加 JMeter及 thrift依赖包
<dependencies><dependency><groupId>org.apache.jmeter</groupId><artifactId>ApacheJMeter_core</artifactId><version>4.0</version></dependency><dependency><groupId>org.apache.jmeter</groupId><artifactId>ApacheJMeter_java</artifactId><version>4.0</version></dependency><dependency><groupId>org.apache.thrift</groupId><artifactId>libthrift</artifactId><version>0.11.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.11.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.11.1</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.7.0</version><configuration><source>1.8</source><target>1.8</target><encoding>utf-8</encoding></configuration></plugin></plugins></build>
ThriftClient测试类编写
public class ThriftClient {private ComputeServer.Client client = null;private TTransport tTransport = null;public ThriftClient(String ip,int port){try {TTransport tTransport = new TFramedTransport(new TSocket(ip,port));tTransport.open();TProtocol tProtocol = new TBinaryProtocol(tTransport);client = new ComputeServer.Client(tProtocol);} catch (TTransportException e) {e.printStackTrace();}}public ComputeResponse getResponse(ComputeRequest request){try {ComputeResponse response = client.getComputeResult(request);return response;} catch (TException e) {e.printStackTrace();return null;}}public void close(){if (tTransport != null && tTransport.isOpen()){tTransport.close();}}}
注意:需要把编写 IDL接口文件拷贝到工程里
新建一个 JavaClass,如下例中的 TestThriftByJmeter,并继承 AbstractJavaSamplerClient。 AbstractJavaSamplerClient中默认实现了四个可以覆盖的方法,分别是 getDefaultParameters(), setupTest(), runTest()和 teardownTest()方法。
getDefaultParameters方法主要用于设置传入界面的参数;setupTest方法为初始化方法,用于初始化性能测试时的每个线程;runTest方法为性能测试时的线程运行体;teardownTest方法为测试结束方法,用于结束性能测试中的每个线程。
编写TestThriftByJmeter测试类
public class TestThriftByJmeter extends AbstractJavaSamplerClient {private ThriftClient client;private ComputeRequest request;private ComputeResponse response;//设置传入界面的参数@Overridepublic Arguments getDefaultParameters(){Arguments arguments = new Arguments();arguments.addArgument("ip","172.16.14.251");arguments.addArgument("port","9999");arguments.addArgument("X","0");arguments.addArgument("Y","0");arguments.addArgument("type","0");return arguments;}//初始化方法@Overridepublic void setupTest(JavaSamplerContext context){//获取Jmeter中设置的参数String ip = context.getParameter("ip");int port = context.getIntParameter("port");int x = context.getIntParameter("X");int y = context.getIntParameter("Y");ComputeType type = ComputeType.findByValue(context.getIntParameter("type"));//创建客户端client = new ThriftClient(ip,port);//设置request请求request = new ComputeRequest(x,y,type);super.setupTest(context);}//性能测试线程运行体@Overridepublic SampleResult runTest(JavaSamplerContext context) {SampleResult result = new SampleResult();//开始统计响应时间标记result.sampleStart();try {long begin = System.currentTimeMillis();response = client.getResponse(request);long cost = (System.currentTimeMillis() - begin);//打印时间戳差值。Java请求响应时间System.out.println(response.toString()+" 总计花费:["+cost+"ms]");if (response == null){//设置测试结果为fasleresult.setSuccessful(false);return result;}if (response.getErrorNo() == 0){//设置测试结果为trueresult.setSuccessful(true);}else{result.setSuccessful(false);result.setResponseMessage("ERROR");}}catch (Exception e){result.setSuccessful(false);result.setResponseMessage("ERROR");e.printStackTrace();}finally {//结束统计响应时间标记result.sampleEnd();}return result;}//测试结束方法public void tearDownTest(JavaSamplerContext context) {if (client != null) {client.close();}super.teardownTest(context);}}
特别说明:
result.setSamplerLabel("7D"); //设置java Sampler的标题result.setResponseOK(); //设置响应成功result.setResponseData(); //设置响应内容
编写测试Run Main方法
public class RunMain {public static void main(String[] args) {Arguments arguments = new Arguments();arguments.addArgument("ip","172.16.14.251");arguments.addArgument("port","9999");arguments.addArgument("X","1");arguments.addArgument("Y","3");arguments.addArgument("type","0");JavaSamplerContext context = new JavaSamplerContext(arguments);TestThriftByJmeter jmeter = new TestThriftByJmeter();jmeter.setupTest(context);jmeter.runTest(context);jmeter.tearDownTest(context);}}
测试结果通过

使用 mvn cleanpackage打包测试代码

使用 mvn dependency:copy-dependencies-DoutputDirectory=lib复制所依赖的jar包都会到项目下的lib目录下

复制测试代码 jar包到 jmeter\lib\ext目录下,复制依赖包到 jmeter\lib目录下

这里有两点需要注意:
如果你的jar依赖了其他第三方jar,需要将其一起放到lib/ext下,否则会出现ClassNotFound错误
如果在将jar放入lib/ext后,你还是无法找到你编写的类,且此时你是开着JMeter的,则需要重启一下JMeter
打开 Jmeter,在添加 Java请求时,注意要选择 Jmeter测试类,下面的列表中可以看到参数和默认值。

下面我们将进行性能压测,设置线程组,设置10个并发线程。

服务端日志:

性能工具之Jmeter压测Thrift RPC服务的更多相关文章
- 性能工具之Jmeter压测Hprose RPC服务
概述 Hprose(High Performance Remote Object Service Engine),国人开发的一个远程方法调用的开源框架.它是一个先进的轻量级的跨语言跨平台面向对象的高性 ...
- Jmeter压测Thrift服务接口
此文已由作者夏鹏授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. Apache Jmeter是基于Java开发的性能测试工具,支持多种协议的测试,包括:Web(HTTP/HTT ...
- 【Java分享客栈】未来迈向高级工程师绕不过的技能:JMeter压测
前言 因为工作需要,久违的从自己的有道云笔记中去寻找压测相关的内容,翻开之后发现还不错,温故一遍后顺便整理出来分享给大家. 题外话,工作8年多,有道云笔记不知不觉都6G多了,扫一眼下来尽是云烟过往,竟 ...
- JMeter接口压测——ServerAgent监控服务端性能指标
ServerAgent作为一个服务端性能监控插件,结合JMeter自身插件PerfMon可以实现JMeter压测的图形化实时监控,具有良好的实用性.下面讲解一个应用实例 思路: 1. 插件准备 2.打 ...
- windows下Jmeter压测端口占用问题(亲测有效)
windows下Jmeter压测端口占用问题 1 报错信息描述 压测的初期,在设置了 150qps/s 的并发数下压测几分钟后 Jmeter 就出现了如下报错. JAVA.NET.BINDEXCEPT ...
- jmeter压测、操作数据库、分布式、 linux下运行的简单介绍
一.jmeter压测 1.如何压测 常规性能压测:10-15分钟 稳定性测试:一周.2天等 如果想要压测10分钟,勾选永远,勾选调度器,填写600秒.也可以使用固定启动时间. 2.tps.响应时间 ( ...
- windows下Jmeter压测端口占用问题
https://blog.csdn.net/weixin_43757847/article/details/88188091 1 前情提要人脸识别项目中,云平台新增了人脸识别的校验接口.考虑到存在大量 ...
- 一文揭秘测试平台中是如何将测试用例一键转化Jmeter压测脚本
接上篇,一键转化将接口测试平台测试用例转化成Jmeter压测脚本思路,这里我首先在java 上面做了一个简单的实验,看看 转化的中间遇到的问题,这里呢,我只是给了一个简单的demo 版本, ...
- jmeter压测app
使用代理的方式,录制app端脚本,之后用jmeter压测就没啥好说的了 1.电脑端谷歌设置本地代理(端口号为8888) 2.jmeter设置HTTP代理服务器(端口号为8888) 3.手机端wifi设 ...
随机推荐
- 推荐一个不得不知道的 Visual Studio 快捷键
不得不说,Visual Studio 内置了很多非常棒的快捷键,借助于这些快捷键我们甚至不需要再使用鼠标,就可以快速高效的编写代码,因此学习和熟悉这些快捷键是值得的. 其中有一个快捷键是我非常喜欢,也 ...
- 优麒麟使用教程第三期:Windows 平台 U 盘启动盘制作
优麒麟使用教程第三期:Windows 平台 U 盘启动盘制作 发布时间:2019-06-27 09:00:15 点击次数:2847 在前几期教程中,小编介绍了如何在虚拟机中安装和使用优麒麟,接下来,小 ...
- 《SystemVerilog验证-测试平台编写指南》学习 - 第1章 验证导论
<SystemVerilog验证-测试平台编写指南>学习 - 第1章 验证导论 测试平台(testbench)的功能 方法学基础 1. 受约束的随机激励 2. 功能覆盖率 3. 分层的测试 ...
- MegaCli是一款管理维护硬件RAID软件,可以通过它来了解当前raid卡的所有信息,包括 raid卡的型号,raid的阵列类型,raid 上各磁盘状态
MegaCli 监控raid状态 转载weixin_30344131 最后发布于2015-10-16 13:05:00 阅读数 简介 MegaCli是一款管理维护硬件RAID软件,可以通过它来了 ...
- Linux_权限管理理论概述
一.权限定义 1.文件权限作用的对象 owner :属主 - u group :属组 - g other :其他人 - o 2.文件的三种权限 //针对文件的权限 r 可读 可以使用cat命令查看文件 ...
- docker容器中日志文件过大处理方法
背景 :在日常工作中一个基于centos镜像构建起来的python爬虫程序,日志文件在两个月内到了500G,日志存放在根目录下面,在不扩容的情况下把这个问题给解决掉.通过定时任务和脚本的方法,定期的清 ...
- JDK 14 都已经发布了,Java 8 依然是我的最爱
在 JDK 版本的世界里,从来都是 Oracle 发他的新版本,我们 Java 程序员继续用我们的老版本 几年之前用 JDK 7,后来终于升级到了 JDK 8.自从升级了没多久,JDK 就开始了半年发 ...
- 设置添加SSH-(转自破男孩)
很多朋友在用github管理项目的时候,都是直接使用https url克隆到本地,当然也有有些人使用 SSH url 克隆到本地.然而,为什么绝大多数人会使用https url克隆呢? 这是因为,使用 ...
- docker启动失败如何查看容器日志
docker启动失败如何查看容器日志 在使用docker的时候,在某些未知的情况下可能启动了容器,但是过了没几秒容器自动退出了.这个时候如何排查问题呢? 通常碰到这种情况无非就是环境有问题或者应用有问 ...
- Linux 中/var/spool/postfix/maildrop目录下堆积大量小文件 如何删除
Linux 中/var/spool/postfix/maildrop目录下堆积大量小文件 如何删除 1.先删除maildrop目录下的通知邮件文件 命令:find /var/spool/postf ...