• 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 connection
    SelectionKey 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

    The Acceptor is a single threaded active class. Because it is only responsible for handling the very short-running
    client connection request, it is often sufficient to implement the Acceptor using the blocking I/O model. 

    class Acceptor implements Runnable {
      ...
      void init() {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(true);
        serverChannel.socket().bind(new InetSocketAddress(serverPort));
      }
    
      public void run() {
        while (isRunning) {
          try {
            SocketChannel channel = serverChannel.accept(); 
    
            Connection con = new Connection(channel, appHandler);
            dispatcherPool.nextDispatcher().register(con);  
          } catch (...) {
            ...
          }
        }
      }
    }

    2.Dispatcher

    Because the scalability of a single Dispatcher is limited, often a small pool of Dispatchers will be used. One reason for this limitation 

    is the operating-system-specific implementation of the Selector.

    Most popular operating systems map a SocketChannel to a file handle in a one-to-one relationship. Depending on the concrete system, 

    the maximum number of file handles per Selector is limited in adifferent way.

    The Selector manages the registered channels internally by using key sets. This means that by registering a channel, an associated 

    SelectionKey will be created and be added to the Selector's registered key set. At the same time, the concurrent dispatcher thread 

    could call the Selector's select method, which also accesses the key set.

    Because the key sets are not thread-safe, an unsynchronized registration in the context of the Acceptor thread can lead to deadlocks

    and race conditions. This can be solved by implementing the selector guard object idiom, which allows suspending the dispatcher

    thread temporarily. 

    class Dispatcher implements Runnable {
      private Object guard = new Object();
      …
    
      void register(Connection con) {
        // retrieve the guard lock and wake up the dispatcher thread
        // to register the connection's channel
        synchronized (guard) {
          selector.wakeup();  
          con.getChannel().register(selector, SelectionKey.OP_READ, con);
        }
    
        // notify the application EventHandler about the new connection 
      }
    
    
      void announceWriteNeed(Connection con) {
        SelectionKey key = con.getChannel().keyFor(selector);
        synchronized (guard) {
          selector.wakeup();
          key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        }
      }
    
      public void run() {
        while (isRunning) {
          synchronized (guard) {
            // suspend the dispatcher thead if guard is locked 
          }
          int eventCount = selector.select();
    
          Iterator  it = selector.selectedKeys().iterator();
          while (it.hasNext()) {
            SelectionKey key = it.next(); 
            it.remove();
    
            // read event?
            if (key.isValid() && key.isReadable()) {
              Connection con = (Connection) key.attachment();
              disptacherEventHandler.onReadableEvent(con);
            }
    
            // write event?
          }
        }
      }
    }

    4.Dispatcher-Level EventHandler

    5.Application-Level EventHandler

     



  • 相关阅读:
    为什么不要用VSCODE来写Makefile
    JavaFX第三弹
    javaFX文件和文件夹选择器
    写了一个vsftpd的GUI
    在java中调用shell命令和执行shell脚本
    正交投影与斯密特正交化的好处
    Linux下安装软件
    C++中的仿函数
    C++中重载操作符[ ]
    使用斐波那契查找
  • 原文地址:https://www.cnblogs.com/yuyutianxia/p/3993694.html
Copyright © 2020-2023  润新知