具有多线程的异步服务器套接字

Asynchronous Server Sockets with Multithreading

我想设置一个单服务器 - 多客户端系统。

情况

多个客户端想要与服务器启动 TCP 套接字连接,服务器接受连接。 (连接保持很长时间)。我应该如何实现服务器套接字部分,以便它始终可以以最有效的方式立即接受新的客户端连接(意思是,将负载分散到所有可用的核心上)。这些是我目前找到的方法:

具有未绑定线程池的同步服务器套接字

对于每个客户端连接,服务器使用未绑定的线程池在新线程中创建一个新的服务器套接字。问题是当client多的时候,thread太多,server会处理不过来(因为有垃圾收集器?)

具有固定线程池和 LinkedBlockingQueue 的同步服务器套接字

对于每个客户端连接,服务器使用固定的线程池在新线程中创建一个新的服务器套接字。当客户端数量多于固定线程池中的线程数量时,客户端必须等待直到线程再次可用。

单线程异步服务器套接字

对于每个客户端连接,服务器都会创建一个异步服务器套接字,因为这些套接字是异步的,所以它们都可以 运行 在同一个线程上。但是,服务器上的所有负载都分布在 1 个线程上,这似乎性能较差,因为它全部 运行s 在 1 个核心上。

多线程上的异步服务器套接字?

possible/does 将这些异步连接分布到所有可用内核是否有意义?例如,为每个核心创建一个线程,然后将异步任务均匀地填充到这些线程中?这样就可以拥有 "unlimited" 个客户端连接,并将负载分散到所有可用的核心上。

这主要是@markspace 答案的扩展,但更加关注原因。

如果你的问题是问是否有可能确保每个请求都有一个唯一的核心并且被平均分配,嗯,是的,这是可能的,但如果你把它作为一个实现要求,你就是在与满足要求的语言。所以简而言之,有可能,但是没有,没有意义。

Java 开发人员投入了大量时间来获得正确的并发性和任务调度,这意味着他们希望该工具成为一种多用途 one-size-fits-most 类型的工具。显然,并非每个工具都适合这项工作,但我们的目标是限制您调整工具以使其适合工作所需的工作量。如果您关心的是吞吐量,那么避免尝试将资源分配强制朝某个方向进行,而只是在您需要时请求资源可能对您有利。让 Java 专注于高效地做到这一点。

在java中,你也可以手动分配内存,很像C或Rust。是的,这是可能的,但是 java 的全部意义在于通过垃圾收集器从您那里抽象出所有这些信息,这样您就可以少解决一个问题。更具体地说,这是一个目前以比我们绝大多数人能够做到的更好的方式解决的问题。

但是回到你原来的问题。无论如何,如果您认为您可以比 OS(或 JVM,当 Loom 出现时)更好地执行任务调度和线程管理,请继续。但是除非你对如何去做有一个非常好的想法(或者一个非常好的理由),否则它可能不明智,更不用说值得付出努力了。当前的实施足以满足您职业生涯中 99% 的工作。

当然,如果这只是为了学习一些东西(而不是为了要部署到 PROD 的系统),那么首先要深入研究 - 这里是大多数 Java 的并发和异步逻辑。

https://github.com/openjdk/jdk/tree/master/src/java.base/share/classes/java/util/concurrent

我们在 Hazelcast 中所做的:

有一个线程负责接受传入的 TCP/IP 连接。

一旦 TCP/IP 连接被接受,它就会分配给 I/O 线程数组中的单个线程。因此,单个 TCP/IP 连接由单个 I/O 线程处理。

I/O 线程使用 non-blocking I/O,因此单个 I/O 线程可以同时为多个 TCP/IP 连接提供服务。有关详细信息,请参阅:https://www.baeldung.com/java-nio-selector

可以通过删除接受线程并通过将 SO_REUSEPORT 设置为 true 让每个 I/O 线程也接受接受来简化此设计。这允许不同的进程在同一服务器端口上侦听以接受请求。建立 TCP/IP 连接后,OS 将确定 TCP/IP 连接分配给哪个 I/O 线程。

您可能想看看 Reactor(也许还有 Proactor)设计模式。我上面描述的I/O个线程可以看作是一个Reactor。