Netty optimization and Protocol help.

bantai superman at bantai.com
Fri Aug 14 18:17:13 EDT 2009


Hi,

I am currently working on a project where I need to handle many thousand
unique requests per second and have therefore chosen to create a server
based on a thin framework like Grizzly or Netty. I have currently tested
Netty HTTP successfully but since the communication is highly defined I was
thinking of creating a simpler protocol than relying on HTTP since I'm not
using much of the HTTP protocol. Defaulting to HTTP was the simplest start.

All the requests are final so no session state etc is necessary.

The protocol I have in mind will send text messages that are in the form of
JSON objects and only a few bytes in size.

I've tried creating a server from looking at the examples provided by Netty
and used the LocalTime example as my initial source of inspiration.

Some of the questions I have are:

1.) With the current implementation, I'm getting worse performance than I
did using a Grizzly http server. I must have missed something vital since
I've seen tests where Netty outperforms Grizzly. So I'm wondering if I am
not reusing the connection/channel correctly or if I can pool something?

2.) In my test client I am currently creating a JsonClient per thread. Is
this correct or should I rather create one JsonClient in my TestRunner which
is shared among the TestThreads?

3.) From another thread I saw the recommendations to use the properties
keepAlive, tcpNoDelay and reuseAddress. I've tried playing around with them
but not seen much of a change so Im not sure I'm using them correctly or
whether I would benefit from using other properties?

4.) Tips in general. If I would get better performance rewriting my code
following other guidelines please let me know as I've only started playing
with Netty.

5.) How should i reformat my Server code to implement the Server interface
I'm currently using with Grizzly:

public interface Server {
	public void start();
	
	public void stop();
}

6.) Am I wrong in thinking that I should be able to get better performance
using a dedicated protocol for this task instead of relying on HTTP?

Below is the source code I put together for my test. It is pretty straight
forward, a client sends a generated string in a json object which the server
reverses and sends back. The client then makes sure that the reversed string
is correct (i added this part just to make sure so i was actually receiving
the correct response and not one intended for another client). The actual
decision to reverse the string is just to simulate a little bit of
computation on the server side.

I've been using Netty 3.1.0

I'm using this as a learning experience so I hope you bare me with what may
be some horrible Netty code ;)

Regards

------------------------------------------------

public class JsonServer {

	public static void main(String[] args) throws Exception {
		// Configure the server.
		ServerBootstrap bootstrap = new ServerBootstrap(new
NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
				Executors.newCachedThreadPool()));
		bootstrap.setOption("child.tcpNoDelay", true);
		bootstrap.setOption("child.keepAlive", true);
		bootstrap.setOption("child.reuseAddress", true);

		// Set up the event pipeline factory.
		bootstrap.setPipelineFactory(new JsonServerPipelineFactory());

		// Bind and start to accept incoming connections.
		bootstrap.bind(new InetSocketAddress("localhost", 8080));
	}
}

------------------------------------------------

public class JsonServerPipelineFactory implements ChannelPipelineFactory {

	public ChannelPipeline getPipeline() throws Exception {
		ChannelPipeline p = pipeline();

		p.addLast("decoder", new StringDecoder());
		p.addLast("encoder", new StringEncoder());

		p.addLast("handler", new JsonServerHandler());

		return p;
	}
}

------------------------------------------------

@ChannelPipelineCoverage("all")
public class JsonServerHandler extends SimpleChannelUpstreamHandler {

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
		try {
			String requestString = (String) e.getMessage();

			JSONObject request = new JSONObject(requestString);
			String stringToReverse = request.getString("reverse");

			JSONObject response = new JSONObject();
			response.put("result", new
StringBuilder(stringToReverse).reverse().toString());

			e.getChannel().write(response.toString());
		} catch (JSONException ex) {
			ex.printStackTrace();
		}
	}
}

------------------------------------------------

public class JsonClient {
	private final JsonClientHandler handler;

	public JsonClient(String hostname, int port) {
		// Set up.
		ClientBootstrap bootstrap = new ClientBootstrap(new
NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
				Executors.newCachedThreadPool()));
		bootstrap.setOption("tcpNoDelay", true);
		bootstrap.setOption("keepAlive", true);
		bootstrap.setOption("reuseAddress", true);

		// Configure the event pipeline factory.
		bootstrap.setPipelineFactory(new JsonClientPipelineFactory());

		// Make a new connection.
		ChannelFuture connectFuture = bootstrap.connect(new
InetSocketAddress(hostname, port));

		// Wait until the connection is made successfully.
		Channel channel = connectFuture.awaitUninterruptibly().getChannel();

		// Get the handler instance to initiate the request.
		handler = channel.getPipeline().get(JsonClientHandler.class);
	}

	public JSONObject sendReverseRequest(JSONObject request) {
		return handler.sendReverseRequest(request);
	}
}

------------------------------------------------

public class JsonClientPipelineFactory implements ChannelPipelineFactory {

	public ChannelPipeline getPipeline() throws Exception {
		ChannelPipeline p = pipeline();

		p.addLast("decoder", new StringDecoder());
		p.addLast("encoder", new StringEncoder());

		p.addLast("handler", new JsonClientHandler());

		return p;
	}
}

------------------------------------------------

@ChannelPipelineCoverage("one")
public class JsonClientHandler extends SimpleChannelUpstreamHandler {

	// Stateful properties
	private volatile Channel channel;
	private final BlockingQueue<JSONObject> answer = new
LinkedBlockingQueue<JSONObject>();

	public JSONObject sendReverseRequest(JSONObject request) {
		channel.write(request.toString());

		JSONObject response = null;
		try {
			response = answer.take();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return response;
	}

	@Override
	public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
		channel = e.getChannel();
		super.channelOpen(ctx, e);
	}

	@Override
	public void messageReceived(ChannelHandlerContext ctx, final MessageEvent
e) {
		String responseString = (String) e.getMessage();
		JSONObject response = null;
		try {
			response = new JSONObject(responseString);
		} catch (JSONException e1) {
			e1.printStackTrace();
		}

		boolean offered = answer.offer(response);
		assert offered;
	}

}

------------------------------------------------

public class TestRunner {
	private final int threads = 3;
	private final int iterations = 10000;

	private final List<TestThread> testThreads = new ArrayList<TestThread>();

	public TestRunner() {
		for (int i = 0; i < threads; i++) {
			testThreads.add(new TestThread());
		}

		for (TestThread tt : testThreads) {
			tt.start();
		}
	}

	class TestThread extends Thread {
		private final JsonClient client;

		public TestThread() {
			client = new JsonClient("localhost", 8080);
		}

		@Override
		public void run() {
			long l = System.currentTimeMillis();

			for (int i = 0; i < iterations; i++) {
				String test = "reverse '" + System.currentTimeMillis() + "' and me '" +
i + "'!";

				try {
					JSONObject request = new JSONObject();
					request.put("reverse", test);

					JSONObject response = client.sendReverseRequest(request);
					String reversedTest = response.getString("result");

					if (!reversedTest.equals(new StringBuilder(test).reverse().toString()))
{
						throw new IllegalStateException("Did NOT receive the reversed
string!");
					}
				} catch (JSONException e) {
					e.printStackTrace();
				}
			}

			System.err.println(iterations + " iterations completed in " +
(System.currentTimeMillis() - l) + "ms");
		}
	}

	public static void main(String[] args) {
		new TestRunner();
	}
}

------------------------------------------------


-- 
View this message in context: http://n2.nabble.com/Netty-optimization-and-Protocol-help.-tp3447755p3447755.html
Sent from the Netty User Group mailing list archive at Nabble.com.


More information about the netty-users mailing list