本博文记录了学习Reactor反应器模式的一些笔记。java.util.concurrent包的作者,大师Doug Lea关于分析与构建可伸缩的高性能IO服务的一篇经典文章——《Scalable IO in Java》,在文章中Doug Lea通过各个角度,循序渐进的梳理了服务开发中的相关问题,以及在解决问题的过程中服务模型的演变与进化,文章中基于Reactor反应器模式的几种服务模型架构,也被Netty、Mina等大多数高性能IO服务框架所采用。所以可以仔细拼读一下《Scalable IO in Java》。
反应器模式由Reactor反应器线程、Handlers处理器两大角色组成: (1)Reactor反应器线程的职责:负责响应IO事件,并且分发到Handlers处理器。这里的IO事件,就是NIO中选择器监控的通道IO事件。(2)Handlers处理器的职责:非阻塞的执行业务处理逻辑。与IO事件(或者选择键)绑定,负责IO事件的处理。完成真正的连接建立、通道的读取、处理业务逻辑、负责将结果写出到通道等。
1、单线程Reactor反应器
class EchoServerReactor implements Runnable { EchoServerReactor() throws IOException { InetSocketAddress address = new InetSocketAddress(127.0.0.1,9000); //将新连接处理器作为附件绑定到sk选择键上,在选择键注册完成之后,调用attach方法,将Handler处理器绑定到选择键; //轮询监听 void dispatch(SelectionKey sk) { //当事件发生时,调用attachment方法,可以从选择键取出Handler处理器,将事件分发到Handler处理器中,完成业务处理。( 调用之前attach绑定到选择键的handler处理器对象,attachment与 attach往往是结合使用。) /** *Handler:新连接处理器 *接收新连接,并创建一个输入输出处理器。
class EchoHandler implements Runnable { EchoHandler(Selector selector, SocketChannel c) throws IOException { //将Handler作为选择键的附件 //第二步,注册Read就绪事件 public void run() { try { if (state == SENDING) { |
在以上单线程反应器模式中,Reactor反应器和Handler处理器,都执行在同一条线程上。这样,带来了一个问题:当其中某个Handler阻塞时,会导致其他所有的Handler都得不到执行。另外,目前的服务器都是多核的,单线程反应器模式模型不能充分利用多核资源。
2、多线程池Reactor反应器
基于对单线程反应器模式的分析,多线程反应器可以分为两个方面:(1)、处理器多线程;分离业务处理线程和服务监听及IO事件查询线程。(2)、反应器多线程;引入多个选择器,每个子反应器负责一个选择器,以充分利用CPU。
public class MultiThreadEchoServerReactor { ServerSocketChannel serverSocket; AtomicInteger next = new AtomicInteger(0); Selector[] selectors = new Selector[2]; //selectors集合,引入多个selector选择器 SubReactor[] subReactors = null; //引入多个子反应器 public MultiThreadEchoServerReactor() throws IOException { //初始化多个selector选择器 selectors[0] = Selector.open(); selectors[1] = Selector.open(); serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(127.0.0.1, 9000)); serverSocket.configureBlocking(false); //非阻塞 //第一个selector,负责监控新连接事件 SelectionKey sk = serverSocket.register(selectors[0], SelectionKey.OP_ACCEPT); //附加新连接处理handler处理器到SelectionKey(选择键) sk.attach(new AcceptorHandler()); //第一个子反应器负责第一个选择器 SubReactor subReactor1 = new SubReactor(selectors[0]); //第二个子反应器负责第二个选择器 SubReactor subReactor2 = new SubReactor(selectors[1]); subReactors = new SubReactor[]{subReactor1, subReactor2}; } private void startService() { // 一子反应器对应一条线程 new Thread(subReactors[0]).start(); new Thread(subReactors[1]).start(); } //反应器 class SubReactor implements Runnable { //每条线程负责一个选择器的查询 final Selector selector; public SubReactor(Selector selector) { this.selector = selector; } public void run() { try { while (!Thread.interrupted()) { selector.select(); Set<SelectionKey> keySet = selector.selectedKeys(); Iterator<SelectionKey> it = keySet.iterator(); while (it.hasNext()) { //Reactor负责dispatch收到的事件 SelectionKey sk = it.next(); dispatch(sk); } keySet.clear(); } } catch (IOException ex) { ex.printStackTrace(); } } void dispatch(SelectionKey sk) { Runnable handler = (Runnable) sk.attachment(); //调用之前attach绑定到选择键的handler处理器对象 if (handler != null) { handler.run(); } } } // Handler:新连接处理器 class AcceptorHandler implements Runnable { public void run() { try { SocketChannel channel = serverSocket.accept(); if (channel != null) new MultiThreadEchoHandler(selectors[next.get()], channel); } catch (IOException e) { e.printStackTrace(); } if (next.incrementAndGet() == selectors.length) { next.set(0); } } } public static void main(String[] args) throws IOException { MultiThreadEchoServerReactor server = new MultiThreadEchoServerReactor(); server.startService(); } } //多线程处理器 public class MultiThreadEchoHandler implements Runnable { final SocketChannel channel; final SelectionKey sk; final ByteBuffer byteBuffer = ByteBuffer.allocate(1024); static final int RECIEVING = 0, SENDING = 1; int state = RECIEVING; //引入线程池 static ExecutorService pool = Executors.newFixedThreadPool(4); public MultiThreadEchoHandler(Selector selector, SocketChannel c) throws IOException { channel = c; c.configureBlocking(false); //仅仅取得选择键,后设置感兴趣的IO事件 sk = channel.register(selector, 0); //将本Handler作为sk选择键的附件,方便事件dispatch sk.attach(this); //向sk选择键注册Read就绪事件 sk.interestOps(SelectionKey.OP_READ); selector.wakeup(); } public void run() { //异步任务,在独立的线程池中执行 pool.execute(new AsyncTask()); } //异步任务,不在Reactor线程中执行,使用同步修饰符synchronized,保证统一线程池中的业务处理可以有序进行。 public synchronized void asyncRun() { try { if (state == SENDING) { //写入通道 channel.write(byteBuffer); //写完后,准备开始从通道读,byteBuffer切换成写模式 byteBuffer.clear(); //写完后,注册read就绪事件 sk.interestOps(SelectionKey.OP_READ); //写完后,进入接收的状态 state = RECIEVING; } else if (state == RECIEVING) { //从通道读 int length = 0; while ((length = channel.read(byteBuffer)) > 0) { Logger.info(new String(byteBuffer.array(), 0, length)); } //读完后,准备开始写入通道,byteBuffer切换成读模式 byteBuffer.flip(); //读完后,注册write就绪事件 sk.interestOps(SelectionKey.OP_WRITE); //读完后,进入发送的状态 state = SENDING; } //处理结束了, 这里不能关闭select key,需要重复使用 //sk.cancel(); } catch (IOException ex) { ex.printStackTrace(); } } //异步任务的内部类,实现异步业务asyncRun独立的提交到线程池中。 class AsyncTask implements Runnable { public void run() { MultiThreadEchoHandler.this.asyncRun(); } } } |
3、反应器模式和生产者消费者模式对比
4、反应器模式和观察者模式(Observer Pattern)对比
5、反应器模式总结
第1步:通道注册。IO源于通道(Channel)。IO是和通道(对应于底层连接而言)强相关的。一个IO事件,一定属于某个通道。但是,如果要查询通道的事件,首先要将通道注册到选择器。只需通道提前注册到Selector选择器即可,IO事件会被选择器查询到。 第2步:查询选择。在反应器模式中,一个反应器(或者SubReactor子反应器)会负责一个线程;不断地轮询,查询选择器中的IO事件(选择键)。 第3步:事件分发。如果查询到IO事件,则分发给与IO事件有绑定关系的Handler业务处理器。 第4步:完成真正的IO操作和业务处理,这一步由Handler业务处理器负责。
|