Runtime: シングルトンHttpClientはDNSの変更を尊重しません

作成日 2016年08月29日  ·  77コメント  ·  ソース: dotnet/runtime

次の投稿で説明されているように: httpHttpClientインスタンスの保持を開始すると、次のようになります。フェイルオーバーシナリオの場合、クライアントがDNSレコードの更新を尊重しないという問題が発生します。

根本的な問題は、デフォルト値のConnectionLeaseTimeout-1に設定されていることです。 クライアントの破棄時にのみ終了しますが、これは非常に非効率的です。

修正は、次のようにサービスポイントの値を更新することです。

var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar/baz/123?a=ab"));
sp.ConnectionLeaseTimeout = 60*1000; // 1 minute

残念ながら、今日.NETCoreでこれを行う方法はありません。

ServicePointManagerを.NETCoreに移行するか、同様の同等の機能を他の方法で有効にする必要があります。

area-System.Net.Http enhancement

最も参考になるコメント

これは絶対に問題です。 @darrelmillerによって提案されたソリューションは、ユースケースの理解が不足していることを示しているようです。

Azure Traffic Managerを使用して、インスタンスの正常性を評価します。 それらは短いTTLでDNS上で動作します。 ホストはそれが不健康であることを魔法のように知らず、C onnection:closeヘッダーの発行を開始します。 トラフィックマネージャは、単に異常な状態を検出し、それに応じてDNS解決を調整します。 設計上、これはクライアントとホストに対して完全に透過的です...現状では、トラフィックマネージャーによってDNSを(無意識のうちに)解決する静的HttpClientを持つアプリケーションは、以前に解決されたホストが異常になったときに新しいホストを受信することはありません。

netcore 1.1の時点で、私は現在、この正確な問題の解決策を探しています。 とにかく、私はそれを見ません。

全てのコメント77件

私はそれが同じ特性だとは思いません。 これは接続タイムアウトであり、ConnectionLeaseTimeoutではありません。

https://msdn.microsoft.com/en-us/library/system.net.servicepoint.connectionleasetimeout.aspxに記載されているように、一部のシナリオでは定期的に削除する必要があります。 ただし、.NETCoreの動作を変更する明確な方法はありません。

@onovotnyええ、それは明示的な接続を行っているように見える奇妙なことです特定の時間枠の後に

@darrelmillerなので、私の領域から抜け出していますが、

@ onovotnyServicePointManagerクラスはありません。これは、.NET Framework(デスクトップ)の問題のように聞こえます。 UserVoiceまたはConnectで問題を開いてください。

@onovotny元の問題は、私が理解しているように、サーバー側で、誰かがDNSエントリを更新して、ホストAへの今後のすべての要求が新しいIPアドレスに送信されるようにすることです。 私の意見では、そのDNSエントリが変更された場合、ホストAのアプリケーションは、今後のすべての応答でC onnection:closeヘッダーをクライアントに返すように指示する必要があります。 これにより、すべてのクライアントが接続を切断して新しい接続を確立するように強制されます。 解決策は、クライアントにタイムアウト値を設定しないことです。これにより、クライアントは、その「リース」タイムアウトの期限が切れたときにC onnection:closeを送信します。

これは絶対に問題です。 @darrelmillerによって提案されたソリューションは、ユースケースの理解が不足していることを示しているようです。

Azure Traffic Managerを使用して、インスタンスの正常性を評価します。 それらは短いTTLでDNS上で動作します。 ホストはそれが不健康であることを魔法のように知らず、C onnection:closeヘッダーの発行を開始します。 トラフィックマネージャは、単に異常な状態を検出し、それに応じてDNS解決を調整します。 設計上、これはクライアントとホストに対して完全に透過的です...現状では、トラフィックマネージャーによってDNSを(無意識のうちに)解決する静的HttpClientを持つアプリケーションは、以前に解決されたホストが異常になったときに新しいホストを受信することはありません。

netcore 1.1の時点で、私は現在、この正確な問題の解決策を探しています。 とにかく、私はそれを見ません。

@ kudoz83正解です。ホストは、トラフィックマネージャーがDNS解決を調整したことを魔法のように知らないので、なぜ私が言ったのですか。

そのDNSエントリが変更された場合、ホストA上のアプリケーションに通知する必要があります

c onnection:closeヘッダーを返す必要があることをオリジンサーバーに通知することが実用的でないと思われる場合は、いくつかの選択肢があります。

  • クライアントまたはミドルウェアからの接続を定期的に閉じ、それらの接続を冗長に再開するコストを支払います
  • クライアントにサービスをポーリングさせて、接続をリセットするタイミングを確認します。
  • DNSがいつ切り替えられたかを知るのに十分スマートなミドルウェアを使用してください。

そして、私に関しては、ユースケースを理解していません。 元の問題は、ユースケースが本番スロットとステージングスロットの切り替えに関連しており、ヘルスモニタリングとは関係がないというブログ投稿に基づいていました。 どちらのシナリオでも、DNS /スロットスイッチの通知を受信できるというメリットがあります。

失敗したホストに失敗したことを通知する方法はありませんか? 一般的に、これはある種のエラー状態にあることを意味します。 元の投稿では、フェイルオーバーのシナリオについて説明しています。

Azure TrafficManagerについてはこちらで説明していますhttps://docs.microsoft.com/en-us/azure/traffic-manager/traffic-manager-monitoring

これはDNSレベルで動作し、設計上、クライアントとホストに対して透過的です。 DNS解決のためにTrafficManagerを介して相互に通信するAzureインスタンスは、HttpClientのDNS更新にTTLを設定できずに問題が発生しています。

HttpClientの現在の動作方法は、Microsoft独自のAzureサービスの提供と矛盾しているように見えます。これが全体のポイントです。 言うまでもなく、同様のサービス(EG Amazon Route 53)はまったく同じように機能し、まったく同じ短いDNSTTL依存関係があります。

この問題の影響を受けているので、明らかにこのスレッドを見つけました。 DNSフェイルオーバーを確実に成功させるために、HttpClientを(パフォーマンスと複雑さを犠牲にして)任意に再作成する以外に、現在理想的な回避策はないように思われることを明確にしようとしています。

