Dalam proses menulis Go, saya sering membandingkan ciri-ciri dua bahasa yang telah saya pijak dan menemui banyak tempat menarik Dalam artikel ini, saya akan bercakap tentang mekanisme tamat masa HttpClient yang disertakan dengan Go. Saya harap Bermanfaat kepada semua orang.
Sebelum memperkenalkan mekanisme tamat masa HttpClient Go, mari kita lihat dahulu Java Bagaimana untuk melaksanakan tamat masa. [Cadangan berkaitan: Pergi tutorial video]
Tulis HttpClient asli Java, dan tetapkan tamat masa sambungan dan baca tamat masa yang sepadan dengan kaedah asas:
Berbalik kepada kod sumber JVM, saya mendapati bahawa ia adalah enkapsulasi panggilan sistem Malah, bukan sahaja Java, kebanyakan bahasa pengaturcaraanmenggunakan keupayaan tamat masa yang disediakan oleh sistem pengendalian.
Namun, Go's HttpClient menyediakan satu lagi mekanisme tamat masa, yang agak menarik Mari kita lihat. Tetapi sebelum kita bermula, mari kita fahami Konteks Go terlebih dahulu.
Apakah Konteks?
Menurut ulasan dalam kod sumber Go:
// Konteks membawa tarikh akhir, isyarat pembatalan dan nilai lain di seluruh // Sempadan API. // Kaedah konteks mungkin dipanggil oleh berbilang goroutin secara serentak.
Konteks hanyalah antara muka yang boleh membawa tamat masa, isyarat pembatalan dan data lain akan dipanggil oleh berbilang goroutin secara serentak.
Konteks agak serupa dengan ThreadLocal Java Ia boleh menghantar data dalam urutan, tetapi ia tidak sama persis, manakala ThreadLocal ialah pemindahan tersirat juga boleh menjalankan tamat masa, isyarat Batal.
Konteks hanya mentakrifkan antara muka dan beberapa pelaksanaan khusus disediakan dalam Go:
Contoh dalam bahagian ini datang daripada kod sumber Go, terletak di Untuk membawa data
src/context/example_test.go
gunakan
Batalcontext.WithValue
Value
// 来自 src/context/example_test.go func ExampleWithValue() { type favContextKey string f := func(ctx context.Context, k favContextKey) { if v := ctx.Value(k); v != nil { fmt.Println("found value:", v) return } fmt.Println("key not found:", k) } k := favContextKey("language") ctx := context.WithValue(context.Background(), k, "Go") f(ctx, k) f(ctx, favContextKey("color")) // Output: // found value: Go // key not found: color }
, sehingga ctx.Done()
mengembalikan 5, panggil
// 来自 src/context/example_test.go gen := func(ctx context.Context) <-chan int { dst := make(chan int) n := 1 go func() { for { select { case <-ctx.Done(): return // returning not to leak the goroutine case dst <- n: n++ } } }() return dst }
context.WithCancel
gen
Nampaknya ia boleh difahami secara ringkas sebagai membenamkan bendera akhir dalam gelung satu coroutine, dan coroutine lain menetapkan bendera akhir. gen
cancel
gen
Tamat masa
// 来自 src/context/example_test.go ctx, cancel := context.WithCancel(context.Background()) defer cancel() // cancel when we are finished consuming integers for n := range gen(ctx) { fmt.Println(n) if n == 5 { break } } // Output: // 1 // 2 // 3 // 4 // 5
Dengan bayangan awal pembatalan, tamat masa mudah difahami Pembatalan adalah pembatalan manual dan tamat masa adalah pembatalan automatik selagi coroutine yang dijadualkan dimulakan , Hanya laksanakan batal selepas masa.
Satu lagi mekanisme tamat masa Go HttpClient context.WithTimeout
context.WithDeadline
// 来自 src/context/example_test.go func ExampleWithTimeout() { // Pass a context with a timeout to tell a blocking function that it // should abandon its work after the timeout elapses. ctx, cancel := context.WithTimeout(context.Background(), shortDuration) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) // prints "context deadline exceeded" } // Output: // context deadline exceeded }
Lihat arahan konfigurasi tamat masa HttpClient Go: Terjemah ulasan: Termasuk masa untuk menyambung, mengubah hala dan membaca data Pemasa akan mengganggu bacaan data selepas masa tamat masa Jika ditetapkan kepada 0, tiada had tamat masa.
Maksudnya, tamat masa ini ialah
tamat masa keseluruhan permintaanclient := http.Client{ Timeout: 10 * time.Second, } // 来自 src/net/http/client.go type Client struct { // ... 省略其他字段 // Timeout specifies a time limit for requests made by this // Client. The timeout includes connection time, any // redirects, and reading the response body. The timer remains // running after Get, Head, Post, or Do return and will // interrupt reading of the Response.Body. // // A Timeout of zero means no timeout. // // The Client cancels requests to the underlying Transport // as if the Request's Context ended. // // For compatibility, the Client will also use the deprecated // CancelRequest method on Transport if found. New // RoundTripper implementations should use the Request's Context // for cancellation instead of implementing CancelRequest. Timeout time.Duration }
Timeout
Ini mungkin pilihan yang lebih baik untuk pengguna Dalam kebanyakan senario, pengguna tidak perlu mengambil berat tentang bahagian mana yang menyebabkan tamat masa, tetapi hanya ingin tahu bila permintaan HTTP secara keseluruhan boleh dikembalikan.
Prinsip asas mekanisme tamat masa
Menggunakan contoh paling mudah untuk menggambarkan prinsip asas mekanisme tamat masa. 这里我起了一个本地服务,用 Go HttpClient 去请求,超时时间设置为 10 分钟,建议使 Debug 时设置长一点,否则可能超时导致无法走完全流程。 这里返回的 stopTimer 就是可以手动 cancel 的方法,didTimeout 是判断是否超时的方法。这两个可以理解为回调方法,调用 stopTimer() 可以手动 cancel,调用 didTimeout() 可以返回是否超时。 设置的主要代码其实就是将请求的 Context 替换为 cancelCtx,后续所有的操作都将携带这个 cancelCtx: 同时,再起一个定时器,当超时时间到了之后,将 timedOut 设置为 true,再调用 doCancel(),doCancel() 是调用真正 RoundTripper (代表一个 HTTP 请求事务)的 CancelRequest,也就是取消请求,这个跟实现有关。 Go 默认 RoundTripper CancelRequest 实现是关闭这个连接 代码的开头监听 ctx.Done,如果超时则直接返回,使用 for 循环主要是为了请求的重试。 后续的 getConn 是阻塞的,代码比较长,挑重点说,先看看有没有空闲连接,如果有则直接返回 如果没有空闲连接,起个协程去异步建立,建立成功再通知主协程 再接着是一个 select 等待连接建立成功、超时或者主动取消,这就实现了在连接过程中的超时 在上一条连接建立的时候,每个链接还偷偷起了两个协程,一个负责往连接中写入数据,另一个负责读数据,他们都监听了相应的 channel。 其中 wirteLoop 监听来自主协程的数据,并往连接中写入 同理,readLoop 读取响应数据,并写回主协程。读与写的过程中如果超时了,连接将被关闭,报错退出。 超时机制小结 Go 的这种请求超时机制,可随时终止请求,可设置整个请求的超时时间。其实现主要依赖协程、channel、select 机制的配合。总结出套路是: 以循环任务为例 Java 能实现这种超时机制吗 直接说结论:暂时不行。 首先 Java 的线程太重,像 Go 这样一次请求开了这么多协程,换成线程性能会大打折扣。 其次 Go 的 channel 虽然和 Java 的阻塞队列类似,但 Go 的 select 是多路复用机制,Java 暂时无法实现,即无法监听多个队列是否有数据到达。所以综合来看 Java 暂时无法实现类似机制。 本文介绍了 Go 另类且有趣的 HTTP 超时机制,并且分析了底层实现原理,归纳出了这种机制的套路,如果我们写 Go 代码,也可以如此模仿,让代码更 Go。 原文地址:https://juejin.cn/post/7166201276198289445 更多编程相关知识,请访问:编程视频!! Atas ialah kandungan terperinci Mari kita bincangkan tentang mekanisme tamat masa HttpClient Golang sendiri. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP! client := http.Client{
Timeout: 10 * time.Minute,
}
resp, err := client.Get("http://127.0.0.1:81/hello")
1. 根据 timeout 计算出超时的时间点
// 来自 src/net/http/client.go
deadline = c.deadline()
2. 设置请求的 cancel
// 来自 src/net/http/client.go
stopTimer, didTimeout := setRequestCancel(req, rt, deadline)
// 来自 src/net/http/client.go
var cancelCtx func()
if oldCtx := req.Context(); timeBeforeContextDeadline(deadline, oldCtx) {
req.ctx, cancelCtx = context.WithDeadline(oldCtx, deadline)
}
// 来自 src/net/http/client.go
timer := time.NewTimer(time.Until(deadline))
var timedOut atomicBool
go func() {
select {
case <-initialReqCancel:
doCancel()
timer.Stop()
case <-timer.C:
timedOut.setTrue()
doCancel()
case <-stopTimerCh:
timer.Stop()
}
}()
// 位于 src/net/http/transport.go
// CancelRequest cancels an in-flight request by closing its connection.
// CancelRequest should only be called after RoundTrip has returned.
func (t *Transport) CancelRequest(req *Request) {
t.cancelRequest(cancelKey{req}, errRequestCanceled)
}
3. 获取连接
// 位于 src/net/http/transport.go
for {
select {
case <-ctx.Done():
req.closeBody()
return nil, ctx.Err()
default:
}
// ...
pconn, err := t.getConn(treq, cm)
// ...
}
// 位于 src/net/http/transport.go
// Queue for idle connection.
if delivered := t.queueForIdleConn(w); delivered {
// ...
return pc, nil
}
// 位于 src/net/http/transport.go
// Queue for permission to dial.
t.queueForDial(w)
// 位于 src/net/http/transport.go
// Wait for completion or cancellation.
select {
case <-w.ready:
// ...
return w.pc, w.err
case <-req.Cancel:
return nil, errRequestCanceledConn
case <-req.Context().Done():
return nil, req.Context().Err()
case err := <-cancelc:
if err == errRequestCanceled {
err = errRequestCanceledConn
}
return nil, err
}
4. 读写数据
// 位于 src/net/http/transport.go
go pconn.readLoop()
go pconn.writeLoop()
// 位于 src/net/http/transport.go
func (pc *persistConn) writeLoop() {
defer close(pc.writeLoopDone)
for {
select {
case wr := <-pc.writech:
startBytesWritten := pc.nwrite
err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra, pc.waitForContinue(wr.continueCh))
// ...
if err != nil {
pc.close(err)
return
}
case <-pc.closech:
return
}
}
}
总结