reactor线程模型

以下为个人学习心得,只提供参考

一、阻塞io:

public void runServer(int port) throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        while (true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[inputStream.available()];
            inputStream.read(bytes);
            String msg = new String(bytes, StandardCharsets.UTF_8);
            System.out.println("我收到了消息" + msg);
        }
    }

下一个连接必须等待上一个连接IO处理完毕

调用recevfrom
yes
no
return
应用程序
内核
是否存在数据报
存在
不存在
从内核拷贝到用户态
阻塞等待数据

二、伪异步io

伪异步IO指的是在接受网络套接字后,开启一个业务线程去处理IO事件。本质上还是采用上面的阻塞IO模型

 //加上线程池
    public ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
            10, 0, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10),
            new ThreadFactory() {
                AtomicInteger atomicInteger = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "Thread-name" + atomicInteger.addAndGet(1));
                }
            }, new ThreadPoolExecutor.CallerRunsPolicy());


    public void runServer(int port) throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);

        while (true) {
            threadPoolExecutor.execute(() -> {
                try {
                    handler(serverSocket.accept());
                } catch (IOException | InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }

三、非阻塞IO:

就是询问内核是否有数据,如果没数据此时不会阻塞会立即返回EWOULDBLOCK。虽然是非阻塞但是是需要不断的轮询。
在这里插入图片描述

1、IO多路复用:

在这里插入图片描述
如果使用io多路复用,则只需要一个单线程调用select,这时会等待多路复用器返回就绪状态集合。此时应用程序就可以调用recvfrom直接读取已就绪的数据了。整个过程我们只需要一个线程去向内核获取就绪状态的操作。io多路复用器帮我们获取并发的连接。

常见的多路复用器:select,poll,epoll
select: 只知道了有IO事件发生,不知道有几个IO事件,只能轮询所有的连接,时间复杂度为O(n),有连接数量的限制
poll: 和select一样。但是连接数无限制
epoll:直接将对应的连接发生了什么类型的事件告诉我们 速度上体现为O(1)

总结:阻塞IO:应用程序处理多条连接请求,需要一个一个等待accept()
非阻塞IO(多路复用):I/O多路复用器帮我们获取多个套接字的就绪状态,应用程序可以并发的去读取数据处理业务逻辑。

Reactor:

reactor线程模型是基于事件驱动的非阻塞线程模型。Reactor根据IO事件调用相应的处理器进行网络IO操作,如:连接请求调用Acceptor进行处理,IO读写事件调用读写处理器进行处理。

在这里插入图片描述

其中最主要的部件是IO多路复用器,没有IO多路复用器reactor线程模型就无法达到效果。IO多路复用器是底层操作系统提供的。比如select、poll、epoll。当有IO事件处于就绪状态时多路复用器就会返回给应用程序。

1、单线程Reactor:
获得事件通知
连接事件
连接完毕,等待IO事件
io读写事件
客户端请求
多路复用轮询
reactor得到对应的类型事件
调用Acceptor接受连接套接字并进行后处理,如:注册SocketChannel到Selector中
单线程分发事件并处理业务逻辑

代码如下:

public class SingleReactor {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;

    public SingleReactor() throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        //选出一个多路复用器
        selector = Selector.open();
        //非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(8080));
        //新建ServerSocketChannel,并添加到Selector中,注册Accept为感兴趣事件
        SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //为Accept事件绑定 Acceptor处理器
        selectionKey.attach(new Acceptor(serverSocketChannel, selector));
    }

    public void start() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                //无事件阻塞,有事件立即返回
                selector.select();
                //一次性获得多个就绪状态的selectionKey
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    //执行处理器
                    dispatch(selectionKey);
                }
                iterator.remove();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void dispatch(SelectionKey selectionKey) {
     //执行当前事件的处理器(如果程序刚跑第一次,此时的channel只有ServerSocketChannel必然为连接事件,就会执行Acceptor)
        Handler eventHandler = (Handler) selectionKey.attachment();
        eventHandler.execute();
    }
}




//Acceptor
public class Acceptor implements Handler {
    private ServerSocketChannel serverSocketChannel;

    private Selector selector;

    public Acceptor(ServerSocketChannel serverSocketChannel, Selector selector) {
        this.serverSocketChannel = serverSocketChannel;
        this.selector = selector;
    }

    @Override
    public void execute() {
        try {
            SocketChannel socketChannel = serverSocketChannel.accept();
            //selector注册socketChannel
            new EventHandler(socketChannel, selector);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

public class EventHandler implements Handler {


    private SocketChannel socketChannel;

    private SelectionKey selectionKey;


    public EventHandler(SocketChannel socketChannel, Selector selector) throws IOException {
        this.socketChannel = socketChannel;
        socketChannel.configureBlocking(false);
        selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
        selectionKey.attach(this);

    }


    @Override
    public void execute() {
        doExecute();
    }

    private void doExecute() {
        if (selectionKey.isReadable()) {
            System.out.println(Thread.currentThread().getName() + ":读ready");
        }
    }
}

单线程reactor由于处理IO事件和接受连接都是串行执行,面对多并发请求,吞吐量显然不够

2、多线程Reactor:
获得事件通知
连接事件
连接完毕,等待IO事件
io读写事件
客户端请求
多路复用轮询
reactor得到对应类型的事件
调用Acceptor接受连接套接字并进行后处理,如:注册SocketChannel到Selector中
分发事件并用业务线程池处理业务逻辑

相比于单线程reactor这里只是在处理io事件时采用业务线程池进行处理。这种单线程处理连接,多线程处理I/O的模式大部分情况下其实够用了。比如netty中这样设置:

EventLoopGroup boss=new NioEventLoopGroup(1);
EventLoopGroup worker=new NioEventLoopGroup();
3、主从Reactor多线程:
主reactor进行selector轮询
等待新的连接请求
从Reactor进行selector轮询
ASYN
等待新的读写就绪写事件
客户端请求
事件类型
连接请求
读写请求
主reactor调用Acceptor接受连接套接字并用线程池进行后处理,如:注册SocketChannel到Selector中
从reactor用业务线程池分发I/O事件
异步处理I/O

相比于多线程Reactor,此处的连接请求处理也放入有一个业务线程池处理。主要是因为如果是大量客户的连接单线程去接受连接事件显然吞吐量是不够的。日常开发中像redis这种面向的是服务端web应用这样子,单线程就足够了。而像移动端连接到服务端的显然适用于主从Reactor多线程模式。
netty可以这样配置:

EventLoopGroup boss=new NioEventLoopGroup();
EventLoopGroup worker=new NioEventLoopGroup();

代码实现:


//主Reactor
public class MainReactor {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;

    private Executor executor;

    public MainReactor() {
        try {
            executor = Executors.newFixedThreadPool(4);
            serverSocketChannel = ServerSocketChannel.open();
            selector = Selector.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.bind(new InetSocketAddress(8080));
            SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            selectionKey.attach(new Acceptor(serverSocketChannel));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void start() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    selectionKeys.remove(selectionKey);
                    if (selectionKey.isAcceptable()) {
                        Acceptor acceptor = (Acceptor) selectionKey.attachment();
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        executor.execute(() -> {
                            try {
                                acceptor.addSocketChannel(socketChannel);
                                selector.wakeup();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        });
                    }
                }


            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


//Acceptor
public class Acceptor {
    private ServerSocketChannel serverSocketChannel;

    private SubReactor subReactor;


    public Acceptor(ServerSocketChannel serverSocketChannel) {
        this.serverSocketChannel = serverSocketChannel;
        subReactor = new SubReactor();
    }

    public void addSocketChannel(SocketChannel socketChannel) throws IOException {
        subReactor.addSocketChannel(socketChannel);
    }

}


//从Reactor
public class SubReactor {

    private Selector selector;

    private Executor executor;

    ByteBuffer allocate = ByteBuffer.allocate(1024);

    public SubReactor() {
        try {
            selector = Selector.open();
            executor = Executors.newFixedThreadPool(4);
            select();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void addSocketChannel(SocketChannel socketChannel) {
        try {
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void select() {
        executor.execute(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    selector.selectNow();
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey selectionKey = iterator.next();
                        iterator.remove();
                        if (selectionKey.isReadable()) {
                            readOrWrite(selectionKey);

                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }


    public void readOrWrite(SelectionKey selectionKey) {
        try {
            SocketChannel channel = (SocketChannel) selectionKey.channel();
            if (selectionKey.isReadable()) {
                channel.read(allocate);
                String s = new String(allocate.array(), StandardCharsets.UTF_8);
                System.out.println(s);
                allocate.clear();
                selectionKey.interestOps(SelectionKey.OP_WRITE);
            } else if (selectionKey.isWritable()) {

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里贴的代码完全是按照自己的理解写的,可能有不对的地方,部分概念参考了《Netty权威指南》
开发中最好还是用Netty,解决或避免了不少JDK NIO包的bug。比如空轮询。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值