失敗したホストに失敗したことを通知する方法はありませんか?

「失敗した」ホストがコードを実行できなかった場合、オリジンサーバーがコードを返すことができないため、HTTPステータスコード503は必要ありません。 障害が発生したホストが通知を処理できないシナリオもありますが、その場合は、アクティブなTCP / IP接続を長時間開いたままにしない可能性があります。

アリが最初にこのトピックをTwitterで取り上げたとき、ブログ投稿を書く前に、クライアントがスワップアウトされたサーバーから要求を出し、応答を取得し続けるという事実について、彼は一致していました。

あなたの状況は異なり、オリジンサーバーが確実に接続を閉じることができるとは言えないことを理解しています。 私が理解していないのは、あなたの状況では、HttpMessageHandlerを使用して5XX応答を検出し、接続を閉じて要求を再試行できない理由です。 または、さらに簡単に、5XXステータスコードを返すたびにc oncection:closeを返すようにWebサーバーを説得します。

また、懸念している動作がHttpClientにないことにも注意してください。 これは、基盤となるHttpHandler内、またはその下にあり、使用されているHttpHandlerのさまざまな実装がいくつかあります。 私の意見では、現在の動作はHTTPが機能するように設計されている方法と一致しています。 接続が開いているときにDNS設定が更新されると問題が発生することに同意しますが、これは.netの問題ではありません。 これはHTTPアーキテクチャの問題です。 これは、.netがこれを問題から解放するために何かを行うことができなかったという意味ではありません。

私はあなたが解決策がどうあるべきだとあなたが信じているかについて興味があります。 フェイルオーバーが発生したかどうかに関係なく、接続を定期的にドロップするだけのConnectionLeaseTimeoutのようなものを再実装する必要があると思いますか? または、より良い解決策がありますか?

ごめん、

私は昨日聞こえたほど戦闘的に外れるべきではなかった。 悪い日を過ごしていた。

正直なところ、ConnectionLeaseTimeoutが内部でどのように機能するかはわかりません。 必要な機能は、クライアントが破棄されるまで以前のDNSルックアップをキャッシュするのではなく、新しい接続を確立する前に、新しいDNSルックアップを実行するようにHttpClientを構成できるようにすることです。 これには既存の接続を切断する必要はないと思います...

@ kudoz83心配ありません、私たちは皆そのような日があります:-)

私が理解していることから、ConnectionLeaseTimeoutは文字通り、クライアント側からのアイドル状態の接続を定期的に閉じるタイマーです。 これは、コアには存在しないServicePointManagerに実装されています。 これは、一定期間使用されなかった場合にのみ接続を閉じる「アイドル接続」タイムアウトとは異なります。

DNSクライアントキャッシュのフラッシュを可能にするWin32呼び出しが存在する可能性があります。 これにより、新しい接続でDNSルックアップが確実に実行されます。 ざっと見てみましたが見つかりませんでしたが、どこかにあると思います。

Windowsの.netコアでは、HTTP接続の管理は実際にはOSに任されています。 最も近いのは、Win32 API WinHttpOpen https://github.com/dotnet/corefx/blob/master/src/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs#の呼び出しです

DNSエントリに非常に低いTTLを設定するだけで、Azure Traffic Managerを入手する方が簡単ではないでしょうか。 存続期間の長い接続を気にしない場合は、TTLを低くすると、停止したサーバーに対して新しい接続がセットアップされる可能性を最小限に抑えることができます。

WinHttpHandlerの構築方法の利点は、OSアップデートでHTTP / 2のようなものを無料で入手できることです。 欠点は、マネージコードをあまり制御できないことです。

@darrelmiller

[ConnectionLeaseTimeout]は、コアには存在しないServicePointManagerに実装されています。

ServicePoint.ConnectionLeaseTimeoutが.Net Standard 2.0 / .Net Core1.2に戻ってきているようです。 (たとえば、WinHttpHandler接続がそれに準拠するかどうかはわかりませんが。)

ここにはいくつかの誤解があると思います。DNSTTLはHTTP / TCP接続の有効期間とは完全に無関係です。

新しいHTTP接続はDNSルックアップを実行し、返されたホストアドレスに接続します。 同じ単一の接続を介して複数のHTTP要求を行うことができますが、HTTP接続(および基盤となるTCP接続)を開くと、DNSルックアップは再度実行されません。

これは正しい動作であるため、数日間続く接続を介して数千の要求を行った場合でも、接続が変更されていないため、DNS解決は重要ではありません。 新しいHTTP接続を介して新しいHTTPリクエスト

このDNS更新はHttpClientとは関係ありません。これHttpClientHandler 、デフォルトでHttpWebRequestソケットスタックを使用します)、DNS解決はすべてそのスタックによって処理されます。 ServicePointManagerは、特定のホストへの接続であるスタック内のServicePointオブジェクトを管理します。 ServicePointManagerは、調整可能なDNSルックアップの最小限のクライアント側キャッシュがあります。

デフォルト設定は、効率を上げるために接続を無期限に開いたままにすることを目的としています。 接続されたホストが使用できなくなると、接続が自動的に切断され、新しいDNSルックアップで新しい接続が発生しますが、DNSTTLによって新しい接続が発生することはありません。 それが必要な場合は、アプリにコーディングする必要があります。

別のDNSルックアップを実行する場合は、クライアントの接続をリセットする必要があります。 TCP接続の最大有効期間を強制したり、 connection: closeヘッダーを送信したり、 HttpClientまたは基になるServicePointます。 Dns.GetHostAddressesを使用してDNS解決を行い、代わりにHTTPリクエストにIPアドレスを使用することもできます。

それがお役に立てば幸いです。もし私がこれをやめているなら、私に知らせてください。

@manigandham

ここにはいくつかの誤解があると思います。DNSTTLはHTTP / TCP接続の有効期間とは完全に無関係です。

はいDNSTTLはかなり無関係ですが、完全ではありません。

新しいHTTP接続はDNSルックアップを実行し、返されたホストアドレスに接続します。

