关于reactor单线程模型的理解

我们平常应该会遇到一个redis的面试题
Redis 的线程模型是什么?
简单来说就是内部采用的是reactor单线程模型,它内部用的是一个叫做文件事件处理器的东西,这个文件事件处理器这个东西就是单线程的,所以说redis也是一个单线程的模型
这个可能涉及到一些网络编程的知识:
什么是BIO,NIO,两者的区别是什么
BIO (Blocking I/O): 同步阻塞I/O模式数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式

正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
这是抄来的定义,怎么理解呢?
看了很多视频,自己也敲了敲代码,说下我自己的理解
首先说说BIO,同步阻塞机制,我写了一个小Demo
服务端代码

public class RedisServer {
    
    
    public static void main(String[] args) throws IOException {
    
    
        ServerSocket serverSocket = new ServerSocket(9090);
        while (true){
    
    
            //阻塞的方法
            Socket socket = serverSocket.accept();
            System.out.println("XXXXX来连接了");
            //在这里是错误的方法,因为我们的BIO不把我们的socket放入线程的话是不能支持并发的
            //因为我们的read方法是会阻塞的,我们另一个客户端进来会在这个地方阻塞掉,是不会回去接收我们的客户端的socket的,所以只能有一个客户端连接
            byte[] bytes = new byte[1024];
            //阻塞的方法   没有消息可读会阻塞
            socket.getInputStream().read(bytes);
            System.out.println("收到客户端的消息" + new String(bytes));
        }
    }
}

客户端代码

public class RedisClient {
    
    
    public static void main(String[] args) throws IOException {
    
    
        Socket socket =  new Socket("127.0.0.1",9090);
        Scanner sc = new Scanner(System.in);
        String s = sc.nextLine();
        socket.getOutputStream().write(s.getBytes());
        socket.close();
    }
}

试想一个客户端去连接,应该是没啥问题的,但是如果两个以上的客户端去连接这个服务端就会出现问题了,因为第二个客户端来连接的时候服务器端的read()方法是同步阻塞机制的,也就是说我们的方法会阻塞到这个地方,第二个客户端是没办法进行操作的。
这也是我们为啥要将服务端这部分代码放到Thread去执行的原因,单线程这样操作是不支持并发的,但是问题又来了,我们都知道线程占资源是很宝贵的,如果我们的服务端创建了大量的线程就会很不好(资源,CPU占用之类的),用线程池来解决呢?确实可以解决一部分问题,但是并不能很完美的解决这个问题,怎么办?就是我们的NIO,他就是致力于单线程下处理多请求并发问题的

补充一些知识

画图理解,在别人的基础上自己理解的,有雷同就是借鉴别人的
一个请求连接共用了几个socket
在这里插入图片描述
单线程怎么处理并发请求呢?
NIO包提供了一个叫Selector选择器集合的东西
我贴一部分代码

        selector = Selector.open();//创建选择器对象
        ssc = ServerSocketChannel.open();//打开服务端socket
        InetSocketAddress addr = new InetSocketAddress(port);
        ssc.socket().bind(addr);//在我们的ServerSockerChanel绑定端口
        ssc.configureBlocking(false);//设置ServerSockerChanel为非阻塞
        //
        SelectionKey sk = ssc.register(selector,SelectionKey.OP_ACCEPT);//
        //给定key一个附加的Acceptor对象,我们可以通过这个来判断是什么事件
        sk.attach(new Acceptor(selector,ssc));

还有一部分代码是这样,至于什么业务我们不用关心,我只是来理解这个思想的

            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> it = selectionKeys.iterator();
            //遍历已经发生的事件
            while (it.hasNext()){
    
    
                SelectionKey selectionKey = it.next();
                //根据事件的Key去调度
                dispatch(selectionKey);
                it.remove();
            }

流程图在这里插入图片描述
也就是说,单线程的情况下,我们用了一个selector的集合来存储我们的socket,这里还附加了一个Acceptor的对象,
流程就是:
如果我们是第一次连接,将我们的accept请求注册到这个集合中,附加一些关于他的信息,然后遍历这个时候我们的selectkeys集合,看看里面存放了什么感兴趣的事,如果是accept那我就调用它的run方法,如果是read方法,我就调用read的run方法,同时好几个请求,既有链接,也有读请求,那我就一起放入我的selectKeys中,轮询一个一个操作,一个一个的删除,这就是我理解的NIO的大致思想
然后我们再来理解redis的线程模型
这里我贴一个老哥的博客,觉得他写的redis的模型不错

猜你喜欢

转载自blog.csdn.net/qq_22155255/article/details/115262800
今日推荐