知识库

限制 Bolt 请求

当大量数据在 Neo4j 数据库与客户端之间传输(通常是从服务器到客户端的大型查询结果)时,可能会触发一些隐藏的限速机制。

TCP 限速

Bolt 连接在客户端和 Neo4j 服务器之间是基于 TCP 进行传输的。

客户端接收窗口

TCP 会尝试调节发送的数据量,以免让客户端不堪重负。客户端确实需要处理收到的数据,这可能需要时间,导致读取缓冲区填满的速度快于被清空的速度。

它是怎么工作的?每当客户端确认收到数据时,会发送一个 TCP ACK,并在其中携带当前读取缓冲区的可用容量,这个值叫做接收窗口(Receive Window)。

在捕获流量(例如使用 tcpdump)并在 Wireshark 等工具中打开捕获文件时,接收窗口会显示为 ACK 包中的属性 “WIN”。该数值最大可达 65536。要将其换算为字节数,需要将该值乘以窗口缩放因子(Window Scaling),该因子可以在最初的 TCP 三次握手(SYN/SYN-ACK/ACK)中找到,属性名为 “WS”。

服务器会使用该接收窗口来调节其发送的数据量。它会跟踪“在途”数据的大小(已发送但尚未被确认),并确保该大小不超过接收窗口,通过降低发送速率来实现。如果客户端的接收窗口降为零(Wireshark 会将这些 ACK 标记为 TCP ZeroWindow),服务器将停止发送并等待窗口再次增大(此时客户端会发送 TCP 窗口更新来表示窗口已增大)。

拥塞窗口

TCP 还会根据网络拥塞情况调节传输速率。服务器维护一个拥塞窗口(Congestion Window)。该窗口从较小的值(网络 MTU 的若干倍)开始,并稳步增长直至出现丢包。当发生丢包时,拥塞窗口会被减半(具体减幅取决于所使用的拥塞控制算法),随后继续稳步增长。实际的发送速率取决于客户端接收窗口和拥塞窗口两者的最小值。

Bolt 服务器限速

除了 TCP 本身的限速外,Neo4j Bolt 服务器还会对写入其写缓冲区的速率进行限速。

以下配置参数用于控制该行为:

unsupported.dbms.bolt.outbound_buffer_throttle=true
unsupported.dbms.bolt.outbound_buffer_throttle.high_watermark=512k
unsupported.dbms.bolt.outbound_buffer_throttle.low_watermark=128k
unsupported.dbms.bolt.outbound_buffer_throttle.max_duration=15min

当写缓冲区填满到高水位线时,写入会暂停。Bolt 服务器在等待期间会收到客户端的 ACK,这些 ACK 允许它从写缓冲区中移除相应的数据。服务器每秒轮询一次缓冲区,以检查是否已达到低水位线。如果已达到,则恢复写入。

关于超时的说明

配置参数 unsupported.dbms.bolt.outbound_buffer_throttle.max_duration 控制连接被限速的最长时间。当达到该时间后,服务器会向客户端返回异常:“Bolt 连接 [%s] 将被关闭,因为客户端在 %s 内未消耗出站缓冲区,这并非预期行为”。

该超时可能会与事务超时(dbms.transaction.timeout)产生冲突。当事务超时时间小于限速超时时间时,限速导致的暂停可能会推迟事务被终止的时刻,从而看起来像是事务超时已被触发。

如何判断 Bolt 正在限速?

线程转储(Thread dump)是唯一可行的手段,因为限速触发时日志中没有相应条目。通常可以假设,当查询结果集相对于高水位线(1 MB 以上)非常大时会发生限速,因为结果的消费速度往往慢于产生速度,导致出站缓冲区快速填满。

© . This site is unofficial and not affiliated with Neo4j, Inc.