Showing posts with label Netty. Show all posts
Showing posts with label Netty. Show all posts

Tuesday, March 29, 2016

How to use distributed JMeter to test netty server performance

JMeter is good at performance testing. Usually, we often need more than one hosts to send traffic to one server to see how the sever behaves. This Many-To-One test scenario is called distributed testing.  In JMeter's world, the hosts, which generate traffic,  are called 'jmeter server'. You can think them as slaves. The one, on which you setup test plan and control the start/stop of others, is called 'jmeter client' . You can think this only one as master.

image

Netty is a high performanc NIO framework. In this demo, a simple Echo server written with Netty 4 is what's going to be tested.  This demo use 5 other hosts to generate traffic to the echo server with long connection.  Everyone establishes 20,000 connections to the server and 100,000 connections totally for 5 hosts.

This article wants to bring you:

  1. Basic concepts of JMeter. like Thread group, Logic Controller, Sampler, Timer and Listener,
  2. How to setup a jmeter testplan with loop, thread group, timer, sampler and listeners
  3. How to run jmeter in distributed test
  4. How to read the test result.
  5. Have a basic understanding of netty's performance. Since only 5 hosts used to generate traffic,  we can't fully show the capacity of the server, but only some idea of the workload in 100,000 connections. 

The JMeter version used in this article is 2.13. The server under test works as an Echo server, just send back what is recieved. The Echo server's code can be found in the end of this article.

1. JMeter basic elements

Every test you run is called a 'test plan', it's a container of other elements.  In a 'test plan', there is usually a 'thread group', which defines the concurrency of your test, like how many thread you want to use to run the 'sampler'.

What is 'sampler'? it's kinda of a real action the test performs, like send out a http request, a tcp request or make a JDBC connection.  'Timer' can help your test wait for some time before perform another action. The name of 'Listener' is a little abstract, but what 'listener' do is just show you the result of the test.  You can have more than one listeners to show the result in different format. e.g. in table or in chart.

JMeter also bunch of 'logic controller' to help you control test logic. In this demo, loop is used to send TCP request repeatedly.

2.Config JMeter test plan.

Test plan is the running unit of JMeter. It has all the test logic defined in it. In distributed testing, this test plan only need to be defined once on the master, and will be submitted to all slaves hosts automatically.

Let's define a test plan to send tcp request repeatly.  The steps are common for other kind of tests.

2.1 Add 'Thread Group'

Right click the test plan and add a thread group.

image

The default thread group only have 1 thread. Change it to 20,000.

image

2.2 Add Loop

we need a thread send tcp requst, recieve the response, then wait for a few seconds and send again. So we need a loop. In this loop we send tcp request and sleep.  Right click the thread group, add loop controller.

image

For this demo, let's choose loop forever.

image

2.3 Add Sampler

Now we have a big goup of thread ready to do something. Sampler defines what the real task is.  Right click on the loop controller, add a tcp sampler.

image

The tcp sampler needs some configuration. 

image

Besides the ip and port of the server, there are 3 places need to config. First re-use connection for 'long connection'. Secondly set String 'send message\n'  as the payload sent to server. Finally, set the EOL byte, jmeter default is 0x00, since we send string message, '\n' is the delimer for every message.

2.4 Add timer

Add a wait after each tcp request. Right click TCP sampler, add a constant timer.

image

Change the delay to 5000 ms.

image

Now the test logic is done, but we need to add listerner to check the test result.

2.5 Add listeners

