Scalable IO in Java之网络编程演进过程

592 阅读4分钟

《Scalable IO in Java》 是java.util.concurrent包的作者,大师Doug Lea关于分析与构建可伸缩的高性能IO服务的一篇经典文章。原文地址:gee.cs.oswego.edu/dl/cpjslide…
主要讲了网络编程的演进过程,文中提到的Reactor模式被netty借鉴,书中的代码稍加补全即可运行,下面借助代理理解一下:

Classic Service Designs 传统的服务设计模式

image.png Each handler may be started in its own thread. 每个handler都有单独的线程处理.
示例代码:

public class Server implements Runnable {
    private static final int PORT = 8899;
    private static final int MAX_INPUT = 4096;
    public void run() {
        try {
            ServerSocket ss = new ServerSocket(PORT);
            while (!Thread.interrupted())
                new Thread(new Handler(ss.accept())).start();
            // or, single-threaded, or a thread pool
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    static class Handler implements Runnable {
        final Socket socket;

        Handler(Socket s) {
            socket = s;
        }

        public void run() {
            try {
                byte[] input = new byte[MAX_INPUT];
                socket.getInputStream().read(input);
                byte[] output = process(input);
                socket.getOutputStream().write(output);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        private byte[] process(byte[] cmd) {
            String msg = new String(cmd);
            System.out.println(msg);
            return ("hello " + msg).getBytes();
        }
    }
}

在main方法中new Thread(new Server()).start(); 启动,通过 nc localhost 8899 即可访问。

构建高性能可伸缩的IO服务 在构建高性能可伸缩IO服务的过程中,我们希望达到以下的目标:

  1. 能够在海量负载连接情况下优雅降级;
  2. 能够随着硬件资源的增加,性能持续改进;
  3. 具备低延迟、高吞吐量、可调节的服务质量等特点;

而分发处理就是实现上述目标的一个最佳方式。
分发模式具有以下几个机制:

  1. 将一个完整处理过程分解为一个个细小的任务;
  2. 每个任务执行相关的动作且不产生阻塞;
  3. 在任务执行状态被触发时才会去执行,例如只在有数据时才会触发读操作;

在一般的服务开发当中,IO事件通常被当做任务执行状态的触发器使用,在hander处理过程中主要针对的也就是IO事件;

image.png
java.nio包就很好的实现了上述的机制:

  1. 非阻塞的读和写
  2. 通过感知IO事件分发任务的执行

所以结合一系列基于事件驱动模式的设计,给高性能IO服务的架构与设计带来丰富的可扩展性;

Reactor模式 Reactor也可以称作反应器模式,它有以下几个特点:

  1. Reactor模式中会通过分配适当的handler(处理程序)来响应IO事件,类似与AWT事件处理线程;
  2. 每个handler执行非阻塞的操作,类似于AWT ActionListeners 事件监听
  3. 通过将handler绑定到事件进行管理,类似与AWT addActionListener 添加事件监听;
    首先我们明确下java.nio中相关的几个概念:
    Channels
    支持非阻塞读写的socket连接;
    Buffers
    用于被Channels读写的字节数组对象
    Selectors
    用于判断channle发生IO事件的选择器
    SelectionKeys
    负责IO事件的状态与绑定

基于Reactor模式的服务端设计代码示例:

class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocket;
    Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(port));
        serverSocket.configureBlocking(false);
        SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT); //注册accept事件
        sk.attach(new Acceptor()); //调用Acceptor()为回调方法
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {//循环
                selector.select();
                Set<SelectionKey> selected = selector.selectedKeys();
                Iterator<SelectionKey> it = selected.iterator();
                while (it.hasNext()) {
                    SelectionKey next = it.next();
                    dispatch(next); //dispatch分发事件
                }
                selected.clear();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    void dispatch(SelectionKey k) {
        //不同的SelectionKey会attach不同的Runnable
        //Reactor attach->Acceptor连接处理类
        //Handler attach->Handler读写处理类
        Runnable r = (Runnable)(k.attachment()); //调用SelectionKey绑定的调用对象
        if (r != null) {
            r.run();
        }
    }

    // Acceptor 连接处理类
    class Acceptor implements Runnable { // inner
        public void run() {
            try {
                SocketChannel c = serverSocket.accept();
                if (c != null) {
                    new Handler(selector, c);
                }
            }
            catch(IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

public class Handler implements Runnable {
    private static final int MAXIN = 4096;
    private static final int MAXOUT = 4096;

    final SocketChannel socket;
    final SelectionKey sk;
    ByteBuffer input = ByteBuffer.allocate(MAXIN);
    ByteBuffer output = ByteBuffer.allocate(MAXOUT);
    static final int READING = 0, SENDING = 1;
    int state = READING;

    Handler(Selector sel, SocketChannel c) throws IOException {
        socket = c;
        c.configureBlocking(false);
        // Optionally try first read now
        sk = socket.register(sel, 0);
        sk.attach(this); //将Handler绑定到SelectionKey上
        sk.interestOps(SelectionKey.OP_READ);
        //唤醒Reactor的selector.select();
        sel.wakeup();
    }
    boolean inputIsComplete() {
        return true;
    }

    boolean outputIsComplete() {
        return true;
    }

    void process() {
        input.flip();
        System.out.println("receive: " + new String(input.array()));
    }

    public void run() {
        try {
            if (state == READING) {
                read();
            } else if (state == SENDING) {
                send();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    void read() throws IOException {
        socket.read(input);
        if (inputIsComplete()) {
            process();
            state = SENDING;
            // Normally also do first write now
            sk.interestOps(SelectionKey.OP_WRITE);
        }
    }
    void send() throws IOException {
        String msg = "hello xxx\n";
        output.put(msg.getBytes());
        output.flip();
        socket.write(output);
        if (outputIsComplete()) {
            sk.cancel();
        }
        output.clear();
    }
}

多线程模式

image.png

public class Handler2 implements Runnable {
    private static final int MAXIN = 4096;
    private static final int MAXOUT = 4096;

    private static ExecutorService executorService = Executors.newFixedThreadPool(3);


    final SocketChannel socket;
    final SelectionKey sk;
    ByteBuffer input = ByteBuffer.allocate(MAXIN);
    ByteBuffer output = ByteBuffer.allocate(MAXOUT);
    static final int READING = 0, SENDING = 1;
    static final int PROCESSING = 3;
    int state = READING;

    Handler2(Selector sel, SocketChannel c) throws IOException {
        socket = c;
        c.configureBlocking(false);
        // Optionally try first read now
        sk = socket.register(sel, 0);
        sk.attach(this); //将Handler绑定到SelectionKey上
        sk.interestOps(SelectionKey.OP_READ);
        //唤醒Reactor的selector.select();
        sel.wakeup();
    }
    boolean inputIsComplete() {
        return true;
    }

    boolean outputIsComplete() {
        return true;
    }

    void process() {
        input.flip();
        System.out.println("receive: " + new String(input.array()));
    }

    public void run() {
        try {
            if (state == READING) {
                read();
            } else if (state == SENDING) {
                send();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    synchronized void read() throws IOException {
        socket.read(input);
        if (inputIsComplete()) {
            state = PROCESSING;
            executorService.execute(new Processer());
        }
    }
    void send() throws IOException {
        String msg = "hello xxx\n";
        output.put(msg.getBytes());
        output.flip();
        socket.write(output);
        if (outputIsComplete()) {
            sk.cancel();
        }
        output.clear();
    }

    class Processer implements Runnable {
        public void run() {
            processAndHandOff();
        }
    }

    synchronized void processAndHandOff() {
        process();
        state = SENDING; // or rebind attachment
        sk.interestOps(SelectionKey.OP_WRITE);
    }
}