5

サーバー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 番目のバイトは 2 番目の "." ではなく最初の "/" です。8 バイト早い。また、無効な JSON であるため、完全な応答ではありません。

興味深いことに、このチャンク内で次のデータを取得します。

"all_relatio\r\n
1280\r\n
nships":

つまり、新しいチャンクの始まりのように見えますが、古いチャンクに埋め込まれています。この新しいチャンクは、開始に気付いた場合、正しい場所 (上記の 2 番目の ".") で終了します。チャンク ヘッダーが存在しない場合、古いチャンクは正しい位置 (8 バイト後) で終了します。

次に、バッチの POST リクエストを抽出し、 を使用して実行しましたcat batch-request.txt | nc $SERVER_A 7474。それに対する応答は、完全な有効な JSON オブジェクトを含む有効なチャンク HTTP 応答でした。

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を経由しません。

まだ調査していないいくつかの手段があります: リクエストが失敗する確実性や、正確にどのような条件で失敗するかはわかりません (一度、通常は失敗するバッチで成功するのを見たことがありますが、境界を調査していません)。 )。そして、py2neoを経由せずに、Pythonから直接リクエストを作成しようとはしていません。しかし、これらのいずれも非常に有益であるとは特に期待していません。また、wireshark の「follow TCP stream」を使用して HTTP 会話を抽出する以外は、TCP ダンプを詳しく調べていません。そこで何を探しているのかよくわかりません。失敗したダンプでは Wireshark が黒で強調表示する大きなセクションがあり、成功したダンプでは分離された行だけが黒く強調表示されます。

今のところ: 何が起こっているのか知っている人はいますか? 問題を診断するために他に何かすべきことはありますか?

TCP ダンプは次のとおりです: failedおよびsuccess

編集:失敗した TCP ダンプを理解し始めています。会話全体に約 30 秒かかり、両方のサーバーが ZeroWindow TCP フレームを送信している間、約 28 秒のギャップがあります - これらは私が言及した黒い線です。

まず、py2neo が neo4j のウィンドウをいっぱいにします。neo4j は「私のウィンドウがいっぱいです」というフレームを送信し、次に py2neo のウィンドウをいっぱいにする別のフレームを送信します。次に、「はい、私のウィンドウはまだいっぱいです」と言って、それぞれに約28秒を費やします。最終的に、neo4j が再びウィンドウを開き、 py2neo がもう少しデータを送信すると、py2neo がウィンドウを開きます。どちらももう少しデータを送信し、py2neo はリクエストの送信を終了し、neo4j も終了する前にさらにデータを送信します。

だから、おそらく問題は、どちらももう少しデータを送信するまでそれ以上のデータを処理することを拒否しており、どちらも他のデータを処理するまでそれ以上送信できないようなものだと考えています。最終的に、neo4j は「問題が発生しました」ループに入り、py2neo は「先に進み、さらにデータを送信してください」と解釈します。

興味深いことですが、neo4j から py2neo に送信された最後から 2 番目の TCP フレームが開始する\r\n1280\r\nこと、つまり偽のチャンクの始まりが何を意味するのかわかりません。実際の\r\n8000\r\nチャンクを開始する は、目立たない TCP フレームの途中に表示されます。(これは、py2neo がポスト リクエストの送信を完了した後に送信された 3 番目のフレームでした。)

編集 2: Python がぶら下がっている場所を正確に確認しました。当然のことながら、それはリクエストの送信中だったのでBatchRequestList._execute()、neo4j があきらめるまで返されませ.run()ん。.stream().submit()

4

1 に答える 1

2

回避策は、ヘッダーを設定することX-Stream: true;format=prettyです。(デフォルトではただのtrue; 以前はきれいでしたが、このバグのために削除されました(これは実際には 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')
于 2014-03-21T16:36:02.730 に答える