Reactor 模型
Reactor 单线程模型
Reactor 单线程模型指的是所有的 I/O 操作都在同一个 NIO 线程上完成的。NIO 线程主要有四个作用:
- 作为 NIO 服务端,接收客户端 TCP 连接请求;
- 作为 NIO 客户端,向服务端发起 TCP 连接;
- 读取通信对等端的请求或者应答信息;
- 向通信对等端发送消息请求或者应答消息;
Reactor 单线程模型的整体框架如下:
single thread reactor.jpg
对于编码来说,Reactor 单线程模型的框架如下:
// 服务器端
public class SingleThreadServerReactorModel {
public static void main(String[] args) {
SingleThreadServerReactorModel singleThreadServerReactorModel = new SingleThreadServerReactorModel();
new Thread(singleThreadServerReactorModel.nioThread).start();
}
public Runnable nioThread = new Runnable() {
@Override
public void run() {
try {
Selector selector = Selector.open();
ServerSocketChannel socketChannel = ServerSocketChannel.open();
socketChannel.bind(new InetSocketAddress(PORT));
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
// dispatcher
while (true) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ & SelectionKey.OP_WRITE);
}
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel channel = ((SocketChannel)key.channel());
channel.read(buffer);
handler1(buffer);
handler2(buffer);
}
if (key.isWritable()) {
SocketChannel channel = ((SocketChannel)key.channel());
channel.write(handler4(handler3(writeBuffer)));
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
};
public ByteBuffer handler1(ByteBuffer byteBuffer) {
// 处理器 1,可能是去掉一些不需要的字节
return byteBuffer;
}
public ByteBuffer handler2(ByteBuffer byteBuffer) {
// 处理器 2,向发送缓存写入一些数据
writeBuffer.put(byteBuffer);
writeBuffer.flip();
return byteBuffer;
}
public ByteBuffer handler3(ByteBuffer byteBuffer) {
return byteBuffer;
}
public ByteBuffer handler4(ByteBuffer byteBuffer) {
return byteBuffer;
}
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
public static int PORT = 8080;
}
note
- 该模型适合于一些小容量应用的场景
- 对于高负载、大并发的应用,一个 NIO 线程同时处理成败上千的链路,会负载过重
- 一旦 NIO 线程挂掉,整个服务器都会崩溃
Reactor 多线程模型
Reactor 多线程模型有一组 NIO 线程来处理 I/O 操作,有如下特点:
- 有一个专门的 NIO 线程 - Accepter 线程用于监听服务端,接受客户端的 TCP 连接
- 网络 I/O 操作由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,用于消息的读取、解码、编码和发送等
- 一个 NIO 线程可以同时处理 N 条链路,但是一个链路只能由一个对应的 NIO 线程处理,防止并发操作问题
模型如下,如下:
mutiple thread reactor.jpg
对于编码来说,Reactor 多线程模型的框架如下:
public class MultiplyThreadServerReactorModel {
private Executor nioPool = Executors.newFixedThreadPool(10);
private static final int PORT = 8080;
public static void main(String[] args) {
MultiplyThreadServerReactorModel reactorModel = new MultiplyThreadServerReactorModel();
new Thread(reactorModel.accepter).start();
}
public Runnable accepter = () -> {
try {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(PORT));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel nioChannel = channel.accept();
NioThread nioThread = new NioThread(nioChannel);
nioPool.execute(nioThread);
}
}
} catch (IOException e) {
e.printStackTrace();
}
};
public class NioThread implements Runnable {
public NioThread(SocketChannel socketChannel) {
try {
selector = Selector.open();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_WRITE & SelectionKey.OP_READ);
while (socketChannel.isConnected()) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ & SelectionKey.OP_WRITE);
}
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel channel = ((SocketChannel)key.channel());
channel.read(buffer);
handler2(handler1(buffer), writeBuffer);
}
if (key.isWritable()) {
SocketChannel channel = ((SocketChannel)key.channel());
channel.write(handler4(handler3(writeBuffer)));
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
}
Selector selector;
private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
}
private ByteBuffer handler1(ByteBuffer byteBuffer) {
// 处理器 1,可能是去掉一些不需要的字节
return byteBuffer;
}
private ByteBuffer handler2(ByteBuffer byteBuffer, ByteBuffer writeBuffer) {
// 处理器 2,向发送缓存写入一些数据
writeBuffer.put(byteBuffer);
writeBuffer.flip();
return byteBuffer;
}
private ByteBuffer handler3(ByteBuffer byteBuffer) {
return byteBuffer;
}
private ByteBuffer handler4(ByteBuffer byteBuffer) {
return byteBuffer;
}
}
note
- 在绝大部分情况下,该模型都可以较好的使用
- 但是在极端情况下,比如说需要对客户端握手进行安全验证等,都会影响效率
- 如果 Acceptor 挂掉,会影响整个系统的稳定
主从 Reactor 多线程模型
该模型的主要特点是将 多线程的 Acceptor 线程,放在一个线程池中运行。Acceptor 线程池仅仅用于用户的登陆、握手和安全认证,一旦连接建立成功,就将链路注册到后端 subReactor 线程池中,由 I/O 线程负责后续的 I/O 操作。
整体架构如下:
master thread reactor.jpg
利用主从 NIO 线程模型,可以有效解决一个服务端监听线程无法有效处理所有客户端连接的性能不足问题。在 Netty 的官方 Demo 中,推荐使用该线程模型。
netty 线程池
netty 的线程池(NioEventLoop
)设计并不仅仅是为了纯粹的 I/O 线程(向 channel 中读写数据),除了负责 I/O 操作外,还要负责处理以下的两种任务:
- 系统 Task:通过调用
NioEventLoop
的execute(Runnable task)
实现。创建系统 Task 的原因是当 I/O 线程和用户线程同时操作网络资源时,为了防止并发操作导致的锁竞争,将用户线程的操作封装成 Task 放入消息队列中,由 I/O 线程负责执行,这样就实现了局部无锁化。 - 定时任务:通过调用
NioEventLoop
的schedule(Runnable command, long delay, TimeUnit unit)
实现。
note
- 通过调整线程池的个数,是否共享线程池等方式,netty 可以在单线程、多线程、主从 Reactor 模式之间自由切换
- 为了尽可能的提高性能,netty 在很多地方进行了无锁化设计,如,在 I/O 线程内部进行了串行化设计,避免多线程竞争导致的性能下降问题。
为了要实现定时任务和系统 Task,所有的 netty 线程池及其实现都继承了 ScheduledExecutorService
,该类是 JDK 的基本接口,比较重要的接口函数如下:
// 提交一个任务,在 delay 之后执行
// 返回 ScheduledFuture
// 可以通过 ScheduledFuture 查看多少时间之后执行 (getDelay),查看是否成功(isDone),阻塞的取得运算结果(get)
// 或者可以通过 ScheduledFuture 取消任务(cancel)
// 注意 isDone 返回 true 不代表执行任务成功,也可能是被取消,抛出异常等
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUn