Reactor线程模型

Reactor线程模型

Reactor单线程模型

一个线程来处理所有的请求,会导致并发很高,如果其中一个Channel在进行读写数据的时候,数据量很大,会导致处理速度很缓慢,这时候如果有人再次发次连接,速度会很慢。

单线程模型的特点:接收连接、IO处理、业务处理均在一个线程中完成。

那么可以怎么优化呢?既然问题出在数据的读取上,那能不能把数据的读取放到线程池中进行,这样就不会因为某个Channel的读取导致整个系统的效率降低了。

Reactor多线程模型

将复杂且耗时的数据编解码及业务处理独立出来,扔给业务线程池来进行处理

Reactor多线程模型:连接的接收和IO处理在reactor线程中完成,编解码和业务处理提交给业务线程,数据的发送还是交给send处理。

虽然性能提高了,但是还不是最好,因为IO操作还是在Selector这个线程中完成的。

主从Reactor多线程模型工作模式

思路:细分每一部分的任务和职责,无非就是Accept连接、Channel就绪后进行IO处理、IO处理后进行业务逻辑的编写,我们把这三步用三部分来描述就好了。

主从Reactor多线程模型:mainReactor线程只负责连接的接收,subReactor线程负责IO处理,编解码等业务操作提交给业务线程进行处理,subReactor线程可以根据需要有多个。

主从Reactor多线程的劣势

优势分析:

  • MainReactor 线程与 SubReactor 线程的职责分工明确,MainReactor 线程只需要接收新连接,SubReactor 线程完成事件检测和IO操作,工作线程完成具体业务处理

  • MainReactor 线程与 SubReactor 线程的数据交互简单, MainReactor 线程只需要把新连接传给SubReactor 线程,SubReactor 线程无需返回数据

  • 多个 SubReactor 线程能够应对更高的并发请求

缺点:

  • 编程复杂度较高

总结:这种模式也被叫做服务器的 1+M+N 线程模式,即使用该模式开发的服务器包含一个(或多个,一般来说只需要一个)连接建立线程+M 个 IO 线程+N 个业务处理线程。这是业界成熟的服务器程序设计模式,由于其优点明显,在许多项目中被广泛使用,包括 Nginx、Netty 等

JAVA实现简单的Reactor多线程主从模型

需要创建五个类

Acceptor:用于接收客户端的连接,有事件连接就交给PollerIO

BuTask:处理业务逻辑的任务

PollerIO:子线程,用于注册到selector模型中去,检测IO事件并做IO操作

Server:服务端对象

ServerThread:基类线程,提供selector和selector的常用方法,继承于Thread

ServerThread

java 复制代码
import com.sun.org.apache.bcel.internal.generic.Select;

import java.io.IOException;
import java.nio.channels.Selector;

/**
 * 线程基类
 */
public class ServerThread extends Thread{
    protected Selector selector;

