从远程服务器提交neo4j批处理时出现IncompleteRead错误; 格式错误的HTTP响应

phi*_*ilh 5 http tcpdump neo4j py2neo

我在服务器A上设置了neo4j,我在服务器B上运行了一个连接它的应用程序.

如果我在服务器A上克隆应用程序并运行单元测试,它可以正常工作.但是在服务器B上运行它们,安装程序运行30秒并因IncompleteRead而失败:

Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/nose-1.3.1-py2.7.egg/nose/suite.py", line 208, in run
    self.setUp()
  File "/usr/local/lib/python2.7/site-packages/nose-1.3.1-py2.7.egg/nose/suite.py", line 291, in setUp
    self.setupContext(ancestor)
  File "/usr/local/lib/python2.7/site-packages/nose-1.3.1-py2.7.egg/nose/suite.py", line 314, in setupContext
    try_run(context, names)
  File "/usr/local/lib/python2.7/site-packages/nose-1.3.1-py2.7.egg/nose/util.py", line 469, in try_run
    return func()
  File "/comps/comps/webapp/tests/__init__.py", line 19, in setup
    create_graph.import_films(films)
  File "/comps/comps/create_graph.py", line 49, in import_films
    batch.submit()
  File "/usr/local/lib/python2.7/site-packages/py2neo-1.6.3-py2.7-linux-x86_64.egg/py2neo/neo4j.py", line 2643, in submit
    return [BatchResponse(rs).hydrated for rs in responses.json]
  File "/usr/local/lib/python2.7/site-packages/py2neo-1.6.3-py2.7-linux-x86_64.egg/py2neo/packages/httpstream/http.py", line 563, in json
    return json.loads(self.read().decode(self.encoding))
  File "/usr/local/lib/python2.7/site-packages/py2neo-1.6.3-py2.7-linux-x86_64.egg/py2neo/packages/httpstream/http.py", line 634, in read
    data = self._response.read()
  File "/usr/local/lib/python2.7/httplib.py", line 532, in read
    return self._read_chunked(amt)
  File "/usr/local/lib/python2.7/httplib.py", line 575, in _read_chunked
    raise IncompleteRead(''.join(value))
IncompleteRead: IncompleteRead(131072 bytes read)
-------------------- >> begin captured logging << --------------------
py2neo.neo4j.batch: INFO: Executing batch with 2 requests
py2neo.neo4j.batch: INFO: Executing batch with 1800 requests
--------------------- >> end captured logging << ---------------------
Run Code Online (Sandbox Code Playgroud)

当我提交足够大的批次时会发生异常.如果我减小数据集的大小,它就会消失.它似乎与请求大小而不是请求数量有关(如果我向我正在创建的节点添加属性,我可以减少请求).

如果我使用batch.run()而不是.submit(),我没有得到错误,但测试失败; 好像批次被静默拒绝了.如果我使用.stream()而不是迭代结果,同样的事情发生在.run(); 如果我迭代它们,我会得到相同的错误.submit()(除了它是"0字节读取").

查看httplib.py表明,当HTTP响应具有Transfer-Encoding: Chunked并且不包含期望一个的块大小时,我们将得到此错误.所以我在测试中运行tcpdump,事实上,这似乎正在发生的事情.最后一个块有长度0x8000,最后的字节是

"http://10.210.\r\n
0\r\n
\r\n
Run Code Online (Sandbox Code Playgroud)

(为了清楚起见,在\n之后添加了换行符.)这看起来像正确的分块,但0x8000th字节是第一个"/",而不是第二个".".早八个字节.它也不是一个完整的响应,是无效的JSON.

有趣的是,在这个块中我们得到以下数据:

"all_relatio\r\n
1280\r\n
nships":
Run Code Online (Sandbox Code Playgroud)

也就是说,它看起来像一个新块的开始,但嵌入在旧块中.如果我们注意到它开始,这个新块将在正确的位置(上面的第二个".")完成.如果块头不存在,那么旧块将在正确的位置完成(8个字节后).

