Doug lea《Scalable IO in Java》翻译

896 阅读5分钟

英语水平有限,翻译的不是太好,大家多多见谅:blush:,但是读完之后对Reactor的设计肯定是更上一层楼,还需要细细品尝,建议配合我另一篇文章食用味道更佳:Reactor模型你知道都有哪些吗?

系列文章

创作不易,如果对您有帮助,麻烦辛苦下小手点个关注,有任何问题都可以私信交流哈。 祝您虎年虎虎生威。

目录

  • 可扩展的网络服务
  • 事件驱动处理
  • Reactor设计
    • 基础版本
    • 多线程版本
    • 其他衍生版本

Reactor模型的由来

网络服务

Web服务、分布式对象等大多数都具有相同的基础组件:读请求、解码、处理器、编码、响应,但是每一步处理的方式和成本都不同。

经典服务设计

在这里插入图片描述 每一个请求进来后都会丢给对应的Handler去处理,每个Handler都启动一个线程执行。伪代码如下:

public class Server implements Runnable {
    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            while (!Thread.interrupted()) {
                new Thread(new Handler(serverSocket.accept())).start();
            }
        } catch (IOException exception) {
            exception.printStackTrace();
        }
    }
    static class Handler implements Runnable {
        final Socket socket;
        public Handler(Socket socket) {
            this.socket = socket;
        }
        @Override
        public void run() {
            try {
                byte[] input = new byte[1024];
                socket.getInputStream().read(input);
                byte[] output = process(input);
                socket.getOutputStream().write(output);
            } catch (IOException exception) {
                exception.printStackTrace();
            }
        }
        private byte[] process(byte[] cmd) {
            return null;
        }
    }
}

可伸缩性目标

  • 在客户端不断增加的情况下优雅降级
  • 不断的提升我们的硬件设施(CPU、内存、硬盘、带宽)
  • 但也要满足可用性和性能的目标
    • 低延时
    • 应对陡增的流量
    • 可调控的服务质量

分治处理

分治思想通常是最佳的实现以上目标的的方法:

  • 将处理划分为一个个的小任务,每个任务执行一个动作而不阻塞
  • 当任务被开启后则去执行它

在java.nio包中有相应的基本机制,非阻塞的read、write将不同IO事件发送给事件对应的任务去执行,其实这就是事件驱动的设计,使用的资源更少,不用针对每个请求启用一条线程,为了减少上下文切换,减少阻塞,但是任务分发可能会更慢,而且必须手动将事件和对应的处理动作绑定,响应的编程实现也会更难。

Reactor模型

基础Reactor设计(单线程版本)

在这里插入图片描述

示例代码

public class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocketChannel;
    public Reactor(int port) throws Exception {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        selectionKey.attach(new Acceptor());
    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                //发生阻塞直到有事件
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                //处理所有事件
                while (iterator.hasNext()) {
                    //分发事件
                    dispatch(iterator.next());
                }
                selectionKeys.clear();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private void dispatch(SelectionKey selectionKey) {

        Runnable r = (Runnable) selectionKey.attachment();
        if (r != null) {
            r.run();
        }
    }
    class Acceptor implements Runnable{
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if(channel != null){
                    //单线程处理
                    new BasicHandler(channel,selector);
                    //多线程处理
                }
            }catch (Exception e){

            }
        }
    }
}

final class BasicHandler implements Runnable {
    final SocketChannel socketChannel;
    final SelectionKey selectionKey;
    ByteBuffer input = ByteBuffer.allocate(1024);
    ByteBuffer output = ByteBuffer.allocate(1024);
    static final int READING = 0, SENDING = 1;
    int state = READING;
    public BasicHandler(SocketChannel socketChannel, Selector selector) throws Exception {
        this.socketChannel = socketChannel;
        socketChannel.configureBlocking(false);
        selectionKey = socketChannel.register(selector, 0);
        selectionKey.attach(this);
        selectionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }
    boolean inputIsComplete() {
        return true;
    }
    boolean outputIsComplete() {
        return true;
    }
    void process() { }
    @Override
    public void run() {
        try {
            if(state == READING){
                read();
            }else if(state == SENDING){
                send();
            }
        }catch (Exception e){

}

多线版本设计

  • 增加线程以实现扩展性,主要适用于多处理器模式
  • 工作线程
    • Reactor应该快速的触发Handler去处理
    • 将非IO的处理放到其他线程
  • 多Reactor线程
    • 充分用于IO处理
    • 将负载非给其他的Reactor去执行,充分利用CPU

worker Theads(单Reactor 多线程模型)

  • 只处理IO操作来提升Reactor线程的处理性能
  • 计算绑定处理比转换事件驱动的形式更简单,个人认为,如果使用workder Theads,方便了handler的实现,如果都通过事件驱动,那么handler的实现必须按照Reactor中的要求
  • 对于IO的持续处理比较困难,最好是一开始就读取到所有的输入到一个缓冲区内
  • 使用线程池可以协调和控制,通常需要更小的线程服务更多的设备

在这里插入图片描述 经过修改之后Handler就变成了这个样子:

public class HandlerWithThreadPool implements  Runnable{

    //线程池
    static ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
    static final int PROCESSING = 3;

    final SocketChannel socketChannel;
    final SelectionKey selectionKey;
    ByteBuffer input = ByteBuffer.allocate(1024);
    ByteBuffer output = ByteBuffer.allocate(1024);
    static final int READING = 0, SENDING = 1;
    int state = READING;
    public HandlerWithThreadPool(SocketChannel socketChannel, Selector selector) throws Exception {
        this.socketChannel = socketChannel;
        socketChannel.configureBlocking(false);
        selectionKey = socketChannel.register(selector, 0);
        selectionKey.attach(this);
        selectionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }
    boolean inputIsComplete() {
        return true;
    }
    boolean outputIsComplete() {
        return true;
    }
    void process() { }
    @Override
    public void run() {
        try {
            if(state == READING){
                read();
            }else if(state == SENDING){
                send();
            }
        }catch (Exception e){

        }
    }
    void read() throws Exception {
        socketChannel.read(input);
        if (inputIsComplete()) {
            state = PROCESSING;
            poolExecutor.execute(new Processer());
        }
    }
    void send() throws Exception {
        socketChannel.write(output);
        if (outputIsComplete()) {
            selectionKey.cancel();
        }
    }

    class Processer implements Runnable{
        @Override
        public void run() {
        }
    }
}

协调任务

  • 任务传递,多个Handler链式处理
  • 回调:分发后的处理器回调
  • 队列:不同阶段直接对Buffer的访问
  • Future异步通知:当每个任务产生结果/处理时发生异常通知

使用池化的Executor

  • 一个可调节的worker线程池,相当于Netty的workerGroup(EventLoopGroup)
  • 核心执行方法execute(Runnable r)
  • 可控制
    • 任务队列类型
    • 最大、最小线程数
    • 比守护线程更柔和
    • 保持存活知道idle线程停止
    • 饱和处理策略

多Reactor Theads版本

在这里插入图片描述 mainReactor就像是Netty中的boosGroup或者parentGroup,subReactor就像是Netty中的workerGroup或者childGroup

大家好,我是壹氿,感谢各位小伙伴点赞、收藏和评论,文章持续更新,我们下期再见! 也可以加我的个人VX交流沟通:lhj502819,一起努力冲击大厂,另外有很多学习以及面试的材料提供给大家。