Detaillierte Erklärung des Reaktormodells

Kein Berg ist höher als der Mensch, kein Meer weiter als das Herz, der Mensch ist der Herr der Welt

Detaillierte Erklärung des Reaktormodells

Forschungshintergrund

    In der Tat, wenn wir Netty studieren, dürfen wir NIO nicht umgehen, und wir müssen auch dieses Reaktormodell studieren. Wenn wir dieses Reaktormodell und die NIO-Wissenspunkte nicht erforschen, dürfen wir die Essenz von Netty nicht erfassen. Warum?

  1. Da Netty den NIO-Code unten kapselt, wird angegeben, dass Netty es nicht versteht, selbst wenn Sie es beherrschen, wenn die drei Hauptkomponenten von NIO wie Kanal, Puffer und Selektor nicht klar sind, es ist an der API eben.
  2. Das Reactor-Modell ist einfach zu klassisch. Nettys Modell ist aus drei klassischen Reactor-Modellen weiterentwickelt, und nicht nur Netty hat dieses Modell, sondern bekannte Middleware wie Redis und Nginx greifen alle auf die Ideen dieses Modells zurück.

Reaktormodell

Hauptidee

    Der Kern des Reactor-Modells ist der Reactor plus der entsprechende Prozessor-Handler. Der Reactor läuft in einem separaten Thread und ist verantwortlich für die Überwachung und Verteilung von Ereignissen und die Übergabe der empfangenen Ereignisse an verschiedene Handler zur Verarbeitung. Handler sind die Handler, die I/ O. eigentlicher Ablauf der Veranstaltung

Basistyp

    Lassen Sie uns zunächst über das grundlegende traditionelle Client-Server-Modell sprechen.Hier ist BIO der ursprünglichste Vertreter, und das NIO-Modell leitet sich von der geringenEffizienz ab.

BIO-Modell

    Der klassische Typ ist das BIO-Modell. Wenn ein Client kommt, um eine Verbindung anzufordern, muss der Server einen Thread erstellen, um die Verbindungsanfrage zu verarbeiten. Dies ist für eine kleine Anzahl von Clients in Ordnung. Wenn eine große Anzahl von Clients Verbindungsanfragen stellen Daher werden die Thread-Ressourcen auf der Serverseite knapp und sowohl die Server- als auch die Clientseite werden in diesem Prozess blockiert, und der herkömmliche BIO-Modus hat immer noch das Problem einer geringen Synchronisierungseffizienz Die Serverseite wird dumm warten. Der Client sendet eine Anfrage. Wenn es keine Anfrage gibt, wird der Thread die ganze Zeit blockiert, was zu einer Verschwendung von Ressourcen führt.

Diagramm

image.png

