Одной из самых популярных причин использования Go является то, что он поддерживает широкий спектр встроенных пакетов. Одним из таких пакетов является Httptrace. Например, если вам нужно позвонить на сервер с использованием протокола HTTP, необходимо выполнить промежуточные шаги, такие как установление соединения, разрешение DNS и выполнение рукопожатия TLS перед выполнением запроса.
Рассмотрим сценарий, в котором ваш API должен обращаться к серверу во время пиковой нагрузки, а клиент выходит из строя из-за тайм-аутов, отказов в соединении и других проблем. В таких сценариях мы можем использовать Httptrace для отслеживания ошибок и прошедшего времени промежуточных операций HTTP, чтобы проанализировать, где возникают ошибки и где именно HTTP занимает больше времени, чем ожидалось.
Как только вы определили виновника с помощью Httptrace, вы можете соответствующим образом улучшить свою конфигурацию. Например, если получение соединения с сервером занимает больше времени, чем ожидалось, вы можете обновить MaxIdleConns в зависимости от пиковой нагрузки. Точно так же вы можете обновить MaxIdleConnsPerHost и IdleConnTimeout на основе выявленных вами несоответствий.
Для лучшего понимания таймаутов перейдите по этой ссылке https://cs.opensource.google/go/go/+/refs/tags/go1.20.3:src/net/http/transport.go
Трассировка HTTP:-
func getHttpTrace() *httptrace.ClientTrace { // variables to track time/duration var ( dnsStart, dnsEnd, connStart, connEnd, connectStart, connectEnd, tlsHandShakeStart, tlsHandShakeEnd time.Time ) trace := &httptrace.ClientTrace{ GetConn: func(hostPort string) { connStart = time.Now() }, GotConn: func(info httptrace.GotConnInfo) { connEnd = time.Now() if info.Reused { log.Println("connection reused") } else { log.Println("time elapsed for Getting connection in micro seconds ", connEnd.Sub(connStart).Microseconds()) } }, ConnectStart: func(network, addr string) { connectStart = time.Now() }, ConnectDone: func(network, addr string, err error) { connectEnd = time.Now() if err != nil { log.Println("error at ConnectDone", err) } else { log.Println("time elapsed to connect in micro seconds ", connectEnd.Sub(connectStart).Microseconds()) } }, DNSStart: func(info httptrace.DNSStartInfo) { dnsStart = time.Now() }, DNSDone: func(info httptrace.DNSDoneInfo) { dnsEnd = time.Now() log.Println("time elapsed to resolve DNS in micro seconds ", dnsEnd.Sub(dnsStart).Microseconds()) }, TLSHandshakeStart: func() { tlsHandShakeStart = time.Now() }, TLSHandshakeDone: func(state tls.ConnectionState, err error) { if err != nil { log.Println("tls error", err) } else { tlsHandShakeEnd = time.Now() log.Println("time elapsed for TLS Handshake in micro seconds ", tlsHandShakeEnd.Sub(tlsHandShakeStart).Microseconds()) } }, PutIdleConn: func(err error) { if err != nil { log.Println("error at putIdleConn", err) } else { log.Println("put idle connection") } }, } return trace }
мы можем изменить приведенный выше код, например, добавить идентификатор трассировки, найти среднее значение всех вызовов и т. д.
пример для нахождения среднего значения каждой операции:-
func getHttpTrace() *httptrace.ClientTrace { var ( dnsStart, dnsEnd, connStart, connEnd, connectStart, connectEnd, tlsHandShakeStart, tlsHandShakeEnd time.Time ) trace := &httptrace.ClientTrace{ GetConn: func(hostPort string) { connStart = time.Now() }, GotConn: func(info httptrace.GotConnInfo) { connEnd = time.Now() if info.Reused { log.Println("connection reused") } else { avgGotConn = append(avgGotConn, connEnd.Sub(connStart).Microseconds()) } }, ConnectStart: func(network, addr string) { connectStart = time.Now() }, ConnectDone: func(network, addr string, err error) { connectEnd = time.Now() if err != nil { log.Println("error at ConnectDone", err) } else { avgConnect = append(avgConnect, connectEnd.Sub(connectStart).Microseconds()) } }, DNSStart: func(info httptrace.DNSStartInfo) { dnsStart = time.Now() }, DNSDone: func(info httptrace.DNSDoneInfo) { dnsEnd = time.Now() avgDns = append(avgDns, dnsEnd.Sub(dnsStart).Microseconds()) }, TLSHandshakeStart: func() { tlsHandShakeStart = time.Now() }, TLSHandshakeDone: func(state tls.ConnectionState, err error) { if err != nil { log.Println("tls error", err) } else { tlsHandShakeEnd = time.Now() avgTlsHandShake = append(avgTlsHandShake, tlsHandShakeEnd.Sub(tlsHandShakeStart).Microseconds()) } }, PutIdleConn: func(err error) { if err != nil { log.Println("error at putIdleConn", err) } else { log.Println("put idle connection") } }, } return trace } // finding average of each operation func findAvg() { var ( gotConn, connect, dns, tlsHandshake int64 ) for _, v := range avgGotConn { gotConn += v } log.Println("avg got conn", float64(gotConn)/float64(len(avgGotConn))) for _, v := range avgConnect { connect += v } log.Println("avg got connect", float64(connect)/float64(len(avgConnect))) for _, v := range avgDns { dns += v } log.Println("avg dns", float64(dns)/float64(len(avgDns))) for _, v := range avgTlsHandShake { tlsHandshake += v } log.Println("avg tls handshake", float64(tlsHandshake)/float64(len(avgTlsHandShake))) }
Пример использования: –
cli := http.Client{} trace := getHttpTrace() req, _ := http.NewRequest("GET", "http://localhost:8888/", bytes.NewBuffer([]byte)) req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) response,err:= cli.Do(req)
Пожалуйста, не стесняйтесь добавлять любые комментарии/предложения/ошибки и т. д.…👍