はい、その通りです。 ただし、WindowsにはTTLを尊重するローカルDNSキャッシュがあります。 TTLが高く、DNSサーバーがDNSレコードを変更した場合、DNSレコードのクライアントキャッシュにより、古いIPアドレスに対して新しい接続が開かれます。 クライアントのDNSキャッシュをフラッシュすることは、私が知っている唯一の回避策です。

同じ単一の接続を介して複数のHTTP要求を行うことができますが、HTTP接続(および基盤となるTCP接続)を開くと、DNSルックアップは再度実行されません。

もう一度修正してください。 スワップアウトされたサーバーがまだ応答している本番/ステージングスワップでは、これにより多くの将来の要求が古いサーバーに送信されますが、これは悪いことです。 ただし、最新の会話は、サーバーに障害が発生し、TrafficManagerが新しいサーバーにスワップするシナリオに関するものです。 障害が発生したサーバーへの既存の接続はどうなりますか。 重大なハードウェア障害の場合は、接続が切断される可能性が高くなります。 これにより、クライアントは接続を再確立する必要があり、低TTLが役立つ場合があります。 ただし、サーバーの障害がサーバーを破壊しないと、それは単に5XX応答を返す場合、サーバーは、C返すようにするために、それは有用であろうonnection:近い将来の要求を確実にするためには、うまくいけば、新たな作業に、新しい接続を説明しますサーバ

このDNS更新は、デフォルトでHttpClientHandlerを使用するHttpClientとは関係ありません(それ自体がHttpWebRequestソケットスタックを使用します)

はいといいえ。 .Netコアでは、HttpClientHandlerが書き直され、HttpWebRequestを使用しなくなったため、ServicePointManagerを使用しなくなりました。 したがって、この問題の理由。

IISを詳しく調べたところ、 connection:closeヘッダーを使用して5XX応答を取得する方法がないようです。 その魔法を実行するには、HttpModuleを実装する必要があります。

参考までに、この問題と暫定的な解決策をカバーする関連記事を以下に示します。 HttpClientに注意してください

いずれかのServicePointManagerを.NETCoreに移行する必要があります

プラットフォームパリティ用にServicePointManagerとHttpWebRequestを導入しましたが、基盤となるスタックが高度なコントロールノブを公開しないWinHTTPまたはCurlに解決されるため、.NetCoreのほとんどの機能をサポートしていません。

ServicePointManagerを使用してHttpClientを制御することは、実際にはHttpWebRequestと.NetFrameworkでのマネージドHTTPスタックの使用に対する副作用です。 WinHttpHandler / CurlHandlerは.NetCoreで使用されるため、SPMはHttpClientインスタンスを制御できません。

または同様の同等の機能を他の方法で有効にする必要があります。

WinHTTPチームの答えは次のとおりです。

  1. セキュリティ上の理由から、クライアントがリクエストを送信し続け、_アクティブな接続が存在する_間、WinHTTPは引き続き宛先IPを使用します。 これらの接続がアクティブなままでいる時間を制限する方法はありません。
  2. リモートエンドポイントへのすべての接続がサーバーによって閉じられた場合、_new_接続はDNSにクエリを実行し、新しいIPに送信されます。

クライアントを次のIPに強制するための唯一の実行可能な解決策は、サーバーが新しい接続を拒否し、既存の接続を閉じることを確認することです(このスレッドですでに説明されています)。
これは、 ATMのドキュメントから理解していることと同じです。検出は4回失敗したGET要求(200以外の応答)に基づいて行われ、その場合、接続はサーバーによって閉じられます。

それ自体がDNSをキャッシュしている他のネットワークアプライアンスデバイス(ホームルーター/ APなど)を考えると、低遅延、高可用性の手法のソリューションとしてDNSフェイルオーバーはお勧めしません。 それが必要な場合、制御されたフェイルオーバーの推奨事項は、適切にドレインを停止する方法を知っている専用のLB(ハードウェアまたはソフトウェア)を用意することです。

現状では、トラフィックマネージャーによってDNSを(無意識のうちに)解決する静的なHttpClientを使用するアプリケーションは、以前に解決されたホストが異常になったときに、新しいホストを受信することはありません。

上記のように、不健全とは、DNS TTLの有効期限が切れていることを確認しているときに、サーバーがHTTPエラーコードを返し、TCP接続を閉じることを意味する場合、これは確かにバグです。

シナリオを詳しく説明してください。

  1. フェイルオーバー中、クライアントマシンのDNSキャッシュとTTLは何ですか。 ipconfig /showdnsnslookup -type=soa <domain_name>を使用して、マシンがTTLと見なすものと信頼できるNSのTTLを比較できます。
  2. リモートサーバーへのアクティブな接続はありますか? netstat /a /n /bを使用して、接続とそれらを所有するプロセスを確認できます。
  3. 再現を作成すると、非常に役立ちます。たとえば、.Net Core / Frameworkのバージョン、クライアント/サーバーのコード、ネットワークトレース、関連するリモートDNS LB / TrafficManagerなどの利用可能な情報を添付してください。

マシンに接続してもTCP / HTTP接続が引き続き機能する場合、ある時点でサーバーが適切でなくなったと想定する必要があるのはなぜですか。
Azure側には改善の余地があると思います。 たとえば、マシンが異常な環境やスワッピング環境である場合、既存の接続を閉じることができます。

または単に...基礎となるServicePoint破棄します

@manigandhamどうやって? これは使い捨てではなく、私が見ることができる唯一の関連するメソッドはCloseConnectionGroupこれは、グループ名が実装の詳細(現在はハンドラーのハッシュ)であるため、実際には使用できません。 また、 HttpClientがServicePointを使用しているときに(特に同時に)ServicePointを破棄しようとすると、どうなるかわかりません。

@ohadschn

.NET Core 2.0は、 ServicePointクラスと機能を復活させます: https

接続リースタイムアウトの使用に戻って、アクティブな接続がリセットされる前の最大時間枠を設定できます。

