【问题标题】:IncompleteRead error when submitting neo4j batch from remote server; malformed HTTP response从远程服务器提交 neo4j 批处理时出现 IncompleteRead 错误;格式错误的 HTTP 响应
【发布时间】:2014-04-27 03:32:35
【问题描述】:

我已经在服务器 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 转储在这里:failedsuccessful

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

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

所以我认为问题可能是这样的,他们都拒绝处理更多数据,直到他们发送更多数据,并且在其他人处理一些数据之前都不能发送更多数据。最终,neo4j 进入了“出现问题”循环,py2neo 将其解释为“继续发送更多数据”。

这很有趣,但我不确定这意味着什么,从 neo4j 发送到 py2neo 的倒数第二个 TCP 帧开始 \r\n1280\r\n - 假块的开始。启动实际块的\r\n8000\r\n 只是在一个不起眼的 TCP 帧中出现。 (这是 py2neo 发送完 post 请求后发送的第三帧。)

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

【问题讨论】:

    标签: http neo4j tcpdump py2neo


    【解决方案1】:

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

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

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

    您可以为单个批次设置标题

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

    或者用

    设置全局标题
    neo4j._add_header('X-Stream', 'true;format=pretty')
    

    【讨论】:

      猜你喜欢
      • 2021-04-29
      • 2015-09-12
      • 1970-01-01
      • 2018-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-13
      • 1970-01-01
      相关资源
      最近更新 更多