design\project\Vertx(三)

225 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 1 天

Vertx 官方 DEMO 示栗(一)

Vertx 快速上手指北(一)

第一章: Vertx 与 异步编程

本章大纲:

  1. 分布式软件是大势所趋,而 网络服务 有各种各样弊端
  2. 为此,我们引入了异步 AsynchronousEventLoop
    1. 基于 NIO 的异步非阻塞技术
    2. 基于多路复用 EventLoop 的异步事件处理
  3. 这类程序,我们称之为:Reactive 响应式 程序
  4. Vertx 是构建这种 Reactive 应用程序 的工具包

个人总结:

  1. 本章概要:不是所有的软件都叫 “特仑苏”
  2. 个人认为本章相当精彩,也非常重要
    1. 首先:申明了使用 场景前提(杜绝了,使用铁砂掌穿针的情况发生)
    2. 其次:申明了自身 优势设计思想

优势

  • 支持多语言 JavaScript, Ruby,Kotlin、Scala、Groovy 等等
  • 轻量:Vert.X 是一个工具箱,而不是一个框架
  • ……
从古老的 网络服务 技术 NIO 开始说起
  1. 首先,点名批评 BIO 同学
/**
 * 基于多线程的 Echo 服务
 */
public class SynchronousEcho {

  public static void main(String[] args) throws Throwable {
    ServerSocket server = new ServerSocket();
    server.bind(new InetSocketAddress(3000));
    while (true) {   // <1>
      Socket socket = server.accept();
      // 每次启动一个线程
      new Thread(clientHandler(socket)).start();
    }
  }

  /**
   
   */
  private static Runnable clientHandler(Socket socket) {
    return () -> {
      try (
        BufferedReader reader = new BufferedReader(
          new InputStreamReader(socket.getInputStream()));
        PrintWriter writer = new PrintWriter(
          new OutputStreamWriter(socket.getOutputStream()))) {
        String line = "";
        while (!"/quit".equals(line)) {
          line = reader.readLine();      // <2>
          System.out.println("~ " + line);
          writer.write(line + "\n");  // <3>
          writer.flush();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    };
  }
}
  1. BIO 同学有什么问题呢?
  • 使用多线程实现 “伪异步”
    • 线程绝不是廉价的资源
      • 线程需要内存
      • 线程越多,对操作系统内核调度器的压力就越大
      • 因为它需要为线程提供CPU时间
    • 我们可以通过使用线程池在关闭连接后重用线程来改进
    • 但在任何给定的时间点,我们仍然需要为n个连接使用n个线程
  • 加上:存在操作系统级的阻塞
    • 读操作可能正在等待来自网络的数据到达
    • 之前的写操作已经将缓冲区填满,写操作可能需要等待缓冲区被清空

来自博主的提示:Vertx 核心应用场景:边缘服务调用

  • 当我们讨论数以万计的并发连接呢?

    • 假设有一个 API 需要请求 4 个边缘服务接口
    • 如果我们有1000个并发的网络请求,会使用到 5000 个线程
  • 加上:应用程序经常部署到容器化或虚拟化环境中

    • 这意味着它们分配的CPU时间可能有限
    • 进程的可用内存也可能受到限制
    • 应用必须与其他应用程序共享CPU资源
    • 如果所有应用程序都使用阻塞I/O,那么很快就会有太多线程需要管理和调度
接下来,我们介绍 NIO 同学
/**
 * NIO 服务器栗子
 * 许多并发连接可以在单个线程上复用
 */
public class AsynchronousEcho {

  public static void main(String[] args) throws IOException {
    // 您可能已经想到了 C 语言中的 select 函数
    Selector selector = Selector.open();

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(3000));
    serverSocketChannel.configureBlocking(false);
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    // 无需等待 I/O 操作完成,我们可以转向 NIO
    while (true) {
     
      selector.select();
      Iterator<SelectionKey> it = selector.selectedKeys().iterator();
      // NIO 背后的思想是请求阻塞时,继续执行其他任务,直到操作结果准备好
      while (it.hasNext()) {
        SelectionKey key = it.next();
        if (key.isAcceptable()) {
          // 执行线程会做其他事情(比如处理另一个连接)
          newConnection(selector, key);
          // 直到数据被放入缓冲区,准备好在内存中使用。
        } else if (key.isReadable()) {
          echo(key);
        } else if (key.isWritable()) {
          continueEcho(selector, key);
        }
        it.remove();
      }
    }
  }
  // 以下省略一堆我看不懂的代码 ..... 大家可以去 www.github.com 找 vertx-in-action
  
}
  • 解释下 NIO ?对不起,我也看不懂 😃
接下来,有请 多路复用同学
  • EventLoop

1.2.png

图片来源于官网,侵删

EventLoop 是一个流行处理异步事件的线程模型。事件被推入 EventLoop,而不是前面 NIO 中那样轮询 SelectKey

  • 事件到达时进行排队
  • 它们可以是I/O事件,栗如:
    • 数据已经准备好使用
    • 或者缓冲区已经完全写入套接字
  • 它们也可以是任何其他事件,栗如:
    • 计时器触发
  • 分配一个线程给 EventLoop 处理接入事件
    • 被处理事件不应该执行任何阻塞或长时间运行的操作(只做线程压栈)
    • 否则,线程会阻塞,违背了使用 EventLoop 的目的。
  • 解释下 EventLoop ?我不会。建议自己去看下 React 设计模式,注意,是设计模式,不是 React 框架 😅
最后,Vertx 同学上场
  • Vertx 是 是一个在 JVM 上构建 Reactive 应用程序的工具包

来自博主的提示:Vertx 核心设计思想:Reactive

  • 那 .... Reactive 是什么

    1. Elastic 弹性
    2. Resilient - 高可用性是弹性的另一面(博主瞎译的)
    3. Responsive - 水平扩展(博主瞎译的)
    4. Message-driven - 使用 异步消息 而不是 阻塞式的 RPC
    5. 其他
      1. 系统层:还意味著:可靠,消息驱动、水平扩展
      2. 编码层:异步事件处理
      3. 数据层:stream 流支持
  • Vertx 不仅仅是(第一个迭代的目标) “ JVM 的 Node.js ”

  • 最后 Vertx 同学首次亮相(不着急,我们下章慢慢讲)

public class VertxEcho {

  private static int numberOfConnections = 0;

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();

    // 创建一个 TCP 服务器
    vertx.createNetServer()
      .connectHandler(VertxEcho::handleNewClient)
      .listen(3000);

    // 每隔 5 秒打印一次,当前 TCP 服务器的连接数
    vertx.setPeriodic(5000, id -> System.out.println(howMany()));

    // 创建一个 http 服务器,返回当前 TCP 服务器的连接数
    vertx.createHttpServer()
      .requestHandler(request -> request.response().end(howMany()))
      .listen(8080);
  }

  // 新连接处理器
  private static void handleNewClient(NetSocket socket) {

    // 当前连接数++(EventLoop 是单线程的,这里是线程安全的)
    numberOfConnections++;

    // 此 TCP 服务器原样返回请求数据
    socket.handler(buffer -> {
      socket.write(buffer);
      if (buffer.toString().endsWith("/quit\n")) {
        socket.close();
      }
    });

    // 配置,连接关闭监听器
    socket.closeHandler(v -> numberOfConnections--);
  }

  private static String howMany() {
    return "We now have " + numberOfConnections + " connections";
  }
}