HttpClientリクエストの進行中に接続を閉じることについてエキゾチックなことは何もありません。これは、インターネットを閲覧しているときに接続が失われるのと同じです。 リクエストが成功しないか、成功して応答が返されません。どちらもhttpエラーが返されるはずであり、アプリケーションでそれらを処理する必要があります。

接続リースタイムアウト設定を使用すると、リクエストが送信された後にのみこれが実行され、制限時間内に次のリクエストの前にリセットされるようにすることができます。

.NET Core 2.0は、ServicePointのクラスと機能を復活させます: https ://docs.microsoft.com/en-us/dotnet/api/system.net.servicepoint?view = netcore-2.0
接続リースタイムアウトの使用に戻って、アクティブな接続がリセットされる前の最大時間枠を設定できます。

実際には、機能が完全に復活するわけではありません。 APIサーフェスが戻ってきました。 ただし、HTTPスタックはそのオブジェクトモデルを使用しないため、基本的には何もしません。

cc: @stephentoub @geoffkizer

このスレッドの参加者の多くは、この主題の「夢のチーム」のようなものだと思いますが、今日のベストプラクティスがどのようなものであるかについてコンセンサスが得られているかどうかはまだわかりません。 いくつかの重要な点について、このグループから決定的な答えを得るのは素晴らしいことです。

多くのプラットフォームを対象とする汎用HTTPライブラリがあり、そのすべてがServicePointManagerをサポートしているわけではなく、デフォルトでベストプラクティスに従ってHttpClientインスタンスをスマートに管理する動作を提供しようとしているとします。 DNSの変更/接続:クローズヘッダーに関してホストが適切に動作しているかどうかは言うまでもなく、どのホストが呼び出されるかは事前に

  1. HttpClientインスタンスを最適に管理するにはどうすればよいですか? エンドポイントごとに1つ? ホスト/スキーマ/ポートごとに1つ? 文字通り合計1つのシングルトン? (消費者はデフォルトのヘッダーなどを利用したいと思うかもしれないので、私は疑っています。)

  2. このDNSの問題に最も効果的に対処するにはどうすればよいですか? 1分後にインスタンスを破棄しますか? それを「非常にありそうもない」ものとして扱い、デフォルトで廃棄動作を提供しませんか?

(ところで、架空の状況ではありません。私は現在、これらの正確な質問に取り組んでいます。)

ありがとう!

私はドリームチームのトライアウトに招待されることすらありませんが、あなたのシングルトンHttpClientコメントに関して1つの洞察があります。 まず、いつでもSendAsyncメソッドを使用してさまざまなヘッダーとアドレスを指定できるため、 HttpClientと同様の共有データ(デフォルトのヘッダー、ベースアドレスなど)を提供するクラスでそれをラップできます。同じ単一のクライアントを呼び出すことになります。 しかし、さらに良いことに、すべて同じHttpClientHandler共有する複数のHttpClientインスタンスを作成できます: https

DNSの問題に関しては、ここでのコンセンサスは次のように思われます。

クライアントを次のIPに強制するための唯一の実行可能な解決策は、サーバーが新しい接続を拒否し、既存の接続を閉じることを確認することです。

これが実行可能でない場合、Darrel Millerはここでさらにいくつかのオプションを提案しました: https ://github.com/dotnet/corefx/issues/11224#issuecomment-270498159。

@ohadschnありがとう、そしてうまくいけば私はあまり話題から外れていませんが、複数のホスト間で同じHttpClient(またはHttpClientHandler)を共有することには_利点_はありますか? 私は間違っているかもしれませんが、新しいホストは新しいDNSルックアップ、新しい接続、新しいソケットを意味するように思えます。 TIME_WAITのソケットを再利用して、別のホストに再利用できますか? 私はそうは思わなかったが、私たちは間違いなく私の専門知識の限界に達している。 可能であれば、これは、複数のホストでHttpClientシングルトンを使用することの利点でしょうか。

DNSのアドバイスに関しては、コンセンサスが最も不足しているところだと感じました。当然のことながらそうです。 その側で何が起こっているかを制御できない場合、サーバーが正しく動作することを保証することは実行可能ではありません。 インスタンスが定期的に破棄されるようなものを設計することができます。 問題は、ほとんどの場合、それがトレードオフの価値があるかどうかです。 判断を下すのは私次第だと思います。 :)

私にとって、同じHttpClient [ Handler ]を共有することの最も明白な利点は、単純さです。 ホストとクライアント間のマッピングなどを維持する必要はありません。 私はTIME_WAITについては知りませんが、実際には私の専門知識でもありません。

DNSに関して、サーバーを制御しない場合の1つの可能なアプローチについて言及しました。 また、DNSを定期的に照会し、実際の変更が発生した場合にのみインスタンスを破棄するメカニズムを実装することもできます...

サーバー側で何が起こるかを指定する与えられた答えは理想的な世界では正確ですが、現実の世界では、開発者は通信しているサーバー側のスタック全体を制御できないことがよくあります。 その点で、私はConnectionLeaseTimeoutの概念が本当に好きです。それは、ジョンとジェーン・ドーベロパーの日常生活の現実への譲歩だからです。
誰かが.NETCoreでそのようなタイムアウトを強制するミドルウェアを書くことを提案しました。 NodaTimeとSystem.Collections.Immutableに基づくこのクラスは仕事をしますか?

    public class ConnectionLeaseTimeoutHandler : DelegatingHandler
    {
        private static string GetConnectionKey(Uri requestUri) => $"{requestUri.Scheme}://{requestUri.Host}:{requestUri.Port}";

        private readonly IClock clock;
        private readonly Duration leaseTimeout;
        private ImmutableDictionary<string, Instant> connectionLeases = ImmutableDictionary<string, Instant>.Empty;

        public ConnectionLeaseTimeoutHandler(HttpMessageHandler innerHandler, IClock clock, Duration leaseTimeout)
            : base(innerHandler)
        {
            this.clock = clock;
            this.leaseTimeout = leaseTimeout;
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            string key = GetConnectionKey(request.RequestUri);
            Instant now = clock.GetCurrentInstant();

            Instant leaseStart = ImmutableInterlocked.GetOrAdd(ref connectionLeases, key, now);

            if (now - leaseStart > leaseTimeout)
            {
                request.Headers.ConnectionClose = true;
                ImmutableInterlocked.TryRemove(ref connectionLeases, key, out leaseStart);
            }

            return base.SendAsync(request, cancellationToken);
        }
    }

