我已经在服务器 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 << ---------------------
当我提交足够大的批次时会发生异常。如果我减小数据集的大小,它就会消失。它似乎与请求大小而不是请求数量有关(如果我向正在创建的节点添加属性,我可以有更少的请求)。
如果我使用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,但对 localhost 的请求不会通过 tcp。
我还有一些尚未探索的途径:我还没有弄清楚请求失败的可靠性或在什么条件下(我曾经看到它通过通常失败的批处理成功,但我还没有探索边界)。而且我没有尝试直接从 python 发出请求,而不通过 py2neo。但我并不特别期望这些中的任何一个都能提供非常丰富的信息。除了使用wireshark的'follow TCP stream'来提取HTTP会话外,我还没有仔细研究过TCP转储;我真的不知道我会在那里寻找什么。在失败的转储中,wireshark 以黑色突出显示了很大一部分,而在成功的转储中只有孤立的黑色线条,也许这是相关的?
所以现在:有人知道会发生什么吗?还有什么我应该尝试诊断问题的吗?
编辑:我开始了解失败的 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:我检查了python挂在哪里。不出所料,这是在发送请求时 - 所以BatchRequestList._execute()
直到 neo4j 放弃后才返回,这就是为什么两者都没有.run()
或.stream()
没有比.submit()
.