首页 > 代码库 > Architecture of a Highly Scalable NIO-Based Server

Architecture of a Highly Scalable NIO-Based Server

一。 thread-per-connection

The thread-per-connection approach uses an exclusive worker thread for each connection. Within the handling loop, a worker thread

waits for new incoming data, processes the request, returns the response data, and calls the blocking socket‘s read method

public class Server {  private ExecutorService executors = Executors.newFixedThreadPool(10);  private boolean isRunning = true;    public static void main(String... args) throws ... {    new Server().launch(Integer.parseInt(args[0]));  }   public void launch(int port) throws ... {    ServerSocket sso = new ServerSocket(port);    while (isRunning) {      Socket s = sso.accept();      executors.execute(new Worker(s));    }  }  private class Worker implements Runnable {    private LineNumberReader in = null;    Worker(Socket s) throws ... {      in = new LineNumberReader(new InputStreamReader(...));      out = ...    }    public void run() {      while (isRunning) {        try {          // blocking read of a request (line)           String request = in.readLine();          // processing the request          String response = ...          // return the response          out.write(resonse);          out.flush();        } catch (Exception e ) {           ...         }      }      in.close();      ...    }   }}

Because each connection has an associated thread waiting on the server side, very good response times can be achieved. However,

higher loads require a higher number of running, concurrent threads, which limits scalability. In particular, long-living connections

like persistent HTTP connections lead to a lot of concurrent worker threads, which tend to waste their time waiting concurrent

threads can waste a great deal of stack space. Note, for example, that the default Java thread stack size for Solaris is 512 KB.

二。thread-on-event

If a readiness event occurs, an event handler will be notified to perform the appropriate processing within dedicated worker threads.


To participate in the event architecture, the connection‘s Channel has to be registered on a Selector. This will be done by calling 

the register method. Although this method is part of the SocketChannel, the channel will be registered on the Selector, not the

other way around.

SocketChannel channel = serverChannel.accept();channel.configureBlocking(false);// register the connectionSelectionKey sk = channel.register(selector, SelectionKey.OP_READ);

To detect new events, the Selector provides the capability to ask the registered channels for their readiness events. By calling the select  

method, the Selector collects the readiness events of the registered channels. This method call blocks until at least one event has been

occurred. In this case, the method returns the number of connections that have become ready for I/O operations since the last select call.

The selected connections can be retrieved by calling the Selector‘s selectedKey method. This method returns a set of SelectionKey objects,

which holds the IO event status and the reference of the connection‘s Channel.

 

Selector is held by the Dispatcher. This is a single-threaded active class that surrounds the Selector. The Dispatcher is responsible to

retrieve the events and to dispatch the handling of the consumed events to the EventHandler.

Within the dispatch loop, the Dispatcher calls the Selector‘s select method to wait for new events. If at least one event has been occurred,

the method call returns and the associated channel for each event can be acquired by calling the selectedKeys method.

while (isRunning) {  // blocking call, to wait for new readiness events  int eventCount = selector.select();    // get the events
  Iterator it = selector.selectedKeys().iterator();
  while (it.hasNext()) {    SelectionKey key = it.next();    it.remove();        // readable event?    if (key.isValid() && key.isReadable()) {      eventHandler.onReadableEvent(key.channel());    }       // writable event?     if (key.isValid() && key.isWritable()) {      key.interestOps(SelectionKey.OP_READ); // reset to read only      eventHandler.onWriteableEvent(key.channel());    }    ...  }  ...}

Because worker threads are not forced to waste time by waiting for new requests to open a connection, the scalability and 

throughput of this approach is conceptually only limited by system resources like CPU or memory. That said, the response

times wouldn‘t be as good as for the thread-per-connection approach, because of the required thread switches and

synchronization. The challenge of the event-driven approach is therefore to minimize synchronizations and optimize thread

management, so that this overhead will be negligible.

 

三。构成

 

1.Acceptor



Architecture of a Highly Scalable NIO-Based Server