@snboisenそれは正しいように見えると思いますが、私をそれに拘束しないでください。 :)

私にとってより大きな問題は、 Connection: closeヘッダーを送信することが実行可能な解決策でさえあるかどうかです。 このスレッドを促したブログ投稿のコメント@darrelmillerが言ったことをここで繰り返す価値があります。

ConnectionLeaseTimeoutは奇妙な獣であり、Orenが今日それについて言及するまで私は気づいていませんでした。 これにより、リースの期限が切れた後、送信要求にC onnection:Closeヘッダーが含まれるようになり、サーバーが応答を送信した後に接続が終了します。 クライアントがアクティブにリクエストを行っているときに接続を強制的に閉じるのは奇妙な方法であるため、これがデフォルトに無限に設定されている理由は理にかなっています。

私はダレルの意見を尊重し、それはこのアプローチ全体をかなりハックなように見せます。 一方、サーバー側で何が起こっているのかを制御できない開発者は何をすべきかということに同意します。 それが_時々_機能する場合、多分それは私たちが持っている最良のオプションですか? HttpClientを定期的に破棄したり、DSNルックアップを定期的に実行したりするよりも、確かに「安価」です。

この問題に対して開いているAzureチケットはありますか?
私の見解では、Azureのトラフィックマネージャーでこれを実装する方法を回避しようとしているようです。
私はAzureユーザーではないので、遠くから見て、フープを飛び越えようとは思わない。

@tmdsまだ見たことがありませんが、他の人が言及しているように、これはAzure Traffic ManagerだけではありませんAppServiceがあります。 そして、他のクラウドプロバイダーもこのスワッピングメカニズムに似たものを提供しているに違いありません。

@snboisen AWSや他のクラウドプロバイダー(どちらですか?)もDNSタイムアウトを使用してこれらの機能を実装していると言っていますか?

@tmds AWSの場合、はい、少なくともフェイルオーバーの場合: https//aws.amazon.com/route53/faqs/#adjust_ttl_to_use_failover
他のクラウドプロバイダーについては知りませんが、一部のプロバイダーも同様に知っているようです。

@tmds AWSの場合、はい、少なくともフェイルオーバーの場合: https//aws.amazon.com/route53/faqs/#adjust_ttl_to_use_failover
他のクラウドプロバイダーについては知りませんが、一部のプロバイダーも同様に知っているようです。

ここには2つの問題のあるシナリオがあります。

  • クライアントは、もう存在しないサーバーに接続されています
  • クライアントは、古いバージョンのソフトウェアを実行しているサーバーに接続されています

DNSフェイルオーバーは最初のものに関連しています。 到達不能なサーバーをDNSから削除するのが適切です(AWS、Azureなど)。
クライアントでそれを検出する適切な方法は、タイムアウトメカニズムです。 HttpClientにそのようなプロパティがあるかどうかはわかりません(確立された接続での最大応答タイムアウト)。
プロトコルレベルでは、新しいWebSocketおよびhttp2プロトコルは、接続の活性をチェックするためのping / pongメッセージを定義します。

Azureで問題を開くことを提案するときは、2番目のシナリオです。
クライアントが機能している接続を閉じる必要がある理由はありません。 サーバーが適切でなくなった場合は、クライアントを切断し、サーバーにアクセスできないようにする必要があります。
AWS Blue / Greenのデプロイに関する素晴らしいドキュメントを見つけました: https
いくつかのテクニックがリストされています。 DNSを使用する場合は、_DNSTTLの複雑さ_について説明します。 ロードバランサーを使用する場合_DNSの複雑さはありません_。

@tmdsリンクをありがとう、それは非常に興味深いです。

DNS TTLに依存するのと、ロードバランサーを使用するのがどれほど一般的か疑問に思います。 後者は、一般的にはるかに信頼性の高いソリューションのようです。

HttpClient介して通信するAzure App Servicesで2つの異なるAPIアプリを操作するときに、同様の動作が発生しました。 負荷テストで約250のリクエストが発生し、タイムアウトが発生し始め、「接続を確立できませんでした」 HttpRequestException -同時ユーザーかシーケンシャルリクエストかは関係ありませんでした。

usingブロックに切り替えて、各リクエストでHttpClientが確実に処理されるようにしたとき、これらの例外は発生しませんでした。 シングルトンからusing構文へのパフォーマンスへの影響は、あったとしてもごくわずか

usingブロックに切り替えて、各リクエストでHttpClientが確実に処理されるようにしたとき、これらの例外は発生しませんでした。 シングルトンからusing構文へのパフォーマンスへの影響は、あったとしてもごくわずか

それは必ずしもパフォーマンスに関するものではなく、TCP接続をリークせずにSocketExceptionを取得することです。

MSDNから:

HttpClientは、一度インスタンス化され、アプリケーションの存続期間を通じて再利用されることを目的としています。 特にサーバーアプリケーションでは、リクエストごとに新しいHttpClientインスタンスを作成すると、高負荷で使用可能なソケットの数が使い果たされます。 これにより、 SocketExceptionエラーが発生します。

また、 https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/を参照して

@khellangリンクをありがとう(そしてSocketExceptionの説明、私は間違いなくそこで誤解を持っていました)-私はその投稿とこれとまったく同じことを概説している他の3つか4つの投稿に出くわしました。

単一のインスタンス化が意図されていることは理解していますが、Azure App Servicesで使用している場合、かなり適度な負荷(上記の250のシーケンシャルリクエスト)で常にHttpRequestExceptionsに到達しています。 別の解決策がない限り、 HttpClientを破棄することは、これらの一貫した例外に遭遇することを回避するために実装しなければならなかった回避策です。

私は、すべての保留中の要求が明示的に処分取消、「より正確」ソリューションは、これらの例外をキャッチするだろうと仮定していますHttpClient 、再インスタンス化にHttpClient 、そしてすべての要求を再試行してください。