Fallcode
public class BIOServer {
    public static void main(String[] args) {
        try {
            // 服务端监听端口8080
            ServerSocket serverSocket = new ServerSocket(8080);
            // 服务端接收客户端链接请求
            Socket socket = serverSocket.accept();
            new Thread(() -> {
                try {
                    byte[] bytes = new byte[1024];
                    // 将信息从输入流读取到创建的byte数组中
                    socket.getInputStream().read(bytes);
                    String message = new String(bytes, CharsetUtil.UTF_8);
                    System.out.println("客户端发送过来的信息是:" + message);
                    byte[] byteWrite = "Hello Client".getBytes(CharsetUtil.UTF_8);
                    // 返回信息给客户端
                    socket.getOutputStream().write(byteWrite);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

NIO-Modell

    上面的 BIO 模式就是效率低下的阻塞IO,而NIO 是基于事件驱动的IO模型,他这种方式就好很多了,他不会进行线程的阻塞,因为他是有一个专门负责事件轮询的selector选择器进行channel通道监听,如果有事件发生那么就进行相应的事件处理就可以了, 更多详情可以阅读我之前写的NIO 系列的文章

图解

image.png

案例代码
public class ChatServer {
    public static void main(String[] args) throws Exception {
        // 1. 创建选择器
        Selector selector = Selector.open();
        // 2. 创建服务端 channel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 3. 创建服务端的监听端口
        serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 9000));
        // 4.设置serversocketchannel 是非阻塞的
        serverSocketChannel.configureBlocking(false);
        // 5. 将serversocketchannel注册到selector选择器上面,并将事件设置成连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 监听就绪事件
        while (true) {
            System.out.println("等待......");
            // 休眠1秒  无论是否有读写事件发生 selector每隔1秒被唤醒
            int selected = selector.select(1000);
            if (selected > 0) { // 证明有事件已经准备就绪
                // 返回已经就绪的事件
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                if (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    // 获取socketChannel
                    if (key.isAcceptable()) { 
                        // 连接事件就绪,将其感兴趣的事件设置成已读事件
                        // 处理接入的新请求
                        handleAccept(selector, key);
                    }
                    if (key.isReadable()) { // 已读事件就绪
                        // 处理通道的读请求
                        handleRead(key);
                    }
                    iterator.remove();// 将处理完的数据进行了移除
                }
            }
        }

    }

    /**
     * 处理客户端读操作请求
     */
    private static void handleRead(SelectionKey key) {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        // 申请一个buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 将通道的数据读入到buffer中
        try {
            socketChannel.read(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("客户端发来消息: " + new String(buffer.array(), 
                                                    CharsetUtil.UTF_8));
    }

    /**
     * 处理连接操作
     */
    private static void handleAccept(Selector selector, SelectionKey key) {
        // 通过 ServerSocketChannel 监听过来连接的客户端事件
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        //通过调用 accept 方法,返回一个具体的客户端连接管道
        try {
            SocketChannel socketChannel = serverChannel.accept();
            System.out.println("客户端 " + 
                               socketChannel.getRemoteAddress() + "已上线......");
            // 将channel 注册到selector 上面,而且需要设置成是非阻塞的
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

    这种方式就可以通过一个线程来进行接收客户端的所有链接请求,之后监听所有的链接通道channel,如果有相应事件发生那么就进行对应的相应事件处理,比如读事件、连接请求事件等等

单 Reactor 单线程模型

生活中的例子

    酒店的前台,当前的这种情况就是前台和服务员是同一个人,全程一个人进行服务,效率会非常的低下,后面新来的客人只能在大厅等待了,客户的体验也不好

模型详解

    上面的NIO代码就是单Reactor单线程模型的,确实是一个selector监听轮询所有的channel不假,但是如果真正的多数据量处理读写请求的时候他也是堵塞在那里等待着handler处理完才能进行处理下一个请求,所以这种场景只适合小数据量的处理,瞬间完成或者是毫秒级完成才能达到高效率
缺点:

  1. 高并发复杂数据处理的时候效率不高性能低下,容易造成堵塞效果
  2. 由于是单线程所以发挥不出来多核心的效果

有点:

  1. 模型简单、不存在线程并发的时候造成数据不安全的问题

图解

image.png

单 Reactor 多线程模型

生活中的例子

    此时就是一个前台接待员对应多个前台的服务员了,这样的话前台的接待员专门对接就是接待客人的任务,后面的工作任务都是其他服务员的,这样其他的客人来了能进行及时的接待,即使间隔比较短的来人,那么也是稍等一小会儿就可以了

模型详解

    单线程模型其实就是进行数据逻辑处理的时候效率比较低下,那我们可以将单线程改成多线程,那么就是还是一个Reactor 中的selector进行事件监听,之后Acceptor进行处理客户端的连接请求,创建一个Handler进行该连接请求的后续处理工作,但是这个Hanlder只是负责事件的响应操作,真正的业务逻辑处理还是直接交给了后续的线程池去处理,线程池将任务完成后返回给Handler,之后Handler将处理好的结果返回给客户端
缺点:

  1. 大并发上来的时候还是会存在性能瓶颈的问题
  2. 在并发场景下会存在数据安全性的问题

优点:

  1. 多线程可以充分的利用了系统的CPU资源

图解

image.png

主从 Reactor 多线程模型

生活中的例子

    这种就是接待员只负责类似喊句话的操作,欢迎光临这种,之后就将其交给了其他的接待员进行处理了,比如订房间等等、之后剩下的工作任务交给其他的服务员,比如端茶倒水带领客户去对应的房间,这样客户体验感会更好,能处理客户的需求更快

模型详解

    主从模式就是,Reactor 的主线程模型通过selector 进行连接事件监听,收到的如果是连接事件的话,那么用Acceptor进行连接事件处理,之后将创建好的连接事件交给Reactor子线程进行处理【Reactor主线程和Reactor子线程是一对多的关系】,此时子线程将连接加入到连接队列进行事件监听,如果发生了其他事件比如读事件,那么Reactor子线程就会调用相应的Handler进行事件处理,handler进行数据读取后复杂的业务也是交给后面的线程池进行业务处理并返回结果,Handler接收到处理结果后返回给客户端
缺点:

  1. 高并发的时候依旧存在数据安全性问题
  2. 编码起来比较繁琐

优点:

  1. 能够处理高并发、吞吐量大、效率高、结构之间分工明确netty 其实就是这种场景的演化

图解

image.png

总结

Reactor模型具有如下优点

  1. 响应速度快,不必为单个同步事件所阻塞,因为是事件轮询机制
  2. 可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销
  3. 扩展性好,可以方便的通过增加Reactor实例个数来充分利用CPU资源
  4. 复用性好,Reactor模型本身与具体事件处理逻辑无关,具有很高的复用性

Reactor模型具有如下缺点

  1. 相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试

    想要掌握netty 那么就必须掌握这个模型的机制,也必须要了解NIO的相关知识不然的话掌握不到其精髓,我们后续会进行netty的知识的学习和研究

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

Ich denke du magst

Origin juejin.im/post/7117943615472533535
Empfohlen
Rangfolge