我已经在服务器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 << ---------------------

当我提交足够大的批处理时会发生异常(exception)。如果我减少数据集的大小,它就会消失。它似乎与请求的大小有关,而不是与请求的数目有关(如果我将属性添加到正在创建的节点中,则请求数可能会减少)。

如果我使用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

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

有趣的是,在此块中,我们获得以下数据:
"all_relatio\r\n
1280\r\n
nships":

也就是说,它看起来像是一个新块的开始,但是嵌入了旧块中。如果我们注意到它开始了,那么这个新块将在正确的位置(上面的第二个“。”)结束。并且如果不存在块头,则旧块将在正确的位置(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

尽管现在速度要慢得多,但它仍然可以正常工作。

我也尝试在服务器A上执行tcpdump,但对本地主机的请求不会通过tcp。

我仍然有一些我没有探索过的途径:我还没有弄清楚请求失败的可靠程度或确切的条件是什么(我曾经看到它以一批通常会失败的批次成功,但是我没有探索界限)。而且我还没有尝试直接通过python发出请求,而不经过py2neo。但是我并不特别期望这些内容中的任何一个都非常有用。除了使用wireshark的“跟随TCP流”提取HTTP对话外,我没有仔细研究TCP转储。我真的不知道我在那儿要找什么。有一个很大的部分,在失败的转储中,wireshark高亮显示为黑色,而在成功的转储中,只有孤立的线为黑色,也许这是相关的吗?

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

TCP转储在这里:failedsuccessful

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

首先,py2neo填满了neo4j的窗口; neo4j发送一个框架,说“我的窗口已满”,然后发送另一个框架,填充py2neo的窗口。然后,我们花了约28秒的时间,每个人都说“是的,我的 window 还满了”。最终neo4j再次打开其窗口,py2neo发送更多数据,然后py2neo打开其窗口。他们两个都发送了更多的数据,然后py2neo完成了其请求的发送,而neo4j在完成之前发送了更多的数据。

因此,我认为问题可能出在某种程度上,他们俩都拒绝处理更多的数据,直到他们发送了更多的数据为止,而他们都拒绝发送更多的数据,直到其他人处理了一些数据。最终neo4j进入“出问题了”循环,py2neo将其解释为“继续发送更多数据”。

这很有趣,但是我不确定这是什么意思,从neo4j发送到py2neo的倒数第二个TCP帧以\r\n1280\r\n开始-伪块的开始。开始实际块的\r\n8000\r\n只是在不明显的TCP帧的中间出现。 (这是py2neo完成发送其发布请求后发送的第三帧。)

编辑2:我检查了一下,以确切地了解python卡在哪里。毫不奇怪,它是在发送请求时-直到neo4j放弃后BatchRequestList._execute()才返回,这就是为什么.run().stream()都没有比.submit()更好的原因。

最佳答案

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

看起来,通过设置format=pretty,我们使neo4j在处理完所有输入之前不发送任何数据。因此,它不会尝试发送数据,不会在发送时阻塞,也不会拒绝读取直到发送了某些内容。

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

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

batch._batch._headers['X-Stream'] = 'true;format=pretty'

或使用以下命令设置全局标题
neo4j._add_header('X-Stream', 'true;format=pretty')

10-06 05:24
查看更多