何かのための少し混乱のように思えます

一度インスタンス化され、アプリケーションの存続期間を通じて再利用されることを目的としています

よりクリーンなソリューションが欲しいのですが、それはコードに問題はなく、フレームワークの制限のようです。 私は間違っていることをとてもうれしく思います。私はこれを意図したとおりに使用し、これらの問題に遭遇しないようにする方法についてのガイダンスが必要です。

@snboisen ConnectionLeaseTimeoutHandlerをテストしましたが、複数の接続では機能しないことがわかりました。 「クローズ」は、リースタイムアウトが発生するたびに、1つの接続に対してのみ発行されますが、要求を並行して送信する場合、単一のHttpClientインスタンスで複数の接続が開かれる可能性があります。 「close」が送信された後、フィドラー(DNSスイッチをチェックするためにエラーを生成するために不正なIPアドレスを使用します)に表示されるものから、一部のリクエストは新しいIPを使用し、一部のリクエストは古いIPを使用します。 私の場合、接続制限は2で、両方の接続を更新するには、接続リースタイムアウトを数回繰り返す必要があります。 実稼働サーバーからの同時接続が増えると、明らかにさらに悪化しますが、しばらくすると最終的には切り替わる可能性があります。

HttpClientインスタンスで使用されているすべての個々の接続に「クローズ」を発行する方法がわかりません。 これは確かに理想的ですが、これから行うのは、HttpClientのGetOrCreate()ファクトリメソッドを使用し、120秒ごとに新しいHttpClientインスタンスを作成して、すべての新しい接続が作成されるようにすることです。 接続を閉じるためにすべてのリクエストが終了したら、古いHttpClientインスタンスを破棄する方法がわかりませんが、リクエストごとに新しいHttpClientインスタンスを作成することは問題ではなかったため、.NETがそれを処理すると思います。

ちなみに、更新はクライアントで行う必要があると思います。 サーバーは、DNSの変更やミドルウェアの通信を担当することはできません。 DNSの目的は、このような責任を相殺して、IPにわかりやすい名前を付けるだけでなく、クライアントとサーバー間の調整なしでいつでもIPを変更できるようにすることです。 完璧なユースケースは、AWS ElasticIPアドレスです。 変更はDNSにのみ反映されます。 AWSは、ec2インスタンスで実行されているWebサーバーから「close」ヘッダーを送信することを認識しません。 AWSは、管理しているec2インスタンスの上にあるすべてのWebサーバーを認識するわけではありません。 「閉じる」ヘッダーを送信しない古いサーバーのシナリオは、まったく同じソフトウェアが新しいサーバーに展開され、すべてのトラフィックがDNSによって切り替えられる新しいサーバーの展開です。 特に元に戻す必要がある場合は、古いサーバーでは何も変更されません。 DNSを使用すると、すべてを1か所でスムーズに変更できます。

ここで説明するより大きな問題に関係なく、ConnectionLeaseTimeoutはコアに実装されていますか? もしそうなら、なぜこの問題は解決されないのですか? 元の投稿とまったく同じように例をコーディングできましたが、今は問題ありません。 それが私の接続DNSの問題を解決するかどうかにかかわらず、それはコアに実装されているようです。

ServicePointMananager.ConnectionLeaseTimeoutは.NETCoreには実装されていません。 .NETCoreでこのプロパティを使用することはできません。 .NETFrameworkでのみ機能します。 また、デフォルトでは、このプロパティは.NETFrameworkで「オフ」に設定されています。

単一のインスタンス化が意図されていることは理解していますが、Azure App Servicesで使用している場合、かなり適度な負荷(上記の250のシーケンシャルリクエスト)で常にHttpRequestExceptionsが発生します。 別の解決策がない限り、HttpClientを破棄することは、これらの一貫した例外に遭遇することを回避するために実装しなければならなかった回避策です。

アプリケーション内のすべてのhttpリクエストが同じHttpClientインスタンスを再利用できるようにするカスタムAPIでHttpClientをラップすることで、ソケットの枯渇の問題を解決することに成功しました(これだけではありません) 、 RestSharpが、これも利点の1つです)。 これを取得するのはlock{}背後にあります。これは、60分ごとに新しいHttpClientを作成し、代わりにそれを返すためです。 これにより、閉じられていない古い接続を再利用せずに、Webアプリケーションから大量のhttpリクエストを実行できるようになりました(高度な統合が行われているため)。 私たちはこれを約1年間成功させてきました(ソケットの枯渇の問題にぶつかった後)。

これには(特にすべてのロックが原因で)パフォーマンスへの影響がまだあると確信していますが、ソケットの枯渇のためにすべてが死ぬよりはましです。

@KallDrexxなぜ「グローバル」インスタンスをロックする必要があるのですか? 素朴に私はそれを静的に保存し、60分ごとに新しいものと交換します。 それだけでは不十分でしょうか?

HttpClientを取得するためのコードは次のとおりです。

`` `c#
var timeSinceCreated = DateTime.UtcNow-_lastCreatedAt;
if(timeSinceCreated.TotalSeconds> SecondsToRecreateClient)
{{
ロック(南京錠)
{{
timeSinceCreated = DateTime.UtcNow-_lastCreatedAt;
if(timeSinceCreated.TotalSeconds> SecondsToRecreateClient)
{{
_currentClient = new HttpClient();
_lastCreatedAt = DateTime.UtcNow;
}
}
}

        return _currentClient;

`` `

したがって、複数のスレッドが新しいHttpApiClient同時に作成しようとしないように、ロックは1分に1回だけ発生すると思います。 もっと積極的にロックしていると思いました。

なるほど、便利な理由です。
または、タイマーベース(非同期)の更新を使用するか、複数のスレッドを並行して作成できるようにする(スレッドセーフでないレイジー初期化スタイル)か、異なる同期メカニズムを使用して、作成時に他のスレッドをブロックしないようにすることができます(それらは古いものを再利用できます)。

なるほど、便利な理由です。