We add 2 listeners by right click on the test plan.  One is 'Aggregate Report' to see the statistic result of test. The other listener dispaly response time in line chart. (It's a jmeter plugin from the plugin standard set, see http://jmeter-plugins.org/) . The test plan is ready to run, now it looks like below. 

image

3. Run the test locally

Select 'Run' ->'Start', let's run the test locally.  We can see the test result from listeners. The 'Aggregate Report' listener looks like below.

image

The 'Response Times Over Time' listener has the response time as a line chart.

image

So far so good. The sever seems can handle client from 1 host very easily. Next we are going to add more host to generate traffice. This's so-called distributed testing.

4. Run the test remotely

4.1 Prerequisition

  • Every slave hosts has same version jmeter install. (ver 2.13 in this demo)
  • all host in the same subnet to reduce the network delay.

4.2 Change properties file of 'master' jmeter

Modify ${JMeter_Install_Dir}/bin/jmeter.properties file, add all slaves' ip

image

Also in this file, change jmeter mode to 'StrippedAsynch' to reduce master's workload. This's necessary when play with large concurrency performance test.

image

Remember only change this on the master's host.  Now if start the jmeter on master host, you can see the slaves from menu.

image

Now the master, or we can say jmeter client,  is ready. 

4.3 Start JMeter engine on every slave host

Run 'jmeter-server' command on every slave hosts.

image

4.4 Run remotely

On master, click  run remote all button.

image

Very soon you will find the active thread goes up to 100k (20k x 5 slaves)

image

Now the test result looks like below.

image

image

Check  the server's workload, cpu usage is still quite low. On our 2CPU server ( Intel Xeon E5-27750 2.0G 20M Cache, total 32 thread cores), only about 2 cores are used. 

image

Although the echo server has no real logic, the nice performance of Netty framework is impressive.

5. More

Here are the source of our Netty 4 echo server.  It has 2 classes. First the ServerHandler.java

package com.shengwang.demo;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class ServerHandler extends ChannelInboundHandlerAdapter {

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    ctx.writeAndFlush(msg);  // loop message back
  }
}

Then the main class.

package com.shengwang.demo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;

import java.io.IOException;

public class NettyServer {

  public static void main(String[] args) throws IOException, InterruptedException {
    NioEventLoopGroup boosGroup = new NioEventLoopGroup();
    NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(boosGroup, workerGroup);
    bootstrap.channel(NioServerSocketChannel.class);
    
    // ===========================================================
    // 1. define a separate thread pool to execute handlers with
    // slow business logic. e.g database operation
    // ===========================================================
    final EventExecutorGroup group = new DefaultEventExecutorGroup(1500); //thread pool of 500
    
    bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
      @Override
      protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new StringEncoder()); 
        pipeline.addLast(new StringDecoder()); 
        
        //===========================================================
        // 2. run handler with slow business logic 
        //    in separate thread from I/O thread
        //===========================================================
        pipeline.addLast(group,"serverHandler",new ServerHandler()); 
      }
    });
    
    bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
    bootstrap.bind(19000).sync();
  }
}

This echo server depends on netty 4.0.34.

    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.0.34.Final</version>
    </dependency>

Thursday, January 29, 2015

Using Netty Component in Apache Camel - Hello World Example

This tutorial will demo how to use Netty Component in Apache Camel. By using netty component with camel in the example, this article is going to achieve these goals:

  • Create a TCP Socket server as camel endpoint with netty component, waiting for TCP client.
  • Utilize camel to route the received TCP message from Netty endpoint to other process logic
  • Send the processed result back to the TCP client.

In this example, every time server get a string, it will reply with a prefix "Hello" to form a greeting. If "Tom" is received from client, "Hello Tom" will be send back to the client.

This example also shows there features:

  • Use Camel and Netty component with Spring framework.
  • Use POJO class for business logic, which does not depend on any Camel or Netty componenent APIs.

1. What you need

  • JDK 1.7+
  • Maven 3.2.1

The dependencies will be managed by Maven.

2. Configure the maven pom.xml

The key artifacts are camel-core, camel-spring, camel-netty. The camel-spring artifact is needed for using Spring framework. The camel-netty artifact is needed for using netty component in camel.

<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.shengwang.example</groupId>
<artifactId>spring-camel-netty3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-camel-netty</name>
<url>http://maven.apache.org</url>

<properties>
<!-- Generic properties -->
<java.version>1.7</java.version>

<!-- Camel -->
<camel.version>2.14.1</camel.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-netty</artifactId>
<version>${camel.version}</version>
</dependency>
</dependencies>


<build>
<plugins>
<!-- Use JDK 1.7 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>

<!-- Allows the routes to be run via 'mvn camel:run' -->
<plugin>
<groupId>org.apache.camel</groupId>
<artifactId>camel-maven-plugin</artifactId>
<version>${camel.version}</version>
</plugin>

</plugins>
</build>
</project>

In the POM file, Jave version and Camel version are defined in properties, followed by 3 dependencies. Then there are 2 plugins, the first one is to specify the Java version for building the project, the second plugin is needed to allow the camel route to be run without explicitly defined main function. It's very handy for demo and test.


