美文网首页
Java服务器编程模型

Java服务器编程模型

作者: 优锐课小U | 来源:发表于2019-12-16 15:31 被阅读0次

    优锐课小U带大家了解有关Java中服务器各种编程模型的更多信息!

    通常,我们的应用程序不需要并行处理成千上万的用户,也不需要在一秒钟内处理成千上万的消息。我们只需要应付数十或数百个并发连接的用户,就可以在内部应用程序或某些微服务应用程序中承受如此大的负担。

    在这种情况下,我们可以使用一些在线程模型/使用的内存方面没有得到优化的高级框架/库,而且,我们仍然可以承受一些合理的资源和相当快的交付时间。

    但是,有时会遇到这样的情况,其中我们的系统部分需要比其他应用程序更好地扩展。用传统的方式或框架编写系统的这一部分可能会导致大量的资源消耗,并且需要启动同一服务的许多实例来处理负载。导致能够处理成千上万个连接的能力的算法和方法也称为C10K问题

    在本文中,小U将主要关注可以通过TCP Connections / Traffic进行的优化,以优化你的(微)服务实例以浪费尽可能少的资源,更深入地研究OS如何与TCP和 套接字,以及最后但并非最不重要的一点,是如何深入了解所有这些内容。让我们开始吧。

    I/ O编程策略

    让我们描述一下我们目前拥有什么样的I/ O编程模型,以及在设计应用程序时需要选择哪些选项。首先,没有好的或坏的方法,只有更适合我们当前用例的方法。选择错误的方法可能在将来带来非常不便的后果。这可能会浪费资源,甚至可能从头开始重写应用程序。

    通过阻塞处理来阻塞I / O

    Thread-per-Connection服务器

    这种方法背后的思想是,没有任何专用/空闲线程,则不接受套接字连接(我们将在后面显示其含义)。在这种情况下,阻塞意味着特定线程绑定到该连接,并始终在从该连接读取或写入该连接时阻塞。

    public static void main(String[]args)throwsIOException{

        try(ServerSocketserverSocket=newServerSocket(5050)) {

            while (true) {

                SocketclientSocket=serverSocket.accept();

                vardis=newDataInputStream(clientSocket.getInputStream());

                vardos=newDataOutputStream(clientSocket.getOutputStream());

                newThread(newClientHandler(dis,dos)).start();

            }

        }

    }

    套接字服务器的最简单版本,它从端口5050开始,以阻塞方式从InputStream读取并写入OutputStream。当我们需要通过单个连接传输少量对象,然后在需要时关闭它时,启动一个新对象很有用。

    1、即使没有任何高级库也可以实现。

    2、使用阻塞流进行读取/写入(等待阻塞InputStream读取操作,该操作将使用当时TCP接收缓冲区中可用的字节填充提供的字节数组,并返回字节数或-1-流的末尾)并消耗字节,直到我们有足够的数据来构造一个请求。

    3、当我们开始以无限方式为传入连接创建线程时,可能会出现一个大问题和效率低下。我们将为非常昂贵的线程创建和内存影响而付出代价,这是将一个Java线程映射到一个内核线程时产生的。

    4、它不适合“实际”生产,除非我们确实需要一个内存不足的应用程序,并且不想加载很多属于某些框架的类。

    具有阻塞处理功能的非阻塞I / O

    Thread-Pool Based服务器

    这是大多数知名企业HTTP Server所属的类别。通常,此模型使用多个线程池来使处理在多CPU环境中更有效,并且更适合企业应用程序。有多种方法可以配置线程池,但是基本概念在所有HTTP服务器中都完全相同。请参阅HTTP Grizzly I / O策略,以获取通常可以基于基于线程池的非阻塞服务器配置的所有可能策略。

    1、第一个线程池,用于接受新连接。如果一个线程能够管理传入连接的速度,则它甚至可以是单线程池。 通常有两个待办事项可以填写,而下一个传入连接将被拒绝。检查是否可以正确使用持久连接。

    2、第二个线程池,用于通过非阻塞方式(选择器线程或IO线程)从套接字读取/写入套接字。每个选择器线程都处理多个客户端(通道)。

    3、第三个线程池,用于分隔请求处理的非阻塞和阻塞部分(通常称为辅助线程)。Selector线程无法通过某些阻塞操作来阻塞,因为所有其他通道将无法进行任何处理(一组通道只有一个线程,该线程将被阻塞)。

    4、非阻塞读取/写入是使用缓冲区实现的,选择器线程会从套接字读取新字节并写入专用缓冲区(缓冲的缓冲区),只要不满足处理请求的特定线程(因为它们没有足够的数据量即可) 构造例如HTTP请求)。

    5、我们需要澄清术语“非阻塞”NON-BLOCKING:

    (1)如果我们在套接字服务器的上下文中进行讨论,则非阻塞意味着线程不与打开的连接绑定,并且不等待传入数据(甚至在TCP发送缓冲区已满时甚至写入数据),只需尝试读取和 如果没有字节,则不将任何字节添加到缓冲区中以进行进一步处理(构造请求),并且给定的选择器线程继续从另一个打开的连接中进行读取。

    (2)但是,就处理请求而言,代码在大多数情况下是BLOCKING,这意味着我们执行了一些代码来阻塞当前线程,并且该线程等待I/ O绑定处理的完成(数据库查询,HTTP调用,从磁盘读取...等)或某些持久的CPU绑定处理(计算散列/阶乘,加密挖掘等)。如果执行完成,则线程将被唤醒并继续执行某些业务逻辑。

    6、业务逻辑的块化性质是工作池如此之大的主要原因,我们只需要投入大量线程来提高吞吐量即可。否则,在负载较高的情况下(例如,更多的HTTP请求),我们最终可能会导致所有线程处于阻塞状态,并且没有可用的线程来处理请求(没有处于Runnable状态的线程可以在CPU上执行)。

    优点

    1、即使请求的数量很高并且许多我们的工作线程在某些阻止操作中被阻止,我们也能够接受新的连接,即使我们可能无法立即处理它们的请求并且数据必须在TCP中等待 接收缓冲区。

    2、底层的许多框架/库(Spring Controllers, Jersey, ..)和HTTP Server(Jetty, Tomcat, Grizzly

    ..)都使用此编程模型,因为编写业务代码非常容易,如果允许,则让线程阻塞 真的需要。

    缺点

    1、并行性通常不是由CPU的数量决定,而是由阻塞操作的性质和工作线程的数量来限制。通常,这意味着如果阻塞操作(I/O)与进一步执行(在请求过程中)的时间比例太高,那么我们可以得出以下结论:

    (1)在阻塞操作(数据库查询,..)上有很多阻塞线程,并且

    (2)很多请求正在等待工作线程被处理,

    (3)由于没有线程而无法充分利用的CPU可以继续执行

    2、更大的线程池导致上下文切换和CPU缓存使用效率低下

    如何设置线程池

    好的,我们有一个或多个线程池来处理阻塞的业务操作。但是,线程池的最佳大小是多少?我们会遇到两个问题:

    1、线程池太小,我们没有足够的线程来覆盖所有线程被阻塞的时间,比如说等待I/ O操作,而你的CPU没有得到有效利用。

    2、线程池太大,我们为许多实际上处于空闲状态的线程付出了代价(请参见下面,当我们运行大量线程时的价格)。

    我会参考Brian Goetz的一本很棒的书《 Java Concurrency in Practice》,该书说调整线程池的大小并不是一门确切的科学,它更多地是关于理解环境和任务性质的。

    1、你的环境有多少CPU和多少内存?

    2、任务主要执行计算,I/ O还是某种组合?

    3、它们是否需要稀缺资源(JDBC连接)?线程池和连接池将相互影响,并且当我们充分利用连接池时,增加线程池以获得更好的吞吐量可能没有任何意义。

    如果我们的程序包含I / O或其他阻塞操作,则你需要一个更大的池,因为不允许你的线程一直一直放置在CPU上。你需要估计等待时间的比率,以便使用某些探查器或基准测试来计算任务的时间,并观察生产工作负载不同阶段(高峰时间与非高峰时间)的CPU利用率。

    具有非阻塞处理的非阻塞I / O

    基于与CPU核心相同数量的线程的服务器

    如果我们能够以非阻塞方式管理大部分工作负载,则此策略是最有效的。这意味着使用非阻塞算法来实现对套接字的处理(接受连接,读取,写入),但是即使业务处理也不包含任何阻塞操作。

    此策略的典型代表是Netty

    Framework,因此让我们深入了解如何实现此框架的体系结构基础知识,以了解为什么它最适合解决C10K问题。如果你想详细了解其实际工作原理,那么我可以推荐以下资源:

    1、Netty in Action - 由Netty Framework Norman Mauer的作者撰写。这是了解如何使用具有各种协议的处理程序基于Netty实现客户端或服务器的宝贵资源。

    2、User Guide for 4.x - Netty中的示例应用程序,显示了所有基本原理。

    具有异步编程模型的I / O

    Netty是一个I/ O库和框架,可简化无阻塞IO编程,并为服务器和传入连接的生命周期中发生的事件提供异步编程模型。我们只需要使用lambda连接到回调,就可以免费获取所有内容。

    许多协议都可以使用,而无需依赖某些庞大的库

    开始使用纯JDK NIO构建应用程序非常令人沮丧,但是Netty包含使程序员处于较低水平的功能,并提供了使许多事情变得更加高效的可能性。Netty已经包含了大多数众所周知的协议,这意味着我们可以比使用很多样板的高级库(例如用于HTTP/ REST的Jersey /Spring MVC)更有效地使用它们。

    确定正确的非阻塞用例,以充分利用Netty的功能

    I / O处理,协议的实现以及所有其他处理程序都应该使用非阻塞操作来永不停止当前线程。我们总是可以使用其他线程池来阻止操作。但是,如果我们需要将每个请求的处理切换到专用线程池以进行阻塞操作,那么我们几乎就不会利用Netty的功能,因为我们很可能最终会遇到与带有阻塞处理的非阻塞IO相同的情况。 线程池只是在我们应用程序的不同部分中。

    在上面的图片中,我们可以看到Netty Architecture的主要组件

    1、EventLoopGroup —收集事件循环并提供要注册到事件循环之一的通道。

    2、EventLoop —处理给定事件循环的已注册通道的所有I / O操作。EventLoop仅在一个线程上运行。因此,一个EventLoopGroup的最佳事件循环数是CPU的数量(发生Page Fault时,某些框架使用CPU数量+ 1来具有其他线程)。

    3、Pipeline —保持处理程序的执行顺序(在发生某些输入或输出事件时被排序和执行的组件,包含实际的业务逻辑)。管道和处理程序在属于EventLoop的线程上执行,因此,处理程序中的阻塞操作将阻塞给定EventLoop上的所有其他处理/通道。


    今天的分享就到这啦!感谢阅读~

    相关文章

      网友评论

          本文标题:Java服务器编程模型

          本文链接:https://www.haomeiwen.com/subject/abhdnctx.html