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的压力剧增。