    ServerThread(String name){
        super(name);
        try {
            this.selector = Selector.open();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //当调用selector.select的时候,如果没有事件就绪,会一直阻塞
    protected void wakeupSelector(){
        this.selector.wakeup();
    }

    //关闭selector
    protected void closeSelector(){
        try {
            selector.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Server

java 复制代码
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;

public class Server {
    //端口
    private int port;

    public int getPort() {
        return port;
    }
    //主线程 acceptor thread
    private Acceptor acceptor;
    //IO线程,子线程
    private Set<PollerIO> ioThreads;
    //业务线程,用线程池代替
    ExecutorService buExecutorService;
    //服务器状态
    public volatile boolean stopped = false;

    //服务生命周期
    public void init(){
        //port
        this.port=9999;
        //最低4个,最多是当前服务器cpu的核心个数
        int ioNumbers=Math.max(4,Runtime.getRuntime().availableProcessors()*2);
        ioThreads=new HashSet<>(ioNumbers);
        for (int i = 0; i < ioNumbers; i++) {
            ioThreads.add(new PollerIO("acceptor"+i,this));
        }
        //acceptor thread
        try {
            this.acceptor=new Acceptor("acceptor",this,ioThreads);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void start(){
        //自下而上启动
        //先启动业务处理线程池子
        this.buExecutorService=new ThreadPoolExecutor(
                200,
                500,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10000)
        );
        //启动IO线程
        for(PollerIO ioThread:ioThreads){
            ioThread.start();
        }
        //start acceptorThread
        acceptor.start();
    }
    public void shutdown(){
        this.stopped=true;
        //关闭线程池
        this.buExecutorService.shutdown();
    }

    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(1);
        Server server=new Server();
        server.init();
        server.start();

        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            server.shutdown();
            latch.countDown();
        }));

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void addBuTask(BuTask task) {
        buExecutorService.execute(task);
    }
}

Acceptor

java 复制代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;

public class Acceptor extends ServerThread{

    private final Server server;
    private final Collection<PollerIO> ioThreads;
    private Iterator<PollerIO> ioIterator;
    private final ServerSocketChannel serverSocketChannel;
    Acceptor(String name, Server server, Set<PollerIO> ioThreads) throws IOException {
        super(name);
        this.server=server;
        this.ioThreads= Collections.unmodifiableList(new ArrayList<>(ioThreads));
        this.ioIterator=this.ioThreads.iterator();

        //打开 serverSocketChannel
        this.serverSocketChannel=ServerSocketChannel.open();
        //非阻塞
        this.serverSocketChannel.configureBlocking(false);
        //绑定端口
        this.serverSocketChannel.bind(new InetSocketAddress(server.getPort()));
        //注册到selector上
        this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    @Override
    public void run() {
        while(!this.server.stopped&&!this.serverSocketChannel.socket().isClosed()){
            try {
                //是否有事件就绪,没有的话会一直阻塞
                selector.select();
                //迭代事件
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while(iterator.hasNext()){
                    //获取在哪个通道上发生事件的,获取通道的SelectionKey
                    SelectionKey selectionKey = iterator.next();
                    //从selector中移除
                    iterator.remove();
                    if(!selectionKey.isValid()){
                        //如果无效
                        continue;
                    }
                    if(selectionKey.isAcceptable()){
                        doAccept(selectionKey);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        closeSelector();
    }

    private void doAccept(SelectionKey selectionKey) throws IOException {
//        selectionKey.channel();也可以获取到发生事件的channel,但这里是类中,可以直接获取类成员的ServerSocketChannel
        //从这个通道上拿到channel
        SocketChannel socketChannel = this.serverSocketChannel.accept();
        if(!ioIterator.hasNext()){
            ioIterator=ioThreads.iterator();
        }
        PollerIO pollerIO = ioIterator.next();//获取到一个IO线程
        pollerIO.addAcceptedConnection(socketChannel);
    }
}

PollerIO

java 复制代码
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

public class PollerIO extends ServerThread{

    private final Server server;
    private final Queue<SocketChannel> acceptQueue;
    PollerIO(String name, Server server) {
        super(name);
        this.server=server;
        this.acceptQueue=new LinkedBlockingQueue<>();
    }

    public void addAcceptedConnection(SocketChannel socketChannel) {
        acceptQueue.offer(socketChannel);
        wakeupSelector();
    }

    @Override
    public void run() {
        while(!server.stopped){
            doSelect();
            doAcceptedConnection();
        }
        closeSelector();
    }

    private void doAcceptedConnection() {
        //创建连接
        SocketChannel socketChannel;
        while(!server.stopped&&(socketChannel=acceptQueue.poll())!=null){
            //如果服务器还在运行,并且accept队列中还有元素,可以注册到selector中
            try {
                socketChannel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
            } catch (ClosedChannelException e) {
                e.printStackTrace();
            }

        }
    }

    private void doSelect() {
        //把链接拿上来处理
        try {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                if(!selectionKey.isValid()){
                    //如果是无效的key
                    continue;
                }
                if(selectionKey.isWritable()||selectionKey.isReadable()){
                    //集中处理
                    handleIO(selectionKey);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void handleIO(SelectionKey selectionKey) {
        if(selectionKey.isReadable()){
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            ByteBuffer inBuffer = ByteBuffer.allocate(1024);
            try {
                socketChannel.read(inBuffer);
                //读取到了数据给业务线程池处理
                BuTask task=new BuTask(inBuffer,socketChannel);
                server.addBuTask(task);
            } catch (IOException e) {
                e.printStackTrace();
                try {
                    socketChannel.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }

        if(selectionKey.isWritable()){

        }
    }
}

BuTask

java 复制代码
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class BuTask implements Runnable{

    private ByteBuffer buffer;
    private final SocketChannel socketChannel;

    public BuTask(ByteBuffer buffer,SocketChannel socketChannel){
        this.buffer=buffer;
        this.socketChannel=socketChannel;
    }

    @Override
    public void run() {
        //先切换读写模式
        buffer.flip();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        buffer=null;
        //解码
        String msg = new String(bytes, Charset.defaultCharset());
        System.out.println("服务端收到来自客户端的数据:"+msg);

        //业务操作
        //encode
        byte[] outs = "来自基于reactor线程模型编写的服务端消息".getBytes(StandardCharsets.UTF_8);
        ByteBuffer outBuffer = ByteBuffer.allocate(outs.length);
        outBuffer.put(outs);//写入数据
        //切换读写
        outBuffer.flip();

        try {
            socketChannel.write(outBuffer);
        } catch (IOException e) {
            try {
                socketChannel.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }

    }
}

这里有一个问题,在PollerIO中:socketChannel.register(selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE);如果我没有去处理写事件,那么CPU的负载会特别高。

原因就是selector是基于epoll封装的接口,在epoll中,只要底层有就绪事件没有处理,epoll也会一直通知用户,也就是调用epoll_wait会一直成功返回,这里的表现就是会一直通知用户这里的管道可以写了,快来写,然后由于我们没有编写对应的写的任务,就会导致陷入死循环,CPU的压力剧增。

小结

相关推荐
永乐春秋34 分钟前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿36 分钟前
【前端】CSS
前端·css
ggdpzhk38 分钟前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
中云DDoS CC防护蔡蔡39 分钟前
微信小程序被攻击怎么选择高防产品
服务器·网络安全·微信小程序·小程序·ddos
HPC_fac130520678162 小时前
以科学计算为切入点:剖析英伟达服务器过热难题
服务器·人工智能·深度学习·机器学习·计算机视觉·数据挖掘·gpu算力
yaoxin5211233 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
学不会•3 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
Theodore_10224 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸5 小时前
01-spring security认证笔记
java·笔记·spring
活宝小娜5 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js