Go http client接続池が多重化されていない問題
http clientの戻り値が空ではなく、reponse headerのみを読みますが、bodyの内容を読まないで、reponse.Body.Closeを実行します.接続は自動的に閉じられ、多重化されません.
テストコードは以下の通りです.
ところで、golang httpclientの注意すべきところは本当に多いです.、例えば、レスリング・Body.Close()がない場合、いくつかのシーンでは、persistConのwriteLoopが漏れることがあります. headerとbodyのいずれにも関わらず、漏れの原因となる接続が干満の接続池になり、後の要求は コンテキスト
いくつかの業務システムが狂ったように各エリアの異なるk 8 sクラスタを呼び出して、機械室を跨いでの遅延を減らすために、新しい古いk 8 sクラスタappiに対応して、k 8 s appi-serverの負荷を減らすために、k 8 scacheサービスを開発しました.配備運転後にこのサービスを監視し始めたところ、metricsが提示するQPSは接続数に比例しないことが分かりました.qpsは1500で、接続数は10個です.トリガのIDle timeoutは回収されたと思いますが、履歴監視図で接続はまだ少ないです.????
k 8 scache呼び出し側の理解によって、彼らはいつも乱暴に多くの協力プログラムを開いてK 8 scacheを訪問します.デフォルトのgolang httpclient transportは接続数にデフォルト制限があり、接続池の総サイズは100で、各host接続数は2です.あるurlを同時に要求すると、接続池の返却ができません.つまり接続池の大きさを超えた接続は自動的にclsoe()されます.だから、我が社のゴンランドの足場ではデフォルトのhttpclientに対してハイスペックのトランスポートを作成します.接続池がクローズアップされているという問題はあまりありません.
もし本当に接続池が爆発したら? 誰が自発的に仕掛けて閉鎖して、誰がtcp time-wait状態があって、しかしnetstat命令を通じて(通って)少量k 8 scacheと関係があるtime-waitだけを発見します.
問題をつきとめる
問題をすでに知っています 敏感な情報を隠すために、簡単なシーンで問題のcaseを設定します.
tcpdumpは問題を分析しますか?
パケット情報は、クライアントアクティブトリガ
linux監視とカーネルログにより、カーネルの設定ではないことが確認できます.リンガーの方がもっと不可能です.???大体の確率は可能です.クリアされていない読み取りバッファの接続を閉じます.
システム呼び出しによって分析できますが、headerの部分だけが読み取られたようです.まだbodyのデータが読めていません.
ここでは、問題の所在を大体推測して、業務先がhttpclientに関わる論理コードを見つけます.ダミーコードは、上の結論と同じように、headerだけ読みましたが、reponse bodyデータは読み終わりませんでした.
特殊なシーンだと思いましたが、結果的に使い方が間違っていました.依頼を投函してからhttp codeだけを判断しました.本当のビジネスコードはbodyにあります.???
詳しくは言いません.header lengthの長さのデータを読んでください.
問題を分析する
他の人が使うべきではないにもかかわらず、なぜ短い接続ができて、多重化できないのかを分析します.
なぜbodyを読み込まないと問題が発生しますか?実はhttp.Resonseフィールドの記述には既に説明があります.Bodyが読み終わらないと、接続が多重化されないかもしれません.
一般的にhttpプロトコルの解釈器はまずheaderを解析してからbodyを解析して、現在の問題と結びつけてこのように推測しています.接続のreadLoopは新しい要求を受け取って、headerを解析してみてから、呼び出し元に戻ってbodyを読むのを待っていますが、呼び出し側は読み取りに行かず、直接bodyを閉じるように選択しました.その後、新しい要求がtransport roundTripによって再スケジュールされた時、readLoopのheaderは読み取りと解析に失敗しました.彼の読み取りバッファには前の未読のデータがありますので、headerは解析できません.一般的なネットワークプログラミングの原則に従って、プロトコルの解析に失敗し、直接接続を閉じます.
そう思いますが、golang net/httpのコードを見ましたが、結局はそうではありませんでした.???
ソースの分析
httpclientは接続ごとに読み書きの協働工程を作成します.それぞれreqchとwritechを使ってroundTripと通信します.上の段阶で使用されているレスポンス.Bodyは何回もカプセル化されています.一回のパッケージのbodyは直接にnet.co nnと交互に読み取っています.二次パッケージのbodyはcloseとeof処理を强化したbody EOFriginalです.
bodyを読み取らずにcloseを行うと、earlyClose Fn()のコールバックがトリガされ、earlyClose Fnの関数定義を見て、closeがio.EOFに会えない時に呼び出します.カスタムearlyCloose Fn方法はreadLoopの傍受するwaitForBodyReadにfalseに伝えられます. このように、aliveがfalseのためにループを続けることができない新しい要求を受信するには、登録されたdeferメソッドを呼び出して、接続をオフにし、接続池をクリーンアップするしかありません.
同僚のhttpclientの使い方がおかしいです.head method以外に、bodyを読み込まないという依頼があるとは思いませんでした.だから、みんなはhttpclientがこのようなことがあると知っています.
また、ネット/httpのコードが回りくどいと感じています.紹介を見たことがないので、直接コードを見ても、はまりやすいです.http clientの実現を専門に説明する時間があります.
テストコードは以下の通りです.
// xiaorui.cc
func HttpGet() {
for {
fmt.Println("new")
resp, err := http.Get("http://www.baidu.com")
if err != nil {
fmt.Println(err)
continue
}
if resp.StatusCode == http.StatusOK {
continue
}
resp.Body.Close()
fmt.Println("go num", runtime.NumGoroutine())
}
}
皆さんが考えているように、HEAD Method以外に、headerだけを読む需要は少ないでしょう.ところで、golang httpclientの注意すべきところは本当に多いです.
だけである.いくつかの業務システムが狂ったように各エリアの異なるk 8 sクラスタを呼び出して、機械室を跨いでの遅延を減らすために、新しい古いk 8 sクラスタappiに対応して、k 8 s appi-serverの負荷を減らすために、k 8 scacheサービスを開発しました.配備運転後にこのサービスを監視し始めたところ、metricsが提示するQPSは接続数に比例しないことが分かりました.qpsは1500で、接続数は10個です.トリガのIDle timeoutは回収されたと思いますが、履歴監視図で接続はまだ少ないです.????
k 8 scache呼び出し側の理解によって、彼らはいつも乱暴に多くの協力プログラムを開いてK 8 scacheを訪問します.デフォルトのgolang httpclient transportは接続数にデフォルト制限があり、接続池の総サイズは100で、各host接続数は2です.あるurlを同時に要求すると、接続池の返却ができません.つまり接続池の大きさを超えた接続は自動的にclsoe()されます.だから、我が社のゴンランドの足場ではデフォルトのhttpclientに対してハイスペックのトランスポートを作成します.接続池がクローズアップされているという問題はあまりありません.
もし本当に接続池が爆発したら? 誰が自発的に仕掛けて閉鎖して、誰がtcp time-wait状態があって、しかしnetstat命令を通じて(通って)少量k 8 scacheと関係があるtime-waitだけを発見します.
問題をつきとめる
問題をすでに知っています 敏感な情報を隠すために、簡単なシーンで問題のcaseを設定します.
tcpdumpは問題を分析しますか?
パケット情報は、クライアントアクティブトリガ
RST
が最後の行で確認される.RSTをトリガするシーンが多いですが、よくあるのはtw_です.bucketがいっぱいになりました.tcp接続列がいっぱいになりました.tcp_を開けます.abort_うむoverflow、配置so_linger、バッファを読んで、データがまだあります.closeにあげます.linux監視とカーネルログにより、カーネルの設定ではないことが確認できます.リンガーの方がもっと不可能です.???大体の確率は可能です.クリアされていない読み取りバッファの接続を閉じます.
22:11:01.790573 IP (tos 0x0, ttl 64, id 29826, offset 0, flags [DF], proto TCP (6), length 60)
host-46.54550 > 110.242.68.3.http: Flags [S], cksum 0x5f62 (incorrect -> 0xb894), seq 1633933317, win 29200, options [mss 1460,sackOK,TS val 47230087 ecr 0,nop,wscale 7], length 0
22:11:01.801715 IP (tos 0x0, ttl 43, id 0, offset 0, flags [DF], proto TCP (6), length 52)
110.242.68.3.http > host-46.54550: Flags [S.], cksum 0x00a0 (correct), seq 1871454056, ack 1633933318, win 29040, options [mss 1452,nop,nop,sackOK,nop,wscale 7], length 0
22:11:01.801757 IP (tos 0x0, ttl 64, id 29827, offset 0, flags [DF], proto TCP (6), length 40)
host-46.54550 > 110.242.68.3.http: Flags [.], cksum 0x5f4e (incorrect -> 0xb1f5), seq 1, ack 1, win 229, length 0
22:11:01.801937 IP (tos 0x0, ttl 64, id 29828, offset 0, flags [DF], proto TCP (6), length 134)
host-46.54550 > 110.242.68.3.http: Flags [P.], cksum 0x5fac (incorrect -> 0xb4d6), seq 1:95, ack 1, win 229, length 94: HTTP, length: 94
GET / HTTP/1.1
Host: www.baidu.com
User-Agent: Go-http-client/1.1
22:11:01.814122 IP (tos 0x0, ttl 43, id 657, offset 0, flags [DF], proto TCP (6), length 40)
110.242.68.3.http > host-46.54550: Flags [.], cksum 0xb199 (correct), seq 1, ack 95, win 227, length 0
22:11:01.815179 IP (tos 0x0, ttl 43, id 658, offset 0, flags [DF], proto TCP (6), length 4136)
110.242.68.3.http > host-46.54550: Flags [P.], cksum 0x6f4e (incorrect -> 0x0e70), seq 1:4097, ack 95, win 227, length 4096: HTTP, length: 4096
HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0x8b3b62c400142f77
Cache-Control: private
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Wed, 09 Dec 2020 14:11:01 GMT
...
22:11:01.815214 IP (tos 0x0, ttl 64, id 29829, offset 0, flags [DF], proto TCP (6), length 40)
host-46.54550 > 110.242.68.3.http: Flags [.], cksum 0x5f4e (incorrect -> 0xa157), seq 95, ack 4097, win 293, length 0
22:11:01.815222 IP (tos 0x0, ttl 43, id 661, offset 0, flags [DF], proto TCP (6), length 4136)
110.242.68.3.http > host-46.54550: Flags [P.], cksum 0x6f4e (incorrect -> 0x07fa), seq 4097:8193, ack 95, win 227, length 4096: HTTP
22:11:01.815236 IP (tos 0x0, ttl 64, id 29830, offset 0, flags [DF], proto TCP (6), length 40)
host-46.54550 > 110.242.68.3.http: Flags [.], cksum 0x5f4e (incorrect -> 0x9117), seq 95, ack 8193, win 357, length 0
22:11:01.815243 IP (tos 0x0, ttl 43, id 664, offset 0, flags [DF], proto TCP (6), length 5848)
...
host-46.54550 > 110.242.68.3.http: Flags [.], cksum 0x5f4e (incorrect -> 0x51ba), seq 95, ack 24165, win 606, length 0
22:11:01.815369 IP (tos 0x0, ttl 64, id 29834, offset 0, flags [DF], proto TCP (6), length 40)
host-46.54550 > 110.242.68.3.http: Flags [R.], cksum 0x5f4e (incorrect -> 0x51b6), seq 95, ack 24165, win 606, length 0
lsofを通じて、プロセス関連のTCP接続を見つけて、ssまたはnetstatを使って読み書きバッファを確認します.情報は以下の通りです.recv-q読み取りバッファは確かにデータが存在します.このバッファバイトはまだ読んでいません.接続が切れるまでrstを誘発しました.$ lsof -p 54330
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
...
aaa 54330 root 1u CHR 136,0 0t0 3 /dev/pts/0
aaa 54330 root 2u CHR 136,0 0t0 3 /dev/pts/0
aaa 54330 root 3u a_inode 0,10 0 8838 [eventpoll]
aaa 54330 root 4r FIFO 0,9 0t0 223586913 pipe
aaa 54330 root 5w FIFO 0,9 0t0 223586913 pipe
aaa 54330 root 6u IPv4 223596521 0t0 TCP host-46:60626->110.242.68.3:http (ESTABLISHED)
$ ss -an|egrep "68.3:80"
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 72480 0 172.16.0.46:60626 110.242.68.3:80
straceトレースシステム呼び出しシステム呼び出しによって分析できますが、headerの部分だけが読み取られたようです.まだbodyのデータが読めていません.
[pid 8311] connect(6, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("110.242.68.3")}, 16
[pid 195519] epoll_pwait(3,
[pid 8311] <...>) = -1 EINPROGRESS ( )
[pid 8311] epoll_ctl(3, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=2350546712, u64=140370471714584}}
[pid 195519] getsockopt(6, SOL_SOCKET, SO_ERROR,
[pid 192592] nanosleep({tv_sec=0, tv_nsec=20000},
[pid 195519] getpeername(6, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("110.242.68.3")}, [112->16]) = 0
[pid 195519] getsockname(6,
[pid 195519] <...>{sa_family=AF_INET, sin_port=htons(47746), sin_addr=inet_addr("172.16.0.46")}, [112->16]) = 0
[pid 195519] setsockopt(6, SOL_TCP, TCP_KEEPINTVL, [15], 4) = 0
[pid 195519] setsockopt(6, SOL_TCP, TCP_KEEPIDLE, [15], 4
[pid 8311] write(6, "GET / HTTP/1.1\r
Host: www.baidu.com\r
User-Agent: Go-http-client/1.1\r
Accept-Encoding: gzip\r
\r
", 94
[pid 192595] read(6,
[pid 192595] <...>"HTTP/1.1 200 OK\r
Bdpagetype: 1\r
Bdqid: 0xc43c9f460008101b\r
Cache-Control: private\r
Connection: keep-alive\r
Content-Encoding: gzip\r
Content-Type: text/html;charset=utf-8\r
Date: Wed, 09 Dec 2020 13:46:30 GMT\r
Expires: Wed, 09 Dec 2020 13:45:33 GMT\r
P3p: CP=\" OTI DSP COR IVA OUR IND COM \"\r
P3p: CP=\" OTI DSP COR IVA OUR IND COM \"\r
Server: BWS/1.1\r
Set-Cookie: BAIDUID=996EE645C83622DF7343923BF96EA1A1:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com\r
Set-Cookie: BIDUPSID=99"..., 4096) = 4096
[pid 192595] close(6
論理コードここでは、問題の所在を大体推測して、業務先がhttpclientに関わる論理コードを見つけます.ダミーコードは、上の結論と同じように、headerだけ読みましたが、reponse bodyデータは読み終わりませんでした.
特殊なシーンだと思いましたが、結果的に使い方が間違っていました.依頼を投函してからhttp codeだけを判断しました.本当のビジネスコードはbodyにあります.???
urls := []string{...}
for _, url := range urls {
resp, err := http.Post(url, ...)
if err != nil {
// ...
}
if resp.StatusCode == http.StatusOK {
continue
}
// handle redis cache
// handle mongodb
// handle rocketmq
// ...
resp.Body.Close()
}
どう解決しますか詳しくは言いません.header lengthの長さのデータを読んでください.
問題を分析する
他の人が使うべきではないにもかかわらず、なぜ短い接続ができて、多重化できないのかを分析します.
なぜbodyを読み込まないと問題が発生しますか?実はhttp.Resonseフィールドの記述には既に説明があります.Bodyが読み終わらないと、接続が多重化されないかもしれません.
// The http Client and Transport guarantee that Body is always
// non-nil, even on responses without a body or responses with
// a zero-length body. It is the caller's responsibility to
// close Body. The default HTTP client's Transport may not
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
// not read to completion and closed.
//
// The Body is automatically dechunked if the server replied
// with a "chunked" Transfer-Encoding.
//
// As of Go 1.12, the Body will also implement io.Writer
// on a successful "101 Switching Protocols" response,
// as used by WebSockets and HTTP/2's "h2c" mode.
Body io.ReadCloser
ご存知のように、golang httpclientはreponse Bodyのクローズに注意しなければなりませんが、上のcaseは確かにbodyに関連しています.ただし、例外的にreponse bodyのデータを読み取っていません.このように接続が異常に閉鎖され、接続池が多重化できなくなることがあります.一般的にhttpプロトコルの解釈器はまずheaderを解析してからbodyを解析して、現在の問題と結びつけてこのように推測しています.接続のreadLoopは新しい要求を受け取って、headerを解析してみてから、呼び出し元に戻ってbodyを読むのを待っていますが、呼び出し側は読み取りに行かず、直接bodyを閉じるように選択しました.その後、新しい要求がtransport roundTripによって再スケジュールされた時、readLoopのheaderは読み取りと解析に失敗しました.彼の読み取りバッファには前の未読のデータがありますので、headerは解析できません.一般的なネットワークプログラミングの原則に従って、プロトコルの解析に失敗し、直接接続を閉じます.
そう思いますが、golang net/httpのコードを見ましたが、結局はそうではありませんでした.???
ソースの分析
httpclientは接続ごとに読み書きの協働工程を作成します.それぞれreqchとwritechを使ってroundTripと通信します.上の段阶で使用されているレスポンス.Bodyは何回もカプセル化されています.一回のパッケージのbodyは直接にnet.co nnと交互に読み取っています.二次パッケージのbodyはcloseとeof処理を强化したbody EOFriginalです.
bodyを読み取らずにcloseを行うと、earlyClose Fn()のコールバックがトリガされ、earlyClose Fnの関数定義を見て、closeがio.EOFに会えない時に呼び出します.カスタムearlyCloose Fn方法はreadLoopの傍受するwaitForBodyReadにfalseに伝えられます. このように、aliveがfalseのためにループを続けることができない新しい要求を受信するには、登録されたdeferメソッドを呼び出して、接続をオフにし、接続池をクリーンアップするしかありません.
// xiaorui.cc
func (pc *persistConn) readLoop() {
closeErr := errReadLoopExiting // default value, if not changed below
defer func() {
pc.close(closeErr) //
pc.t.removeIdleConn(pc) //
}()
...
alive := true
for alive {
...
rc :=
bodyEOFriginalのClose():// xiaorui.cc
func (es *bodyEOFSignal) Close() error {
es.mu.Lock()
defer es.mu.Unlock()
if es.closed {
return nil
}
es.closed = true
if es.earlyCloseFn != nil && es.rerr != io.EOF {
return es.earlyCloseFn()
}
err := es.body.Close()
return es.condfn(err)
}
最終的には、persistConnのclose()を呼び出し、接続をクローズし、closechをオフします.// xiaorui.cc
func (pc *persistConn) close(err error) {
pc.mu.Lock()
defer pc.mu.Unlock()
pc.closeLocked(err)
}
func (pc *persistConn) closeLocked(err error) {
if err == nil {
panic("nil error")
}
pc.broken = true
if pc.closed == nil {
pc.closed = err
pc.t.decConnsPerHost(pc.cacheKey)
if pc.alt == nil {
if err != errCallerOwnsConn {
pc.conn.Close() //
}
close(pc.closech) //
}
}
}
とにかく同僚のhttpclientの使い方がおかしいです.head method以外に、bodyを読み込まないという依頼があるとは思いませんでした.だから、みんなはhttpclientがこのようなことがあると知っています.
また、ネット/httpのコードが回りくどいと感じています.紹介を見たことがないので、直接コードを見ても、はまりやすいです.http clientの実現を専門に説明する時間があります.