9

まず、いくつかの背景。一連の短い URL を展開/解決するワーカーがあります。

http://t.co/example -> http://example.com

したがって、リダイレクトに従うだけです。それでおしまい。接続からデータを読み取ることはありません。200 を取得した直後に、最終的な URL を返し、InputStream を閉じます。

さて、問題そのもの。運用サーバーでは、リゾルバー スレッドの 1 つがInputStream.close()呼び出し内でハングします。

"ProcessShortUrlTask" prio=10 tid=0x00007f8810119000 nid=0x402b runnable [0x00007f882b044000]
   java.lang.Thread.State: RUNNABLE
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
        at java.io.BufferedInputStream.skip(BufferedInputStream.java:352)
        - locked <0x0000000561293aa0> (a java.io.BufferedInputStream)
        at sun.net.www.MeteredStream.skip(MeteredStream.java:134)
        - locked <0x0000000561293a70> (a sun.net.www.http.KeepAliveStream)
        at sun.net.www.http.KeepAliveStream.close(KeepAliveStream.java:76)
        at java.io.FilterInputStream.close(FilterInputStream.java:155)
        at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.close(HttpURLConnection.java:2735)
        at ru.twitter.times.http.URLProcessor.resolve(URLProcessor.java:131)
        at ru.twitter.times.http.URLProcessor.resolve(URLProcessor.java:55)
        at ...

skip()簡単な調査の後、接続プールに戻す前にストリームをクリーンアップするために呼び出されることを理解しました (キープアライブがオンに設定されている場合)。それでも、この状況を回避する方法がわかりません。さらに、私たちのコードに悪い設計があるのか​​、それとも JDK に問題があるのか​​疑問です。

したがって、質問は次のとおりです。

  1. ハングアップを避けることはできclose()ますか?たとえば、妥当なタイムアウトを保証します。
  2. 接続からのデータの読み取りをまったく回避することは可能ですか? 最終的な URL だけが必要であることを思い出してください。skip()本当は、絶対に呼ばれたくない…と思い ます。

アップデート:

KeepAliveStream、79 行目、close()メソッド:

    // Skip past the data that's left in the Inputstream because
    // some sort of error may have occurred.
    // Do this ONLY if the skip won't block. The stream may have
    // been closed at the beginning of a big file and we don't want
    // to hang around for nothing. So if we can't skip without blocking
    // we just close the socket and, therefore, terminate the keepAlive
    // NOTE: Don't close super class
    try {
        if (expected > count) {
        long nskip = (long) (expected - count);
        if (nskip <= available()) {
            long n = 0;
            while (n < nskip) {
            nskip = nskip - n;
            n = skip(nskip);} ...

JDK自体にバグがあるように思えます。残念ながら、これを再現するのは非常に困難です...

4

3 に答える 3

5

リンクしたの実装は、非ブロッキングであることが保証されている契約に違反しているため、実際にブロックされる可能性がありますKeepAliveStreamavailable()skip()

available() のコントラクトは、単一のノンブロッキングを保証しますskip():

この入力ストリームのメソッドの次の呼び出し元によってブロックされることなく、この入力ストリームから読み取る (またはスキップする) ことができる推定バイト数を返します。次の呼び出し元は、同じスレッドまたは別のスレッドである可能性があります。このバイト数の 1 回の読み取りまたはスキップはブロックされませんが、より少ないバイト数の読み取りまたはスキップが行われる可能性があります。

実装がskip()への単一の呼び出しごとに複数回呼び出す場所available():

    if (nskip <= available()) {
        long n = 0;
        // The loop below can iterate several times,
        // only the first call is guaranteed to be non-blocking. 
        while (n < nskip) { 
        nskip = nskip - n;
        n = skip(nskip);
        }

これは、アプリケーションがブロックされていることを証明するものではありませKeepAliveStreamInputStream。の実装InputStreamによっては、より強力なノンブロッキング保証を提供する可能性がありますが、それはおそらく疑わしいと思います。

編集: もう少し調査した結果、これはごく最近修正された JDK のバグです: https://bugs.openjdk.java.net/browse/JDK-8004863?page=com.atlassian.jira.plugin.system.issuetabpanels:すべてのタブパネル。バグ レポートは無限ループについて述べていますが、ブロッキングskip()も結果として発生する可能性があります。skip()この修正により、両方の問題が解決されたようです ( 1つしかありませんavailable()) 。

于 2013-01-30T16:02:32.793 に答える
2

これはキープアライブのサポートを目的としskip()ていると思います。close()

http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.htmlを参照してください。

Java SE 6 より前のバージョンでは、読み取るデータが少量以上残っているときにアプリケーションが HTTP InputStream を閉じると、キャッシュするのではなく、接続を閉じる必要がありました。Java SE 6 では、動作はバックグラウンド スレッドで接続から最大 512 キロバイトを読み取るため、接続を再利用できます。読み取られるデータの正確な量は、 http.KeepAlive.remainingDataシステム プロパティで設定できます。

そのため、キープアライブはhttp.KeepAlive.remainingData=0またはで効果的に無効にすることができますhttp.keepAlive=falseただし、常に同じhttp://t.coホストにアドレス指定すると、パフォーマンスに悪影響を与える可能性があります。

@artbristol が示唆したように、ここでは GET の代わりに HEAD を使用することが望ましい解決策のようです。

于 2013-01-17T14:28:50.743 に答える
0

「HEAD」リクエストを作成しようとしたときに、同様の問題に直面していました。それを修正するために、「HEAD」メソッドを削除しました。これは、単に URL を ping したかったからです。

于 2014-11-02T10:06:46.420 に答える