Why this blog post?
Basic steps in a request/response system.
In a typical request/response system eg: servers, messages undergo the following steps,
1. Input read/listening
2. Input Decode
3. Perform business Logic
4. output encode
5. output write/sending
The steps can overlap depending on the implementation details.
Blocking I/O
In a blocking I/O scenario, as the name implies, the processing thread is blocked on the message processing until the output is written back to the wire. In a typical scenario, each client connection get assigned to a separate thread. Since the threads are dependent on I/O performance, no matter how efficient you process the message, the system throughput is dependent on the I/O behaviour. If the network latency is very high, the system won’t scale with regard to number of requests it can serve even with a lower concurrency level.
Sample Code for Blocking I/O
Non Blocking I/O
In a non blocking I/O scenario, the partial read of the input is possible and the acceptor triggers an event when the input is available. Because of the eventing model, now the processing thread doesn’t have to blocking wait on the input.
Eg: A typical client worker thread blocks on I/O like below,
while (in.readLine()) != null)
Since input/output readiness is triggered by events, the worker threads can go back to the worker thread pool and handle another request/task which is in a ready state. Reactor acts as the event dispatcher. All the other components are handlers that registers themselves with the reactor for interested event. For an example, Acceptor registers itself with the reactor for the event, ‘Operation_Accept’.
Some core semantics
Scaling
The above architecture diagram only depicts the one thread version of reactor pattern. It is possible to scale the above system by means of multiple reactors, thread pools for worker threads, etc. The configuration highly dependent on the target hardware platform.
Libraries support for NIO
Even though one can write a complete service platform using the basic NIO constructs found in Java, there are open-source libraries that abstract out some of the underlying complexities. For an example Apache Http-core project provides abstraction layer for HTTp based NIO usage. Apache synapse make use of Http-core for NIO transport.
Main resource
Scalable IO in Java (presentation slides) - by Doug Lea, State University of New York at Oswego
Recently I was working with a team to improve the mediation performance of Apache Synapse ESB. Java NIO was new to me when i took up the challenge. I spent few days learning I/O concepts and NIO particularly. Soon after my background reading period, I was pulled out to optimize Carbon kernel. Tough luck!. Then again I learnt few things and thought of compiling a blog post based on the initial research.
Basic steps in a request/response system.
In a typical request/response system eg: servers, messages undergo the following steps,
2. Input Decode
3. Perform business Logic
4. output encode
5. output write/sending
The steps can overlap depending on the implementation details.
Blocking I/O
In a blocking I/O scenario, as the name implies, the processing thread is blocked on the message processing until the output is written back to the wire. In a typical scenario, each client connection get assigned to a separate thread. Since the threads are dependent on I/O performance, no matter how efficient you process the message, the system throughput is dependent on the I/O behaviour. If the network latency is very high, the system won’t scale with regard to number of requests it can serve even with a lower concurrency level.
Sample Code for Blocking I/O
ServerSocket serverSocket = null; serverSocket = new ServerSocket(4444); Socket clientSocket = null; clientSocket = serverSocket.accept(); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); String inputLine, outputLine; CustomProtocol customProtocol = new CustomProtocol(); while ((inputLine = in.readLine()) != null) { outputLine = customProtocol.processInput(inputLine); out.println(outputLine);
Non Blocking I/O
In a non blocking I/O scenario, the partial read of the input is possible and the acceptor triggers an event when the input is available. Because of the eventing model, now the processing thread doesn’t have to blocking wait on the input.
Eg: A typical client worker thread blocks on I/O like below,
while (in.readLine()) != null)
Since input/output readiness is triggered by events, the worker threads can go back to the worker thread pool and handle another request/task which is in a ready state. Reactor acts as the event dispatcher. All the other components are handlers that registers themselves with the reactor for interested event. For an example, Acceptor registers itself with the reactor for the event, ‘Operation_Accept’.
Some core semantics
Selectors are responsible for querying available events and keeping track of calling objects (Listeners). For an example non blocking socket channel registers itself with the selector for "OP_ACCEPT" event.
If a connection occurs (that is ACCEPT) the selector will pick it up and appropriate listener will get called.
selector = Selector.open(); serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(port)); serverSocketChannel.configureBlocking(false); SelectionKey selectionKey = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); selectionKey.attach(new Acceptor(serverSocketChannel, selector));
During an event trig, the selector will return all the relevant selection keys, and we can retrieve the attached listener for the key and execute.
while (!Thread.interrupted()) { selector.select(); Set selected = selector.selectedKeys(); Iterator it = selected.iterator(); while (it.hasNext()) { SelectionKey selectionKey = (SelectionKey)it.next(); // Our attached object implements runnable Runnable runnable = (Runnable)selectionKey.attachment(); runnable.run();
Scaling
The above architecture diagram only depicts the one thread version of reactor pattern. It is possible to scale the above system by means of multiple reactors, thread pools for worker threads, etc. The configuration highly dependent on the target hardware platform.
Libraries support for NIO
Even though one can write a complete service platform using the basic NIO constructs found in Java, there are open-source libraries that abstract out some of the underlying complexities. For an example Apache Http-core project provides abstraction layer for HTTp based NIO usage. Apache synapse make use of Http-core for NIO transport.
Main resource
Scalable IO in Java (presentation slides) - by Doug Lea, State University of New York at Oswego