然后,我提取了批处理的POST请求,并使用它运行它cat batch-request.txt | nc $SERVER_A 7474.对此的响应是一个有效的分块HTTP响应,包含一个完整的有效JSON对象.

我想也许netcat发送请求比py2neo更快,所以我介绍了一些减速

cat batch-request.txt | perl -ne 'BEGIN { $| = 1 } for (split //) { select(undef, undef, undef, 0.1) unless int(rand(50)); print }' | nc $SERVER_A 7474
Run Code Online (Sandbox Code Playgroud)

但它继续工作,尽管现在慢得多.

我也尝试在服务器A上执行tcpdump,但是对localhost的请求不会超过tcp.

我仍然有一些我没有探索过的途径:我没有弄清楚请求失败的可靠性或者在哪些条件下(我曾经看到它成功通过一个通常失败的批次,但我没有探索边界) ).而且我没有尝试直接从python发出请求,而不通过py2neo.但我并不特别期望这些中的任何一个都能提供非常丰富的信息.除了使用wireshark的"跟随TCP流"来提取HTTP会话之外,我没有密切关注TCP转储; 我真的不知道我在那里寻找什么.在失败的转储中有一个很大的部分,wirehark以黑色突出显示,并且在成功转储中只有孤立的线条黑色,这可能是相关的吗?

所以现在:有谁知道可能会发生什么?还有什么我应该尝试诊断问题吗?

TCP转储在这里:失败并成功.

编辑:我开始了解失败的TCP转储.整个会话需要大约30秒,而且两个服务器都在发送ZeroWindow TCP帧的时间间隔为~28秒 - 这些是我提到的黑线.

首先,py2neo填满了neo4j的窗口; neo4j发送一个框架说"我的窗口已满",然后另一个框架填满了py2neo的窗口.然后我们花了大约28秒钟,他们每个人只是说"是的,我的窗户仍然是满的".最终neo4j再次打开它的窗口,py2neo再发送一些数据,然后py2neo打开它的窗口.他们都发送了更多的数据,然后py2neo完成发送请求,neo4j在完成之前发送更多数据.

所以我想也许问题可能是这样的,他们都拒绝处理更多的数据,直到他们发送了更多的数据,并且在其他进程处理之前都不能发送更多的数据.最终neo4j进入了"出错了"的循环,py2neo将其解释为"继续并发送更多数据".

这很有意思,但我不确定这是什么意思,从neo4j发送到py2neo的倒数第二个TCP帧开始\r\n1280\r\n- 假块的开头.在\r\n8000\r\n启动实际块,只是出现部分的方式,通过一个不起眼的TCP帧.(这是py2neo完成发送帖子请求后发送的第三帧.)

编辑2:我检查了蟒蛇挂在哪里.不出所料,它是在发送请求时 - 所以BatchRequestList._execute()直到neo4j放弃之后才返回,这就是为什么两者都没有.run()或者.stream()做得更好.submit().

phi*_*ilh 2

看来解决方法是设置 header X-Stream: true;format=pretty。(默认情况下它只是true;它曾经很漂亮,但由于这个错误而被删除(看起来它实际上是一个 Neo4j 错误,并且似乎仍然是开放的,但目前对我来说不是问题)。

看起来,通过设置format=pretty,我们可以让 neo4j 在处理完整个输入之前不发送任何数据。因此它不会尝试发送数据,发送时不会阻塞,并且在发送数据之前不会拒绝读取。

完全删除X-Stream标头,或将其设置为false,似乎与设置具有相同的效果format=pretty(例如,使 Neo4j 发送一个分块的、打印精美的响应,不包含状态代码,并且直到整个请求已被处理),这有点奇怪。

您可以使用以下命令设置单个批次的标题

batch._batch._headers['X-Stream'] = 'true;format=pretty'
Run Code Online (Sandbox Code Playgroud)

或者设置全局标题

neo4j._add_header('X-Stream', 'true;format=pretty')
Run Code Online (Sandbox Code Playgroud)