3. Define a Java class to process the received message


This class is the process logic in this demo. This class is a POJO which means the business code is completely decoupled with camel or camel-netty component.

package com.shengwang.example.server;

import org.springframework.stereotype.Service;

@Service
public class EchoService {
public String sayHello(String guestName) {
System.out.println("Input guestName : "+ guestName);
return "Hello " + guestName;
}
}

The EchoService.java is very short and simple. Annotation @Service means it's will be registered in Spring context as a bean. How to use this bean will be explained later.


4. Define Camel route


Now Let's define another Java class for Camel route. This class extends RouteBuilder from Camel package and override the configure() method. In configure(), route is defined.

package com.shengwang.example.server;

import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.spring.Main;

public class ServerRoute extends RouteBuilder {
public static void main(String[] args) throws Exception {
new Main().run(args);
}

@Override
public void configure() throws Exception {
from("netty:tcp://localhost:7000?sync=true&allowDefaultCodec=false&encoder=#stringEncoder&decoder=#stringDecoder")
.to("bean:echoService");
}
}

There are two methods in the ServerRoute class. The first one is a main, so we can run the route directly with the helper class Main from camel-spring package.


The second method has the route defined. It means somehow like "from to ". In this tutorial we get message from netty endpoint and send to a Spring bean which defined in previous section. The is how the EchoService class is used in this demo. The interesting part is the long from URI:"netty:tcp://localhost:7000?sync=true&allowDefaultCodec=false&encoder=#stringEncoder&decoder=#stringDecoder". It can be break down bit by bit.


"netty:tcp://localhost:7000" means netty component is needed to setup a tcp server on localhost port 7000.


"sync=true" means exchange pattern is InOut, the server will send back the response to client.


"allowDefaultCodec=false" means we don't use default encoder and decoder for netty component. Because in reality, the message come in netty endpoint is most unlikely formed in that message format, so it need to be set to false against its default value true.


"encoder=#stringEncoder&decoder=#stringDecoder" means message comes into netty will be decoded before transferring to the business logic EchoService, and response from EchoService will be encoded before sending back to the client. In this tutorial, server receive and send back Java string, so the StringEncoder and StringDecoder from camel-netty package will be used. The hash tag "#" refer to beans in Spring context which will be defined in Spring configuration file later.


5. Spring configuration


The Spring configuration file app-context.xml is located in directory src/main/resources/META-INF/spring.

<?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:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:camel="http://camel.apache.org/schema/spring"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd">

<context:component-scan base-package="com.shengwang.example" />

<!-- ================================== -->
<!-- Define camel context -->
<!-- ================================== -->
<camel:camelContext id="camel">
<!-- Location for route scan -->
<camel:package>com.shengwang.example.server</camel:package>
</camel:camelContext>

<!-- ================================== -->
<!-- Define netty codecs -->
<!-- ================================== -->
<!-- ChannelBuffer/ByteBuf to String -->
<bean id="stringDecoder" class="org.jboss.netty.handler.codec.string.StringDecoder"/>

<!-- String to ChannelBuffer/ByteBuf -->
<bean id="stringEncoder" class="org.jboss.netty.handler.codec.string.StringEncoder"/>

</beans>

<context:component-scan> scan for normal Spring beans of a package. Here is the EchoServer. Then,<camel:camelContext>, Camel context is defined so does the location of the route. Finally, encoder and decoder beans are defined.


Now all 4 files of the project have been defined. The directory structure looks like this.


Snap_2015.01.27 13.29.23_001


6. Run the server


There are 2 options to run this demo. The first one is to run the main in ServerRouter.java class. The Second is run via command line "mvn camel:run".  The latter way, which needs a maven plugin list at the bottom of the pom file,  is very convenient for test and development.


7. Test with a TCP client


Use telnet command as test client, connect to port 7000 with command "telnet localhost 7000". Anytime a key is pressed, a TCP message will send out to the demo server  and a reply will come back immediately.


Appendix


The netty component camel-netty is based on netty 3.x version. If a netty component based on netty 4.x is preferred, use camel-netty4 component instead.

Powered by Blogger.

About The Author

My Photo
Has been a senior software developer, project manager for 10+ years. Dedicate himself to Alcatel-Lucent and China Telecom for delivering software solutions.

Pages

Unordered List