知识库

限制 Bolt 线程 vs 连接

在读/写事务请求量很高的情况下,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 );

参考资料