利便性として分類するかどうかはわかりません。

あまりにも多くのスレッドが同時に新しいHttpClientを作成しようとすると、特に古いスレッドでその1分間にソケットが適切にクリーンアップされていない場合、毎分作成されるスレッドが多すぎるためにソケットが使い果たされるリスクがあります。 (カスケードを引き起こします)。

さらに、 DateTime更新が不可分操作であるかどうかわからないため(おそらくそうではない)、一部のスレッドは_lastCreatedAt値を読み取る可能性があるため、ある程度のロックが必要です。更新操作の途中。

また、すべてのスレッドが古いクライアントを再利用すると同時に、1つのスレッドで新しいクライアントを作成するには、多くの複雑なロジックが必要になるため、1つのスレッドで新しいクライアントが正常に作成されることが保証されます。 言うまでもなく、別のスレッドがインスタンス化している間、あるスレッドが_currentClientを返さないことを保証できないため、ロックが必要です。

未知数が多すぎて複雑さが増し、本番環境でそれを行うリスクがあります。

@KallDrexx

  • ReaderWriterLockSlim方が使用に適しているようです。
  • 真のx64を実行している場合、 DateTime割り当てはアトミックである必要があります(単一のulongフィールドが含まれているため)。 ロックの外側でDateTime値を監視しているため、そうでない場合、現在のコードはスレッドセーフではないことに注意してください。
  • @karelzによって提案されたタイマーベースのアプローチはDateTimeをまったく必要としません...タイマーは単にHttpClient切り替えるでしょう。 ただし、 HttpClientフィールドをメモリバリアで囲む必要があります( volatileInterlocked.Exchangeなど)。

ReaderWriterLockSlimの方が使用に適しているようです。

きちんとした私はそのクラスについて知りませんでした! ただし、これについて言及したことで、 SemaphoreSlimも使用することを考えました。これは、非同期/待機に適しているため( ReaderWriterLockSlimはそうではないように見えます)、スレッドの競合が発生する可能性があります。新しいHttpClientが必要です。 次に、 ReaderWriterLockSlimは、あなたが正しく指摘した潜在的なDateTime非原子的な問題をより適切に処理するので、これについて考えなければなりません。ありがとうございます。

@karelzによって提案されたタイマーベースのアプローチはDateTimeをまったく必要としません...タイマーは単にHttpClientを切り替えるだけです。 ただし、HttpClientフィールドをメモリバリア(volatile、Interlocked.Exchangeなど)で囲む必要があります。

ああ、私は彼の意味を読み間違えました。 それは理にかなっていますが、それを適切に行うには、メモリバリアメカニズムに精通する必要があります。

ありがとう。

編集:追加するだけで、これは、これを処理するために何かを組み込む必要があると思う理由です。したがって、 HttpClientすべてのコンシューマーは、これを再実装/実行する必要はありません。

.net Core 2.0でもまだ問題があるのでしょうか? DNSの変更についてまだ心配する必要がありますか?

.net Core 2.0でもまだ問題があるのでしょうか? DNSの変更についてまだ心配する必要がありますか?

私の理解では、これはDns自体の問題ではなく、 HttpClientが(ソケットの枯渇を防ぐために)既存の接続を再利用しようとするため、DNSが変更された場合でも、古い接続に接続していることになります。以前の接続を経由しているため、サーバー。

@KallDrexx 、それはそれがまだ青緑色の展開の問題を抱えていることを意味しますか?

私の理解から@withinoneyearはい。 HttpClientを作成し、それをProduction Greenに対して利用する場合、ProductionをBlueにスワップすると、接続が閉じられるまで、 HttpClientはGreenへの接続を開いたままになります。

これは古いことは知っていますが、時間間隔の測定にDateTime.UtcNowを使用するべきではないと思います。 Stopwatch.GetTimestampのようなモノニックタイムソースを使用します。 このようにして、オペレーターによる現地時間の調整や時間の同期の影響を受けません。 QPC値を元に戻すバグのあるチップセットがいくつかありましたが、それらの多くが現在使用されているとは思いません。

var sp = ServicePointManager.FindServicePoint(new Uri( "http://foo.bar/baz/123?a=ab"));
sp.ConnectionLeaseTimeout = 60 * 1000; // 1分

このコードはコンストラクターに配置されますか?

@ whynotme8998 @lundcm私は思いません。 HttpClientがシングルトンであり、複数のエンポイントにアクセスする場合、コンストラクターはそれを配置するための正しい場所ではありません。
この実装を確認してください。 また、プロジェクトのREADMEには、問題を説明するブログ投稿へのリンクがあります。 その記事もこのスレッドを指しています。

誰かがConnectionLeaseTimeout意味を正確に理解するのを手伝ってもらえますか?

私のアプリで1分間に設定した場合、それは接続リースが毎分タイムアウトすることを意味しますか? それは私のバックエンドと話すことにとって正確にはどういう意味ですか?

人々がバックエンドでのDNS更新について話しているのを見ますか? 標準のAzureWebアプリでは、ASP.NETバックエンドを公開するとDNSが更新されますか?

1分間のタイムアウトにより、DNSが更新されたにもかかわらず、タイムアウトがまだ発生していないウィンドウが残る可能性はありませんか?

編集。 これを実現することは、.NET Standard2.0では機能しません。 回避策は何ですか?

そのhttps://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcoreを待つか、自分でそのビットを実装します。

ConnectionLeaseTimeoutの変更は、.netstandard2.0を使用するXamarinでは機能しませんでした!!!

@paradisehuman私はあなたがServicePoint.ConnectionLeaseTimeoutを意味すると思います。 AFAIKは、.NETCoreでも機能しません。 .NET Core 2.1では、 SocketsHttpHandler.PooledConnectionLifetimeを導入しました。 また、ASP.NETの新しいHttpClientFactoryは両方を活用でき、さらに多くのことを実行できます。
Mono / Xamarinには独自のネットワークスタック実装があります(ネットワーク用にCoreFXソースコードを使用しません)。そのため、詳細をリポジトリにバグを報告するのが最善です。

