关于多线程:了解异步Web处理

Understanding asynchronous web processing

我刚读完有关异步WebServlet处理的内容。 [本文]很不错。

但是,从根本上来说,我很困惑为什么此方法是"下一代Web处理"并且实际上根本没有使用。似乎我们在避免更好地配置我们的Web应用程序服务器(WAS)-nginx,apache,tomcat,IIS-而是将问题提交给Web开发人员。

在深入探讨推理之前,我想简要解释一下WAS如何接受并处理请求。

NETWORK <-> OS -> QUEUE <- WEB APPLICATION SERVER (WAS) <-> WEB APPLICATION (APP)

  • Web应用程序服务器(WAS)告诉操作系统(OS)它希望在特定端口上接收请求,例如HTTP的端口80。
  • 操作系统会在端口上打开一个侦听器(如果它是免费的),然后等待客户端连接。
  • 操作系统收到连接时,会将其添加到分配给WAS的队列中(如果有空间,否则客户端的连接将被拒绝)-队列的大小由WAS在请求端口时定义) 。
  • WAS监视连接队列,当连接可用时,接受连接进行处理-将其从队列中删除。
  • WAS将连接传递到Web应用程序进行处理-如果进行编程,它也可以处理流程本身。
  • WAS可以通过使用多个处理器(通常每个CPU内核一个)同时处理多个连接,每个处理器具有多个线程。

    现在,这使我进入了查询。如果WAS可以处理的请求数量取决于它处理队列的速度,而该速度低于分配给WAS的处理器/线程数,那么为什么要在APP内创建一个异步方法来卸载请求从WAS到不属于WAS的另一个线程,而不仅仅是增加WAS可用的线程数?

    如果您认为弹出的(不是这样)新的Web套接字,则当Web套接字建立与WAS的连接时,会为该连接分配一个线程,该线程保持打开状态,以便客户端和WAS可以保持持续的通信。此线程最终是WAS上的线程-表示它正在占用服务器资源-无论是属于WAS还是独立于WAS(取决于APP设计)。

    但是,为什么不创建一个不属于WAS的独立线程,为什么不增加WAS可用的线程数呢?最终,您可以拥有的线程数取决于服务器上可用的资源-内存,CPU。还是通过将Connection卸载到新线程,您根本不需要考虑要为WAS分配多少个线程(这很危险,因为现在您可以在没有适当监视的情况下耗尽服务器资源)。似乎问题正在传递给APP-因此是开发人员-而不是由WAS管理。

    还是我只是误解了Web应用服务器的工作方式?

    将其放入一个简单的Web Application Server示例中。以下将直接将传入的Connection卸载到线程。我并没有限制可以创建的线程数,但是我仅限于Macbook上允许的开放连接数。我还注意到,如果积压(ServerSocket中的第二个数字,当前为50)设置得太小,我将开始在客户端接收Broken PipesConnection Resets

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Date;

    public class Server {

        public static void main(String[] args) throws IOException {
            try (ServerSocket listener = new ServerSocket(9090, 50)) {
                while (true) {
                    new Run(listener.accept()).start();
                }
            }
        }

        static class Run extends Thread {
            private Socket socket;

            Run(Socket socket) {
                this.socket = socket;
            }

            @Override
            public void run() {
                try {
                    System.out.println("Processing Thread" + getName());
                    PrintWriter out = new PrintWriter(this.socket.getOutputStream(), true);
                    out.println(new Date().toString());
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        this.socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    现在使用异步,您只是将线程传递给另一个线程。您仍然受到系统资源的限制-允许的打开文件数,连接数,内存,CPU等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Date;

    public class Server {

        public static void main(String[] args) throws IOException {
            try (ServerSocket listener = new ServerSocket(9090, 100)) {
                while (true) {
                    new Synchronous(listener.accept()).start();
                }
            }
        }

        // assumed Synchronous but really it's a Thread from the WAS
        // so is already asynchronous when it enters this Class
        static class Synchronous extends Thread {
            private Socket socket;

            Synchronous(Socket socket) {
                this.socket = socket;
            }

            @Override
            public void run() {
                System.out.println("Passing Socket to Asynchronous" + getName());
                new Asynchronous(this.socket).start();
            }
        }

        static class Asynchronous extends Thread {
            private Socket socket;

            Asynchronous(Socket socket) {
                this.socket = socket;
            }

            @Override
            public void run() {
                try {
                    System.out.println("Processing Thread" + getName());
                    PrintWriter out = new PrintWriter(this.socket.getOutputStream(), true);
                    out.println(new Date().toString());
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        this.socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    在关于Netflix的tuning-tomcat-for-a-highthroughput \\的博客中,看起来Tomcat与我的第一个代码示例相同。因此,不需要应用程序中的异步处理。

    缺省情况下,

    Tomcat具有两个影响负载的属性,acceptCount定义最大队列大小(默认值:100),而maxThreads定义最大同时请求处理线程数(默认值:200)。也有maxConnections,但是我不确定定义了maxThreads的意义。您可以在Tomcat Config

    上阅读有关它们的信息。


    晚了,但也许总比没有好。 :)

    对于"为什么要使用yy servlet?",我没有很好的答案,但我认为有关信息的另一点会对您有所帮助。

    您要为WAS描述的是Tomcat在其BIO连接器中所做的事情。基本上,每个连接模型都是一个线程。这不仅限制了maxThreads设置,还限制了您可以服务的请求数量,还因为如果未发送Connection:Close,则工作线程可能会继续被束缚,等待连接上的其他请求。 (请参阅https://www.javaworld.com/article/2077995/java-concurrency/java-concurrency-asynchronous-processing-support-in-servlet-3-0.html和Tomcat的BIO有什么区别?连接器和NIO连接器?)

    切换到NIO连接器后,tomcat可以维持数千个连接,同时仍然仅保留一小部分工作线程。