携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 1 天
Vertx 官方 DEMO 示栗(一)
Vertx 快速上手指北(一)
第一章: Vertx 与 异步编程
本章大纲:
- 分布式软件是大势所趋,而 网络服务 有各种各样弊端
- 为此,我们引入了异步 Asynchronous 和 EventLoop
- 基于 NIO 的异步非阻塞技术
- 基于多路复用 EventLoop 的异步事件处理
- 这类程序,我们称之为:Reactive 响应式 程序
- Vertx 是构建这种 Reactive 应用程序 的工具包
个人总结:
- 本章概要:不是所有的软件都叫 “特仑苏”
- 个人认为本章相当精彩,也非常重要
- 首先:申明了使用 场景 和 前提(杜绝了,使用铁砂掌穿针的情况发生)
- 其次:申明了自身 优势 和 设计思想
优势
- 支持多语言 JavaScript, Ruby,Kotlin、Scala、Groovy 等等
- 轻量:Vert.X 是一个工具箱,而不是一个框架
- ……
从古老的 网络服务 技术 NIO 开始说起
- 首先,点名批评 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();
}
};
}
}
- 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
图片来源于官网,侵删
EventLoop 是一个流行处理异步事件的线程模型。事件被推入 EventLoop,而不是前面 NIO 中那样轮询 SelectKey
- 事件到达时进行排队
- 它们可以是I/O事件,栗如:
- 数据已经准备好使用
- 或者缓冲区已经完全写入套接字
- 它们也可以是任何其他事件,栗如:
- 计时器触发
- 分配一个线程给 EventLoop 处理接入事件
- 被处理事件不应该执行任何阻塞或长时间运行的操作(只做线程压栈)
- 否则,线程会阻塞,违背了使用 EventLoop 的目的。
- 解释下 EventLoop ?我不会。建议自己去看下 React 设计模式,注意,是设计模式,不是 React 框架 😅
最后,Vertx 同学上场
- Vertx 是 是一个在 JVM 上构建 Reactive 应用程序的工具包
来自博主的提示:Vertx 核心设计思想:Reactive
-
那 .... Reactive 是什么
- Elastic 弹性
- Resilient - 高可用性是弹性的另一面(博主瞎译的)
- Responsive - 水平扩展(博主瞎译的)
- Message-driven - 使用 异步消息 而不是 阻塞式的 RPC
- 其他
- 系统层:还意味著:可靠,消息驱动、水平扩展
- 编码层:异步事件处理
- 数据层: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";
}
}