残念ながら、今日.NETCoreでこれを行う方法はありません。 ServicePointManagerを.NETCoreに移行するか、同様の同等の機能を他の方法で有効にする必要があります。

@ onovotny、.NET Core 2.1のSocketsHttpHandlerは、PooledConnectionLifetimeを公開します。これは、ハンドラーレベルでConnectionLeaseTimeoutと同様の目的を果たします。 この時点で対処されているこの問題を検討できますか?

@stephentoubは今これを見ているだけです、はい、PooledConnectionLifetimeはそれに対処する必要があると思います。 何かがまだ必要な場合、コミュニティは別の問題を開くことができます。

@onovotny @karelz @stephentoub

これが元のシングルトンHttpClient / DNS問題を解決するための最善の方法の正確な要約であることに皆さんは同意しますか?

  • .NET Framework 2.0以降では、 ServicePoint.ConnectionLeaseTimeoutます。

  • .NET Core 2.1では、 SocketsHttpHandler.PooledConnectionLifetimeます。

  • 他のすべてのプラットフォーム/ターゲット/バージョンについては、信頼できる解決策/ハック/回避策がないため、わざわざ試してはいけません。

このスレッドや他のスレッド(.NET Standard 2.0のプラットフォーム間でConnectionLeaseTimeoutが確実に機能しないなど)から学習を続けると、ある意味でライブラリからこれを解決しようとしている設計図に戻ります。可能な限り多くのプラットフォーム/バージョンで動作します。 プラットフォームスニッフィングは問題ありません。 定期的にHttpClient破棄/再作成することは、それが効果的である場合は問題ありません。 connection:closeヘッダーを定期的にホストに送信するという最初の試みで、間違いを犯したことは間違いありません。 アドバイスをよろしくお願いします!

@tmenier他のすべてのプラットフォーム/ターゲット/バージョンについては、信頼できる解決策/ハック/回避策がないため、わざわざ試してはいけません。

インスタンスはいつでも定期的にリサイクルできます(たとえば、新しいインスタンスを作成し、他のユーザーが使用する静的変数に設定するだけです)。
それがHttpClientFactoryが行うことです。 代わりにHttpClientFactoryを使用することを選択できます。

@karelzありがとう、これは私に前進の道を提供すると思います。 おそらく、プラットフォームのスニッフィングをスキップして、どこでもインスタンスをリサイクルする必要がありますか、または利用可能な場合は他のアプローチを使用することをお勧めします(オーバーヘッドなどを回避するため)?

トリッキーな点は、リサイクルされたインスタンスをいつ安全に破棄できるかを知っていることかもしれませんが(同時実行/保留中の要求について心配する必要があります)、 HttpClientFactoryがそれをどのように処理するかを確認できます。 再度、感謝します。

@tmenier HttpClient呼び出しごとにインスタンスをリサイクルする必要はありません。これは、tcpソケットの枯渇につながるためです。 リサイクルを管理する必要があります(タイムアウトするか、期限切れになる可能性のあるHttpClientのプールを維持します。

@KallDrexxそうですね、数分ごとなど、シングルトン(または「疑似」シングルトン)を_定期的に_リサイクルすることについて話しているだけです。 保留中のリクエストがないと判断されたら、プールの管理と、デッドインスタンスのある種の遅延処理が必要になると思います。

申し訳ありませんが、リサイクルの意味を誤解しました。

古いものを処分する必要はありません。 GCにそれらを収集させます。
コードは、タイマーに基づいて定期的に新しい「疑似」シングルトンを設定するのと同じくらい単純である必要があります。 これ以上何もない。

高性能のシナリオでhttp接続が不足すると、それらを破棄しないと速度が低下する可能性があることがわかりました。

@tmenierでのSocketsHttpHandler.PooledConnectionLifetimeの使用について.NETCore 2.1では、 https://github.com/dotnet/wcf/blob/master/src/System.Private.ServiceModel/src/System/に完全にカプセル化されたHttpClientHandlerの作成

@edavedian dotnet / wcfリポジトリで質問することをお勧めします。 cc @mconnew @Lxiamail

基盤となるスタックは、高度なコントロールノブを公開しないWinHTTPまたはCurlのいずれかに解決されます

.Net Coreには、ソケット上で機能するHTTPクライアントがありますか?

.Net Coreには、ソケット上で機能するHTTPクライアントがありますか?

はい、SocketsHttpHandlerです。2.1以降はデフォルトです。

HttpClient 、私が信じているRestClientと同様に、ヒットしたURIをキャッシュしますか? もしそうなら、それがこれをサポートしているバージョン(.NET Core 3.0 / .NET Framework 3.0など)を教えてください。

プロジェクトが.NetCore 2.1以降のアプリの場合、キャッシュはRestClientのみ実行されることがわかりますか?

HttpClientは、私が信じているRestClientと同様に、ヒットしたURIをキャッシュしますか?

HttpClientはキャッシュを行いません。

@SpiritBob参照しているキャッシュの形式が明確ではありません。 この問題は解決されました。 質問のある新しい問題を開いていただければ、回答を見つけるお手伝いをいたします。

@scalablecorydavidshは私が考えていたもので応答したと思います。 ありがとう!

このスレッドを読み終えたところですが、対処されていないユースケースがあったようです。

HttpClientFactoryを使用する場合、結果のHttpClient自動的に接続を閉じ、内部SocketExceptionを含むHttpRequestExceptionを受信すると、新しいDNSルックアップを実行します。ホストは無効になり、DNSが変更された可能性があります(エラーコード11001-そのようなホストは不明です)?

そうでない場合、DNSレコードでTTLが期限切れになる前にHttpClient DNSルックアップを実行させるための回避策は何ですか?

現在、カスタムHttpMessageHandler HttpClientFactoryを使用しています( AddHttpClient<T, U>().ConfigurePrimaryHttpMessageHandler(...)を使用した依存性注入によって構成されています)。TTLが期限切れになる前にDNSレコードが変更されると、エラーが発生します。 具体的には、Azure BlobStorageに大量のアップロードを行うときに発生します。

このページは役に立ちましたか?
0 / 5 - 0 評価