【发布时间】:2026-02-11 10:05:03
【问题描述】:
好吧,这已经困扰了我好几个星期了,我无法弄清楚我错过了什么,或者这个泄漏在哪里,或者它是否存在。我有一个相当简单的工作量。获取一个 URL 列表,启动一个从通道中提取 URL 的 goroutine 池,并使用 tls.Dialer 创建到它们的 tls 连接。下面是内存图的快照,显示了我的代码的不断上升和 POC。
我的猜测是它与 tls 包完成的分配有关,因为它似乎只会爬上它连接到的更“成功”的 URL。 IE。如果他们中的大多数人没有连接,我看不到内存稳定增加。
这是运行中途的 pprof 输出:
Showing nodes accounting for 190.70MB, 95.58% of 199.53MB total
Dropped 34 nodes (cum <= 1MB)
Showing top 20 nodes out of 77
flat flat% sum% cum cum%
51.52MB 25.82% 25.82% 51.52MB 25.82% runtime.malg
24.10MB 12.08% 37.90% 24.10MB 12.08% bytes.makeSlice
17.07MB 8.55% 46.45% 41.17MB 20.63% crypto/tls.(*Conn).readHandshake
15MB 7.52% 53.97% 78.85MB 39.52% crypto/tls.dial
11MB 5.51% 59.48% 11.50MB 5.76% net.(*netFD).connect
10MB 5.01% 64.50% 15.42MB 7.73% context.WithDeadline
9MB 4.51% 69.01% 9MB 4.51% net.newFD (inline)
8MB 4.01% 73.02% 10.84MB 5.43% time.AfterFunc
7MB 3.51% 76.53% 52.93MB 26.53% net.(*Dialer).DialContext
5.50MB 2.76% 79.28% 5.50MB 2.76% context.(*cancelCtx).Done
5MB 2.51% 81.79% 84.35MB 42.28% main.main.func3
5MB 2.51% 84.30% 5MB 2.51% net.(*netFD).connect.func2
4.50MB 2.26% 86.55% 4.50MB 2.26% time.goFunc
4MB 2.01% 88.56% 4MB 2.01% crypto/tls.Client (inline)
3.16MB 1.58% 90.14% 3.16MB 1.58% main.main
2.84MB 1.42% 91.56% 2.84MB 1.42% time.startTimer
2.50MB 1.25% 92.82% 2.50MB 1.25% crypto/aes.(*aesCipherGCM).NewGCM
2.50MB 1.25% 94.07% 2.50MB 1.25% net.(*Resolver).internetAddrList.func1
1.50MB 0.75% 94.82% 1.50MB 0.75% crypto/tls.(*Config).Clone
1.50MB 0.75% 95.58% 1.50MB 0.75% crypto/aes.newCipher
package main
import (
"crypto/tls"
"net"
"sync"
"time"
)
func connectToTarget(targetString string, dialer *net.Dialer, config *tls.Config) {
tConn, err := tls.DialWithDialer(dialer,"tcp", targetString, config)
if err == nil {
//do something with connection
tConn.Close()
}
}
func main() {
workers := 256 * 256 //65536
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
dialer := &net.Dialer{
FallbackDelay: -1,
KeepAlive: -1,
Timeout: time.Duration(60) * time.Second,
}
targetsChan := make(chan string, workers)
var workerDone sync.WaitGroup
workerDone.Add(workers)
for i := 0; i < workers; i++ {
go func(functionWg *sync.WaitGroup, dialer *net.Dialer, tlsConfig *tls.Config, targets chan string) {
for targetToConnect := range targets {
connectToTarget(targetToConnect, dialer, tlsConfig)
}
functionWg.Done()
}(&workerDone, dialer, tlsConfig,targetsChan)
}
targets := []string{} //in the actual code this reads from a file containing the list since it is large
for _,target := range targets {
targetsChan <- target
}
close(targetsChan)
workerDone.Wait()
}
更新:
这是第一个 pprof(用了 10 分钟),与我在稳定攀爬一段时间后的最后一个 pprof 相比。
Showing nodes accounting for 329.76MB, 83.32% of 395.77MB total
Dropped 57 nodes (cum <= 1.98MB)
flat flat% sum% cum cum%
199.43MB 50.39% 50.39% 199.43MB 50.39% bytes.makeSlice
80.80MB 20.42% 70.81% 280.22MB 70.80% crypto/tls.(*Conn).readHandshake
28.02MB 7.08% 77.89% 28.02MB 7.08% crypto/tls.Client (inline)
18.01MB 4.55% 82.44% 18.01MB 4.55% crypto/aes.(*aesCipherGCM).NewGCM
11MB 2.78% 85.22% 11MB 2.78% crypto/aes.newCipher
9.50MB 2.40% 87.62% 9.50MB 2.40% crypto/tls.(*Config).Clone
-8MB 2.02% 85.60% 15.53MB 3.92% crypto/tls.dial
-5.50MB 1.39% 84.21% -5.50MB 1.39% net.(*netFD).connect
-5MB 1.26% 82.94% -5.50MB 1.39% context.WithDeadline
-4.50MB 1.14% 81.81% -11.50MB 2.91% net.(*Dialer).DialContext
3.50MB 0.88% 82.69% 3.50MB 0.88% net.sockaddrToTCP
-3MB 0.76% 81.93% -3MB 0.76% time.AfterFunc
2MB 0.51% 82.44% 17.53MB 4.43% main.serverCert
1.50MB 0.38% 82.82% 2MB 0.51% crypto/tls.(*cipherSuiteTLS13).expandLabel
1MB 0.25% 83.07% 18MB 4.55% crypto/tls.aeadAESGCM
1MB 0.25% 83.32% 10MB 2.53% crypto/tls.aeadAESGCMTLS13
0.50MB 0.13% 83.45% 201.93MB 51.02% crypto/tls.(*Conn).readRecordOrCCS
-0.50MB 0.13% 83.32% -2MB 0.51% net.(*sysDialer).dialSingle
0 0% 83.32% 118.09MB 29.84% bytes.(*Buffer).Grow (inline)
0 0% 83.32% 81.33MB 20.55% bytes.(*Buffer).Write
0 0% 83.32% 199.43MB 50.39% bytes.(*Buffer).grow
0 0% 83.32% 11MB 2.78% crypto/aes.NewCipher
0 0% 83.32% 18.01MB 4.55% crypto/cipher.NewGCM (inline)
0 0% 83.32% 18.01MB 4.55% crypto/cipher.newGCMWithNonceAndTagSize
0 0% 83.32% 318.24MB 80.41% crypto/tls.(*Conn).Handshake
0 0% 83.32% 318.24MB 80.41% crypto/tls.(*Conn).clientHandshake
0 0% 83.32% 3.01MB 0.76% crypto/tls.(*Conn).readChangeCipherSpec (inline)
0 0% 83.32% 118.09MB 29.84% crypto/tls.(*Conn).readFromUntil
0 0% 83.32% 198.92MB 50.26% crypto/tls.(*Conn).readRecord (inline)
0 0% 83.32% 11.58MB 2.93% crypto/tls.(*Conn).retryReadRecord
0 0% 83.32% 154.61MB 39.06% crypto/tls.(*clientHandshakeState).doFullHandshake
0 0% 83.32% 22.51MB 5.69% crypto/tls.(*clientHandshakeState).establishKeys
0 0% 83.32% 180.12MB 45.51% crypto/tls.(*clientHandshakeState).handshake
0 0% 83.32% 3.01MB 0.76% crypto/tls.(*clientHandshakeState).readFinished
0 0% 83.32% 12MB 3.03% crypto/tls.(*clientHandshakeStateTLS13).establishHandshakeKeys
0 0% 83.32% 117.50MB 29.69% crypto/tls.(*clientHandshakeStateTLS13).handshake
0 0% 83.32% 92.92MB 23.48% crypto/tls.(*clientHandshakeStateTLS13).readServerCertificate
0 0% 83.32% 11.58MB 2.93% crypto/tls.(*clientHandshakeStateTLS13).readServerParameters
0 0% 83.32% 10.50MB 2.65% crypto/tls.(*halfConn).setTrafficSecret
0 0% 83.32% 15.53MB 3.92% crypto/tls.DialWithDialer (inline)
0 0% 83.32% 3MB 0.76% crypto/tls.cipherAES
0 0% 83.32% 318.24MB 80.41% crypto/tls.dial.func2
0 0% 83.32% 17.53MB 4.43% main.main.func3
0 0% 83.32% -2MB 0.51% net.(*sysDialer).dialSerial
0 0% 83.32% -2MB 0.51% net.internetSocket
0 0% 83.32% -2MB 0.51% net.socket
最大的违规者是在握手读取期间调用的 bytes.makeSlice。这可能意味着每次 goroutine 生成一个新的 tls.DialWithDialer 以连接到缓冲区所占用的 URL。这对我来说是一个惊喜,因为我希望 Close() 方法会驱逐这些缓冲区。
【问题讨论】:
-
65536 真的是很多工人。他们每个人都可能需要一段时间才能“开始”(并开始分配更多内存),因为他们正在争夺诸如净带宽之类的资源。另外,你的文件有多大,你是怎么读的?
-
嗯,降低工作程序肯定会降低使用的总内存,只是因为 goroutines 减少了,但它仍然留下了它为什么增长的谜团。即使他们中的很多人,我都希望最终达到稳定状态。我使用了一些来查看 goroutines 正在做什么,没有人坐在那里等待做任何事情,所以我认为他们“开始”马上,该文件大约是 ~7877302 个 URL,我正在使用 bufio.Scanner 发送到一个频道。
-
为什么需要等待初始化所有 65536 个工作 goroutine 才能开始将字符串发送到通道?
-
我有点好奇你为什么说“http客户端”,因为你使用的不是http客户端,而是使用tls,它是https下面的一层。 http 包在执行 Get 请求时会自动使用 tls。
-
http 客户端不正确我同意,对此感到抱歉。我实际上根本不需要响应中的字节,只需要来自成功的 tls 连接的数据,所以我降低了一个级别来创建 tls 连接。我会改写问题。
标签: go concurrency