nGrinder 是一个基于 Grinder 二次开发开源分布式性能压测平台,可以用于大规模的压力测试,基于 Jython编写,是由 NBP(Naver Business Platform) 和 NHN China 联合开发的,官方网站为 http://www.nhnopensource.org/。nGrinder 提供了基于Web界面的可视化管理平台,简洁易用还支持中文,可以通过脚本(python 或 groovy)编写测试用例,并能够监控目标机的运行状态,并产出测试报告。
NHN 是一家韩国搜索门户网站, NBP 是从 NHN 分离出来的一家专注于商业平台公司。
nGrinder 是基于 Grinder 进行的封装,要了解 nGrinder 的原理,先要了解一下 Grinder 的工作方式,Grinder 的架构图如下:
(http://grinder.sourceforge.net/g3/getting-started.html#howtostart)
Grinder 是一个分布式的测试脚本运行框架,其核心由三个进程组成,工作进程(worker)、代理(agent)和控制台( console)。
- 工作进程: 工作进程解析并执行测试脚本。每个工作进程可以通过多线程的方式并行运行多个测试基本。
- 代理进程:是一个长期运行的值守进程,负责工作进程的启动、关闭,以及管理分发自控制台的测试脚本。代理进程可以启动多个并分布在不同的主机上。
- 控制台:负责管理其他进程,手机并展示统计信息,以及提供脚本的编写和分发。
nGrinder 是在 Grinder 基础上做的封装和扩展,使用控制器(Controller)和代理(nGrinder 在 Grinder 的代理基础上做的进一步分装),可以同时执行多套测试脚本。其中控制器提供web管理工具,并负责和其他进程协作。代理负责执行测试脚本,将压力流量发送给目标主机。
当代理启动时,会自动连接控制器,并注册到 AgentControllerServer 组件中,并和Controler 保持持久通信,AgentControllerServer 类似于一个代理资源池,管理所有的代理程序。每当用户运行一次性能测试时,都会从 ConsoleManager 中获取一个空闲的控制台,没有则新建,并从AgentControllerServer中获取足够数量的代理,并和当前分配的控制台进行绑定,然后控制台(为了和 Grinder 中的控制台做区分,这里的控制台被命名为SingleConsole )向已分配的代理发送测试脚本和测试资源文件,并控制本次性能压测的运行,直到测试完成。当测试完成之后,代理资源将会回收到 AgentControllerServer 中,同样 SingleConsole 也返回给 ConsoleManager。
nGrinder和Grinder之间的最大不同在于nGrinder可以在控制器中保留多个控制台实例和代理,控制器之间相互独立,并且可以同时运行,所以在 nGrinder 中可以同时让多个用户运行多个性能压测。代理可以在动态分配、循环利用,相比于 Grinder,nGrinder 可以最大化利用代理机的资源。
下面我们通过例子演示 nGrinder 的用法,设定4台主机,一台控制器,两台代理,一台目标机,角色分配如下:
主机 | 角色 |
---|---|
192.168.56.101 | 控制器 |
192.168.56.106 | 代理 |
192.168.56.107 | 代理 |
192.168.56.109 | 目标机(被压测机器) |
安装控制器
控制器的安装非常简单,官方提供了编译好的 war 包,支持自运行,也可以部署在 Web 容器中(比如Tomcat)运行,这里以自运行的方式为例:
$ wget https://github.com/naver/ngrinder/releases/download/ngrinder-3.4.1-20170131/ngrinder-controller-3.4.1.war
$ java -XX:MaxPermSize=256m -jar ngrinder-controller-3.4.1.war --port 8080
访问,http://192.168.56.101:8080/,打开 nGridner,初始账号密码为 admin/admin,界面截图如下:
安装Agent
代理程序可以通过 Web 界面下载,如上图所示,也可以通过 url 直接下载。代理程序是一个压缩包,解压就可以使用,并且包含了已经设置好的配置文件,默认指向控制器的IP地址。
下载代理程序压缩包
$ wget http://192.168.56.101:8080/agent/download/ngrinder-agent-3.4.1-192.168.56.101.tar
解压代理程序压缩包
$ tar zxvf ngrinder-agent-3.4.1-192.168.56.101.tar
$ ll ./ngrinder-agent
-rw-r--r--. 1 root root 536 Oct 20 00:59 __agent.conf
drwxr-xr-x. 2 root root 4096 Oct 20 01:04 lib
-rwxr-xr-x. 1 root root 367 Oct 20 00:59 run_agent.bat
-rwxr-xr-x. 1 root root 83 Oct 20 00:59 run_agent_bg.sh
-rwxr-xr-x. 1 root root 237 Oct 20 00:59 run_agent_internal.bat
-rwxr-xr-x. 1 root root 99 Oct 20 00:59 run_agent_internal.sh
-rwxr-xr-x. 1 root root 312 Oct 20 00:59 run_agent.sh
-rwxr-xr-x. 1 root root 135 Oct 20 00:59 stop_agent.bat
-rwxr-xr-x. 1 root root 136 Oct 20 00:59 stop_agent.sh
查看默认配置文件,其中的控制器地址和端口都已经自动配置好了。
$ cat __agent.conf
common.start_mode=agent
agent.controller_host=192.168.56.101
agent.controller_port=16001
agent.region=NONE
#agent.host_id=
#agent.server_mode=true
# provide more agent java execution option if necessary.
#agent.java_opt=
# set following false if you want to use more than 1G Xmx memory per a agent process.
#agent.limit_xmx=true
# please uncomment the following option if you want to send all logs to the controller.
# agent.all_logs=true
# some jvm is not compatible with DNSJava. If so, set this false.
# agent.enable_local_dns=false
启动代理程序,nGrinder 的压缩包中已经提供了多种平台的启动脚本,在 Linux 系统下,前台启动用 run_agent.sh,后台运行用 run_agent_bg.sh,关闭 agent 用 stop_agent.sh。
$ ./run_agent_bg.sh -o
代理程序启动之后会在用户目录创建配置文件,路径如 ~/.ngrinder_agent/agent.conf,-o 选项表示用当前目录的 __agent.conf 文件覆盖用户目录中的配置文件。
在进行性能压测是,代理程序会启动大量的线程或进程去执行,而 Linux 默认的最大进程(或线程)比较小,可以通过 ulimit 进行修改,比如 ulimit -u 32768, nGrinder 建议至少调到 10000 以上。另外,最大允许打开文件书也需要调整 ulimit -n 32768。使用 ulimit 指令是临时生效,永久生效可以修改 /etc/security/limits.conf 文件。
在 192.168.56.106 和 192.168.56.107 中执行上述操作。启动成功之后,在控制台(192.168.56.101)的代理管理菜单中可以看到已经注册的代理程序。
安装监控组件
监控程序是一个可选组件,运行在目标测试机器上,控制台通过监控程序搜集在性能压测是,目标机的系统负载情况,比如CPU,内存,收发数量等等。
和代理程序一样监控程序也是一个压缩包,可以通过Web控制台下载,如图:
也可以通过URL下载,比如:
$ wget http://192.168.56.101:8080/monitor/download/ngrinder-monitor-3.4.1.tar
$ tar xvf ngrinder-monitor-3.4.1.tar
$ ll ./ngrinder-monitor
total 28
-rw-r--r--. 1 root root 197 Oct 20 03:15 __agent.conf
drwxr-xr-x. 2 root root 4096 Oct 20 03:44 lib
-rwxr-xr-x. 1 root root 136 Oct 20 03:15 run_monitor.bat
-rwxr-xr-x. 1 root root 85 Oct 20 03:15 run_monitor_bg.sh
-rwxr-xr-x. 1 root root 136 Oct 20 03:15 run_monitor.sh
-rwxr-xr-x. 1 root root 137 Oct 20 03:15 stop_monitor.bat
-rwxr-xr-x. 1 root root 137 Oct 20 03:15 stop_monitor.sh
监控程序的配置也比较简单,主要是配置端口号,如下:
$ cat __agent.conf
common.start_mode=monitor
# If you want to monitor bind to the different local ip not automatically selected ip. Specify below field.
#monitor.binding_ip=hostname_or_ip
monitor.binding_port=13243
启动监程序,在压测运行时,控制器会通过JMX和目标机的监控程序通信,从目标机搜集负载数据。
$ ./run_monitor_bg.sh -o
开始测试
首先创建测试脚本,通过Web工具配置如下图所示,这里我们以 压测 http://192.168.56.109/test.php 这个地址为例,脚本类型可以使用 Groovy 或 Jython。nGrinder 会自动生成测试代码。
生成代码截图,可以通过自带的编辑器修改代码,本例中我们只做简单测试,不需要修改。目标机可以绑定多台,这里我们只绑定192.168.56.109这一台。点击“验证脚本”按钮测试测试脚本是否可以正确执行。
下面是生成的 Groovy 测试脚本。
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.plugin.http.HTTPRequest
import net.grinder.plugin.http.HTTPPluginControl
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
// import static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Date
import java.util.List
import java.util.ArrayList
import HTTPClient.Cookie
import HTTPClient.CookieModule
import HTTPClient.HTTPResponse
import HTTPClient.NVPair
/**
* A simple example using the HTTP plugin that shows the retrieval of a
* single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {
public static GTest test
public static HTTPRequest request
public static NVPair[] headers = []
public static NVPair[] params = []
public static Cookie[] cookies = []
@BeforeProcess
public static void beforeProcess() {
HTTPPluginControl.getConnectionDefaults().timeout = 6000
test = new GTest(1, "192.168.56.109")
request = new HTTPRequest()
// Set header datas
List<NVPair> headerList = new ArrayList<NVPair>()
headerList.add(new NVPair("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0"))
headers = headerList.toArray()
// Set param datas
List<NVPair> paramList = new ArrayList<NVPair>()
paramList.add(new NVPair("param1", "value1"))
params = paramList.toArray()
// Set cookie datas
List<Cookie> cookieList = new ArrayList<Cookie>()
cookieList.add(new Cookie("t", "test", "192.168.56.109", "/", new Date(32503647599000L), false))
cookies = cookieList.toArray()
grinder.logger.info("before process.");
}
@BeforeThread
public void beforeThread() {
test.record(this, "test")
grinder.statistics.delayReports=true;
grinder.logger.info("before thread.");
}
@Before
public void before() {
request.setHeaders(headers)
cookies.each { CookieModule.addCookie(it, HTTPPluginControl.getThreadHTTPClientContext()) }
grinder.logger.info("before thread. init headers and cookies");
}
@Test
public void test(){
HTTPResponse result = request.GET("http://192.168.56.109/test.php", params)
if (result.statusCode == 301 || result.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
} else {
assertThat(result.statusCode, is(200));
}
}
}
创建性能压测的配置,如图:
- 代理:使用多少个代理程序来运行压测。本例中设置了两个代理主机 192.168.56.106 和 192.168.56.107,这里我们设置两台都用。
- 每个代理的虚拟用户数:用来设置有多少个并发请求,每一个线程代表一个虚拟用户,这里我们创建1000个虚拟用户,每个代理设置为500,表示每个代理程序模拟500个用户,并用 10 个进程,每个进程启动50个线程来模拟,在这个例子中,一个用户请求结束之后会立即发起下一个请求。但建议进程不要启动太多,每一个进程会启动一个JVM,进程过多会占用过度内存资源。
- 脚本:测试用的脚本名称。
- 脚本相关的资源:有的压测可能需要事先构造好的文件,比如随机用户ID文件,资源文件可以在脚本中测试脚本中引用,需要改写代码。本例中没有上传资源文件,所以这里为空。
- 目标主机:可以同时对多台台主机进行压测,本例子只使用一台。
- 测试时间:压测的执行时间,这里我们选择一分钟。
- 测试次数:测试时间和测试次数只能二选一,测试次数表示执行多少次测试脚本之后结束。
- Ramp-Up:启用递增压测,这也是在实际压测中常用的方式,流量不是一下子压倒最高,而是按照一定的的证顺序逐渐加压。在本例中,按照每10秒增加10个虚拟用户(每个进程)的速度递增压力,每个代理启用10个线程,递增的节奏是 100,200,300等等,在地50秒时达到500个。如果使用5个进程,会按照 50,100,150,200 的次数递增。
测试完成之后会数据详细的测试报告,包括TPS,平均响应时间,压测过程中的出错率等等。
https://github.com/naver/ngrinder/wiki/Cluster-Architecture
在 nGrinder 3.1 开始,可以按照集群方式运行,不仅解决了Controler 问题,可以水平扩展称为当大代集群,按区域划分Agent为不用的部分划分不用的资源。
- nGrinder uses an embedded svn server(SVNKit DAV) to manage script files. The svn repositories are stored in ${NGRINDER_HOME} directory.
- nGrinder uses EhCache to improve the data retrieval performance from DB and SVN repositories.
- nGrinder uses Spring Security to protect the system and uses Atlassian Plugin Framework for extensibility.
If you enable clustering mode by configuring the system.conf (See link) and install nGrinder into multiple machine, the system architect is changed as below:
- All the controllers in the cluster shares the same DB and file system. All ngrinder controller should point same ${NGRINDER_HOME} folder which should be shared by NFS.
- Each controllers can have its own special properties and log output folder. This information will be saved in ${NGRINDER_EX_HOME} in the each controller.
- All controllers replicates the EhCache to each other to make some data to be visible in all controllers in cluster.
Each controller can serve nGrinder web content equally but handles the different test set depending the region. We don't provide any session clustering by default. So you may suffer login problem because sessions stored in a controller are not replicated to the other controllers. You may need to configure this by referring Tomcat Session Clustering Guide or use the sticky session on L4. If you are not a expert on this, Just let users to connect only one controller. It's the easiest. :-)
(TODO)