java reactor与NIO

reactor

Understanding Reactor Pattern with Java NIO
reactor-demo
以下内容原版可参看英文文章。

什么是Reactor模式

Reactor 模式是一种事件驱动架构的实现技术

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.

我们知道Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的 Request Handler。

标准reactor模式

Handle

Handle代表操作系统管理的资源,包括:网络链接,打开的文件,计时器,同步对象等等。在我们的web系统中,Handle代表与客户端连接的套接字,Synchronous Event Demultiplexer在这些套接字上等待事件的发生。

Synchronous Event Demultiplexer

在一个Handle集合上等待事件的发生。这里常用系统调用select[1],UNIX和WIN32平台都支持这个系统调用。select的返回结果说明handle上发生情况,需要被处理。

Initiation Dispatcher

提供接口:注册,删除和派发Event Handler。上面的Synchronous Event Demultiplexer等待事件的发生,当检测到新的事件,就把事件交给Initiation Dispatcher,它去回调Event Handler。事件种类一般有:接受到连接,数据输入,数据输出,超时。

Event Handler

定义一个抽象接口,包含一个钩子方法,实现特定服务的派发操作。这个方法实现了与特定应用相关的服务。

Concrete Event Handler

继承上面的类,实现钩子方法。应用把Concrete Event Handler注册到Initiation Dispatcher,等待被处理的事件。当事件发生,这些方法被回调。

Reactor pattern in Java NIO

我们将会基于java nio I/O 复用模型 实现reactor模式的demo

Java NIO 是为了弥补传统 I/O 工作模式的不足而研发的,NIO 的工具包提出了基于 Selector(选择器)、Buffer(缓冲区)、Channel(通道)的新模式;Selector、Channel和 SelectionKey(选择键)配合起来使用,可以实现并发的非阻塞型 I/O 能力。

java reactor实现

让我们将java reactor的实现与标准意义对应起来:
Selector (demultiplexer)

Selector is the Java building block, which is analogous to the demultiplexer in the Reactor pattern. Selector is where you register your interest in various I/O events and the objects tell you when those events occur.

Reactor/initiation dispatcher
We should use the Java NIO Selector in the Dispatcher/Reactor. For this, we can introduce our own Dispatcher/Reactor implementation called ‘Reactor’. The reactor comprises java.nio.channels.Selector and a map of registered handlers. As per the definition of the Dispatcher/Reactor, ‘Reactor’ will call the Selector.select() while waiting for the IO event to occur.

Handle
对应 SelectionKey.

Event
不同的IO events 如 SlectionKey.OP_READ , SelectionKey.OP_ACCEPT,SelectionKey.OP_WRITE,SelectionKey.OP_CONNECT

Handler
事件处理器,A handler is often implemented as runnable or callable in Java.

code time

https://github.com/kasun04/rnd/tree/master/nio-reactor
在我们的代码里通过一个map来管理我们关心的事件和对应的事件handler。
当我们通过Selector.select来轮询到来的事件。并遍历Set<SelectionKey>.

while (true) { // Loop indefinitely
demultiplexer.select();// 是阻塞的

Set<SelectionKey> readyHandles =
demultiplexer.selectedKeys();
Iterator<SelectionKey> handleIterator =
readyHandles.iterator();

while (handleIterator.hasNext()) {
SelectionKey handle = handleIterator.next();

if (handle.isAcceptable()) {
EventHandler handler =
registeredHandlers.get(SelectionKey.OP_ACCEPT);
handler.handleEvent(handle);
// 需要将此次事件key移除,避免重复处理
handleIterator.remove();
}

if (handle.isReadable()) {
EventHandler handler =
registeredHandlers.get(SelectionKey.OP_READ);
handler.handleEvent(handle);
handleIterator.remove();
}

if (handle.isWritable()) {
EventHandler handler =
registeredHandlers.get(SelectionKey.OP_WRITE);
handler.handleEvent(handle);
handleIterator.remove();
}
}
}

我们在handleEvent里处理事件,并且注册新的关心的事件,比如在AcceptEventHandler处理完后,注册SelectionKey.OP_READ,将事件抛给下一个handler。并可以在

public void handleEvent(SelectionKey handle) throws Exception {
System.out.println("===== Accept Event Handler =====");
ServerSocketChannel serverSocketChannel =
(ServerSocketChannel) handle.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
socketChannel.configureBlocking(false);
socketChannel.register(
demultiplexer, SelectionKey.OP_READ);
}

}

我们看到上面轮询的代码其实是单线程的每一个handler的handleEvent都是在Reactor这个一个里的,为什么不做成多线程呢?

NIO由原来的BIO的阻塞读写(占用线程)变成了单线程轮询事件,找到可以进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必须要阻塞),剩余的I/O操作都是纯CPU操作,没有必要开启多线程。

我们看read事件的handler,为了提高效率,业务代码开线程,将io处理与业务代码处理分离。

public void handleEvent(SelectionKey handle) throws Exception {
System.out.println("===== Read Event Handler =====");
SocketChannel socketChannel =
(SocketChannel) handle.channel();

// 从socket读取数据
byte[] buffer=read(socketChannel);

// 此处执行业务操作,最好单线程或者多线程,将io处理与业务代码处理分离
doBusiness(buffer);

// Rewind the buffer to start reading from the beginning
// Register the interest for writable readiness event for
// this channel in order to echo back the message
socketChannel.register(
demultiplexer, SelectionKey.OP_WRITE, inputBuffer);

}

BIO与NIO的对比

美团技术博客--Java NIO浅析
关键差别:
BIO accept,read,write 都是阻塞的。
NIO select 阻塞,read,write是非阻塞的。

bio 经典模型

{
ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池

ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(8088);
while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来
Socket socket = serverSocket.accept();//阻塞
//为新的连接创建新的线程
executor.submit(new ConnectIOnHandler(socket));
}

class ConnectIOnHandler extends Thread{
private Socket socket;
public ConnectIOnHandler(Socket socket){
this.socket = socket;
}
public void run(){
while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循环处理读写事件
String someThing = socket.read()....//读取数据
if(someThing!=null){
......//处理数据
socket.write()....//写数据
}

}
}
}

这是一个经典的每连接每线程的模型,之所以使用多线程,主要原因在于socket.accept()、socket.read()、socket.write()三个主要函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,如果是单线程的话必然就挂死在那里(比如阻塞在read,那么就无法accept到其他请求了);但CPU是被释放出来的,开启多线程,就可以让CPU去处理更多的事情。

NIO reactor模型
如上设计和分析:
由于read,write的非阻塞,所以可以不用多线程,并且由于线程的节约,连接数大的时候因为线程切换带来的问题也随之解决,进而为处理海量连接提供了可能。
单线程处理I/O的效率确实非常高,没有线程切换,只是拼命的读、写、选择事件。但现在的服务器,一般都是多核处理器,如果能够利用多核心进行I/O,无疑对效率会有更大的提高。

限制:
Java的Selector对于Linux系统来说,有一个致命限制:同一个channel的select不能被并发的调用。因此,如果有多个I/O线程,必须保证:一个socket只能属于一个IoThread,而一个IoThread可以管理多个socket。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容