知识库

限制 Bolt 线程与连接

在读/写事务请求量大的情况下,Neo4j 服务器可能会拒绝某些入站事务,并在 Neo4j debug.log 中报告以下错误。

ERROR [o.n.b.r.MetricsReportingBoltConnection] Unable to schedule bolt session <session_id> for execution since there are no
available threads to serve it at the moment. You can retry at a later time or consider increasing max thread pool size for
bolt connector(s). Task java.util.concurrent.CompletableFuture$AsyncSupply@9bde0657 rejected from
org.neo4j.bolt.runtime.CachedThreadPoolExecutorFactory$ThreadPool@95fe540f[Running, pool size = 400, active threads = 400,
queued tasks = 0, completed tasks = 197942]

虽然上述建议的 bolt 线程池大小可以在 neo4j.conf 中配置,但人们可能会期望 bolt 连接的总数(由指标 neo4j.bolt.connections_running 和 neo4j.bolt.connections_idle 报告)始终加起来小于配置的 dbms.connector.bolt.thread_pool_max_size 值。然而,这是一个误解,本文旨在解决这个问题。

Bolt 线程与 Bolt 连接的区别

Bolt 线程是 Neo4j 服务器分配给 CPU 的进程执行器的一部分,用于执行给定的任务。Bolt 连接是从客户端驱动程序会话 Neo4j 服务器的请求。在 JVM 服务器中,侦听套接字上的接收器线程接受连接并将它们放入连接队列中。然后,线程池中的请求处理线程从队列中获取连接并处理请求。在线程每请求模型中,线程仅在请求处理期间关联,即服务需要更少的线程来处理相同数量的客户端连接。由于线程使用大量资源,这意味着服务将更具可扩展性。

dbms.connector.bolt.thread_pool_max_size 指标报告 bolt 线程池的最大大小,限制线程池不会限制连接的总数。一个线程可以处理多个连接。Bolt 线程池大小仅限制 Neo4j 服务器可以同时处理多少个 bolt 线程。如果连接处于非活动状态,则没有线程分配给该连接。目前服务器上没有空闲连接的限制。但是,在驱动程序创建期间,可以在客户端配置连接池大小限制。由于默认情况下连接没有上限,因此驱动程序连接池大小可以增加到机器 CPU 上可用的总连接数。

什么是空闲 Bolt 连接?

指标名称 neo4j.bolt.connections_idle 可能表明空闲 bolt 连接是由已完成的 bolt 事务打开的,但是一旦该事务完成,线程将保持打开但空闲的状态,直到线程池将其关闭。

以上是一个误解,因为在 neo4j.conf 中设置 dbms.connector.bolt.thread_pool_max_size 会限制正在处理的并发线程连接的峰值(由 neo4j.bolt.connections_running 指标表示)。然而,被处理的空闲连接不受此配置的限制。线程池配置(服务器端)不能用于调节连接(客户端)。

为什么空闲连接指标命名为 neo4j.bolt.connections_idle?

因为这些连接是由驱动程序建立的,并且驱动程序在驱动程序的生命周期内重用连接。Bolt 连接池是驱动程序配置。线程池是服务器配置,用于指定其处理连接作业的峰值容量。如果连接没有作业,则它在客户端处于空闲状态。服务器不控制这些空闲连接。只有驱动程序可以关闭它们。此外,服务器没有连接计数的上限,空闲连接不应导致 CPU 使用率增加。

如何控制客户端驱动程序打开的连接最大数量?

可以通过 MaxConnectionPoolSize、MaxConnectionLifetime 和 ConnectionAcquisitionTimeout 参数配置驱动程序连接限制。Neo4j Java 驱动程序的示例配置如下:

Config config = Config.builder()
            .withMaxConnectionLifetime( 30, TimeUnit.MINUTES )
            .withMaxConnectionPoolSize( 50 )
            .withConnectionAcquisitionTimeout( 2, TimeUnit.MINUTES )
            .build();
Driver driver = GraphDatabase.driver( uri, AuthTokens.basic( user, password ), config );

参考