Vert.x: 最简单的服务器,1000 rps
Vert.x: simplest server with 1000 rps
假设你需要写一个1000rps的服务器。将来负载可能会增加。服务器仅服务于一种请求 - getGender(name)
接受名称,return Male
/Female
。确定性别是最简单的操作,需要单索引查找,其中索引是内存中的数据结构。
如果理解正确 - 您创建单个 ServerVerticle
和 运行 Runtime.getRuntime().availableProcessors()
worker verticles 委托工作(见下面的代码)。
问题:
- 这是1000rps任务的最佳方案吗?
- 当15个worker不足时,请求高峰会发生什么?假设一个worker可以处理100 rps。你有 15 个工人。但是在高峰时间你有 3000 rps。
- 假设
NetServer
可以处理 3000 rps,但工人坚持处理它们。 Vert.x 有任何队列来保持等待请求吗?怎么做?如果有 - worker fail 会发生什么?
- 假设
NetServer
无法处理 3000 rps - 只需 运行 服务器的几个实例。 没有陷阱,对吧?
- TCP 是该任务的更好选择吗?
- Vert.x 是多反应器,它像节点 运行 一样是事件循环。
ServerVerticle
是 运行 与事件循环在同一线程中,对吗?
- 如果你有 16 个核心,其中 1 个核心专用于事件循环,那么 Vert.x 将 运行 15 个 GenderVerticles,对吗? 不再阅读?
ServerVerticle.java
public class ServerVerticle extends AbstractVerticle {
public static void main(String[] args) {
Consumer<Vertx> runner = vertx -> vertx.deployVerticle("ServerVerticle", new DeploymentOptions());
Vertx vertx = Vertx.vertx();
runner.accept(vertx);
}
@Override
public void start() throws Exception {
NetServerOptions options = new NetServerOptions();
NetServer server = vertx.createNetServer(options);
server.connectHandler(socket -> {
socket.handler(buffer -> {
vertx.eventBus.send("get.gender", buffer, res -> socket.write(res.toString()));
});
});
server.listen(1234, "localhost");
//Deploy worker verticles
DeploymentOptions deploymentOptions = new DeploymentOptions()
.setInstances(Runtime.getRuntime().availableProcessors())
.setWorker(true);
vertx.deployVerticle("GenderServiceVerticle", deploymentOptions);
}
}
GenderVerticle.java
public class GenderVerticle extends AbstractVerticle {
@Override
public void start() throws Exception {
vertx.eventBus().consumer("get.gender", message -> {
String gender = singleIndexLookup(message);
message.reply(gender);
});
}
singleIndexLookup() { ... }
}
这里有几个问题和一些关于vert.x的误解。一旦你使用 Verticle
s 实现你的代码,你就不需要实现你自己的 main
方法,因为在木头下,内部 main
方法将执行它以确保你可以拥有你的 CPU 能力的全部容量,你不需要自己扩展它:
//Deploy worker verticles
DeploymentOptions deploymentOptions = new DeploymentOptions()
.setInstances(Runtime.getRuntime().availableProcessors())
您应该阅读 documentation 的以下部分。
其次,您将 GenderVerticle
称为 worker,因为它会为您做一些操作。请注意,在 vertx
中,worker 意味着它应该在专用线程池上执行,因为该 Verticle 中的代码可能会执行一些阻塞 IO。
使用 worker 模式会带来性能损失,因为您失去了异步 IO 的优势,并且您的请求需要排队等待池中的线程。
由于您的示例解释了您的所有代码所做的都是内存查找,我假设它是 CPU 有界的而不是 IO 绑定的,这意味着您应该避免将其部署为工作者。
回到您的示例,您有 1 个处理所有 HTTP 流量的 Verticle 和另一个处理它的 Verticle。为了获得最佳性能,您可能希望只有 1 个 Verticle,因为跳数较少,但此解决方案无法水平扩展(您提出问题的原因)假设一个节点只能执行 1000rps,我如何处理 3000rps。
现在你已经走上了正确的道路,你将 http 处理与业务处理分开,它有一点损失,但如果你知道 1 个节点可以处理 1000rps,你必须至少处理 3000rps 所有你需要的要做的是在 3 台额外的机器上部署 GenderVerticle
。
执行此操作并启用集群后,您可以通过添加依赖项(例如:hazelcast)来执行此操作:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-hazelcast</artifactId>
<version>3.3.3</version>
</dependency>
然后使用标志 --cluster
启动您的应用程序。您将拥有一个由 4 台机器组成的集群,其中请求将以循环方式负载均衡到每个 GenderVerticles
.
由于 HTTP 代码由 netty 高度优化,您可能不需要超过一台服务器,如果不是这种情况,您可以选择在服务器前面添加一个流量负载均衡器,然后再次在集群中的另一台机器上部署另一个 ServerVerticle
,现在流量负载均衡器将对 2 台服务器之间的 HTTP 流量进行负载均衡,这将轮询到 GenderVerticles
。
所以我猜你开始看到这样一种模式,一旦你的监控告诉你你的 CPU/NetworkIO 已经达到极限,你就会向集群添加更多机器。
假设你需要写一个1000rps的服务器。将来负载可能会增加。服务器仅服务于一种请求 - getGender(name)
接受名称,return Male
/Female
。确定性别是最简单的操作,需要单索引查找,其中索引是内存中的数据结构。
如果理解正确 - 您创建单个 ServerVerticle
和 运行 Runtime.getRuntime().availableProcessors()
worker verticles 委托工作(见下面的代码)。
问题:
- 这是1000rps任务的最佳方案吗?
- 当15个worker不足时,请求高峰会发生什么?假设一个worker可以处理100 rps。你有 15 个工人。但是在高峰时间你有 3000 rps。
- 假设
NetServer
可以处理 3000 rps,但工人坚持处理它们。 Vert.x 有任何队列来保持等待请求吗?怎么做?如果有 - worker fail 会发生什么? - 假设
NetServer
无法处理 3000 rps - 只需 运行 服务器的几个实例。 没有陷阱,对吧?
- 假设
- TCP 是该任务的更好选择吗?
- Vert.x 是多反应器,它像节点 运行 一样是事件循环。
ServerVerticle
是 运行 与事件循环在同一线程中,对吗? - 如果你有 16 个核心,其中 1 个核心专用于事件循环,那么 Vert.x 将 运行 15 个 GenderVerticles,对吗? 不再阅读?
ServerVerticle.java
public class ServerVerticle extends AbstractVerticle {
public static void main(String[] args) {
Consumer<Vertx> runner = vertx -> vertx.deployVerticle("ServerVerticle", new DeploymentOptions());
Vertx vertx = Vertx.vertx();
runner.accept(vertx);
}
@Override
public void start() throws Exception {
NetServerOptions options = new NetServerOptions();
NetServer server = vertx.createNetServer(options);
server.connectHandler(socket -> {
socket.handler(buffer -> {
vertx.eventBus.send("get.gender", buffer, res -> socket.write(res.toString()));
});
});
server.listen(1234, "localhost");
//Deploy worker verticles
DeploymentOptions deploymentOptions = new DeploymentOptions()
.setInstances(Runtime.getRuntime().availableProcessors())
.setWorker(true);
vertx.deployVerticle("GenderServiceVerticle", deploymentOptions);
}
}
GenderVerticle.java
public class GenderVerticle extends AbstractVerticle {
@Override
public void start() throws Exception {
vertx.eventBus().consumer("get.gender", message -> {
String gender = singleIndexLookup(message);
message.reply(gender);
});
}
singleIndexLookup() { ... }
}
这里有几个问题和一些关于vert.x的误解。一旦你使用 Verticle
s 实现你的代码,你就不需要实现你自己的 main
方法,因为在木头下,内部 main
方法将执行它以确保你可以拥有你的 CPU 能力的全部容量,你不需要自己扩展它:
//Deploy worker verticles
DeploymentOptions deploymentOptions = new DeploymentOptions()
.setInstances(Runtime.getRuntime().availableProcessors())
您应该阅读 documentation 的以下部分。
其次,您将 GenderVerticle
称为 worker,因为它会为您做一些操作。请注意,在 vertx
中,worker 意味着它应该在专用线程池上执行,因为该 Verticle 中的代码可能会执行一些阻塞 IO。
使用 worker 模式会带来性能损失,因为您失去了异步 IO 的优势,并且您的请求需要排队等待池中的线程。
由于您的示例解释了您的所有代码所做的都是内存查找,我假设它是 CPU 有界的而不是 IO 绑定的,这意味着您应该避免将其部署为工作者。
回到您的示例,您有 1 个处理所有 HTTP 流量的 Verticle 和另一个处理它的 Verticle。为了获得最佳性能,您可能希望只有 1 个 Verticle,因为跳数较少,但此解决方案无法水平扩展(您提出问题的原因)假设一个节点只能执行 1000rps,我如何处理 3000rps。
现在你已经走上了正确的道路,你将 http 处理与业务处理分开,它有一点损失,但如果你知道 1 个节点可以处理 1000rps,你必须至少处理 3000rps 所有你需要的要做的是在 3 台额外的机器上部署 GenderVerticle
。
执行此操作并启用集群后,您可以通过添加依赖项(例如:hazelcast)来执行此操作:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-hazelcast</artifactId>
<version>3.3.3</version>
</dependency>
然后使用标志 --cluster
启动您的应用程序。您将拥有一个由 4 台机器组成的集群,其中请求将以循环方式负载均衡到每个 GenderVerticles
.
由于 HTTP 代码由 netty 高度优化,您可能不需要超过一台服务器,如果不是这种情况,您可以选择在服务器前面添加一个流量负载均衡器,然后再次在集群中的另一台机器上部署另一个 ServerVerticle
,现在流量负载均衡器将对 2 台服务器之间的 HTTP 流量进行负载均衡,这将轮询到 GenderVerticles
。
所以我猜你开始看到这样一种模式,一旦你的监控告诉你你的 CPU/NetworkIO 已经达到极限,你就会向集群添加更多机器。