Netty、Redis、ZooKeeper高并发实战(笔记三)

做~自己de王妃 提交于 2020-07-27 13:02:42

Netty、Redis、ZooKeeper高并发实战(笔记三)

将Doug Lea著名的文章《Scalable IO in Java》:http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf,开阔下眼界。
Reactor反应器模式
反应器模式由Reactor反应器线程、Handlers处理器两大角色组成:
(1)Reactor反应器线程的职责:负责响应IO事件,并且分发到Handlers处理器。
(2)Handlers处理器的职责:非阻塞的执行业务处理逻辑。
在Java的OIO编程中,最初和最原始的网络服务器程序,是用一个while循环,不断地监听端口是否有新的连接。如果有,那么就调用一个处理函数来处理,示例代码如下:

while(true){
    socket = accept(); //阻塞,接收连接
    handle(socket) ;   //读取数据、业务处理、写入结果
}

这种方法的最大问题是:如果前一个网络连接的handle(socket)没有处理完,那么后面的连接请求没法被接收,于是后面的请求通通会被阻塞住,服务器的吞吐量就太低了。对于服务器来说,这是一个严重的问题。
为了解决这个严重的连接阻塞问题,出现了一个极为经典模式:Connection Per Thread(一个线程处理一个连接)模式。
class ConnectionPerThread implements Runnable {
    public void run() {
        try {
            //服务器监听socket
            ServerSocketserverSocket =
                    new ServerSocket(NioDemoConfig.SOCKET_SERVER_PORT);
            while (!Thread.interrupted()) {
                Socket socket = serverSocket.accept();
                //接收一个连接后,为socket连接,新建一个专属的处理器对象
                Handler handler = new Handler(socket);
                //创建新线程,专门负责一个连接的处理
                new Thread(handler).start();
            }
        } catch (IOException ex) { /* 处理异常 */ }
    }
    //处理器对象
    static class Handler implements Runnable {
        final Socket socket;
        Handler(Socket s) {
            socket = s;
        }
        public void run() {
            while (true) {
                try {
                    byte[] input = new byte[NioDemoConfig.SERVER_BUFFER_SIZE];
                    /* 读取数据 */
                    socket.getInputStream().read(input);
                    /* 处理业务逻辑,获取处理结果*/
                    byte[] output =null;
                    /* 写入结果 */
                    socket.getOutputStream().write(output);
                } catch (IOException ex) { /*处理异常*/ }
            }
        }
    }
}
对于每一个新的网络连接都分配给一个线程。每个线程都独自处理自己负责的输入和输出。当然,服务器的监听线程也是独立的,任何的socket连接的输入和输出处理,不会阻塞到后面新socket连接的监听和建立。早期版本的Tomcat服务器,就是这样实现的。
对应于大量的连接,需要耗费大量的线程资源,对线程资源要求太高。在系统中,线程是比较昂贵的系统资源。如果线程数太多,系统无法承受。而且,线程的反复创建、销毁、线程的切换也需要代价。
在反应器模式中,有Reactor反应器和Handler处理器两个重要的组件:
(1)Reactor反应器:负责查询IO事件,当检测到一个IO事件,将其发送给相应的Handler处理器去处理。这里的IO事件,就是NIO中选择器监控的通道IO事件。
(2)Handler处理器:与IO事件(或者选择键)绑定,负责IO事件的处理。完成真正的连接建立、通道的读取、处理业务逻辑、负责将结果写出到通道等。
基于Java NIO,如何实现简单的单线程版本的反应器模式呢?需要用到SelectionKey选择键的几个重要的成员方法:
方法一:void attach(Object o)
此方法可以将任何的Java POJO对象,作为附件添加到SelectionKey实例,相当于附件属性的setter方法。这方法非常重要,因为在单线程版本的反应器模式中,需要将Handler处理器实例,作为附件添加到SelectionKey实例。
方法二:Object attachment()
此方法的作用是取出之前通过attach(Object o)添加到SelectionKey选择键实例的附件,相当于附件属性的getter方法,与attach(Object o)配套使用。
这个方法同样非常重要,当IO事件发生,选择键被select方法选到,可以直接将事件的附件取出,也就是之前绑定的Handler处理器实例,通过该Handler,完成相应的处理。
总之,在反应器模式中,需要进行attach和attachment结合使用:在选择键注册完成之后,调用attach方法,将Handler处理器绑定到选择键;当事件发生时,调用attachment方法,可以从选择键取出Handler处理器,将事件分发到Handler处理器中,完成业务处理。


 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!