Parlons du mécanisme de délai d'attente HttpClient de Golang
Dans le processus d'écriture de Go, je compare souvent les caractéristiques de ces deux langages. J'ai rencontré de nombreux pièges et trouvé de nombreux endroits intéressants. Dans cet article, je parlerai du mécanisme de délai d'attente de HttpClient fourni avec Go. cela sera utile à tout le monde.
Le principe sous-jacent du délai d'attente Java HttpClient
Avant de présenter le mécanisme de délai d'attente HttpClient de Go, examinons d'abord comment Java implémente le délai d'attente. [Recommandations associées : Tutoriel vidéo Go]
Écrivez un HttpClient natif Java, définissez le délai d'expiration de connexion et le délai d'expiration de lecture correspondant respectivement aux méthodes sous-jacentes :
Retour au code source de la JVM, j'ai trouvé qu'il était correct L'encapsulation des appels système n'est en réalité pas seulement Java, mais la plupart des langages de programmation utilisent les capacités de délai d'attente fournies par le système d'exploitation.
Cependant, HttpClient de Go propose un autre mécanisme de timeout, assez intéressant. Mais avant de commencer, comprenons d’abord le contexte de Go.
Introduction à Go Context
Qu'est-ce que le contexte ?
Selon les commentaires du code source Go :
// Un Context porte une date limite, un signal d'annulation et d'autres valeurs à travers // Limites de l'API. // Les méthodes de Context peuvent être appelées par plusieurs goroutines simultanément.
Context est simplement une interface qui peut transporter des délais d'attente, des signaux d'annulation et d'autres données. Les méthodes de Context seront appelées simultanément par plusieurs goroutines.
Context est quelque peu similaire à ThreadLocal de Java. Il peut transférer des données dans le thread, mais ce n'est pas exactement la même chose. Il s'agit d'un transfert explicite, tandis que ThreadLocal est un transfert implicite. En plus du transfert de données, Context peut également effectuer des délais d'attente. et les signaux d'annulation.
Context définit uniquement l'interface, et plusieurs implémentations spécifiques sont fournies dans Go :
- Background : implémentation vide, ne fait rien
- TODO : je ne sais pas encore quel contexte utiliser, alors utilisez plutôt TODO Context. cancelCtx : Contexte annulable
- timerCtx : Contexte de délai d'attente actif
Context Trois exemples de fonctionnalités
Cette partie de l'exemple provient du code source de Go, situé danssrc/context/example_test.go
src/context/example_test.go
携带数据
使用 context.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 }
取消
先起一个协程执行一个死循环,不停地往 channel 中写数据,同时监听 ctx.Done()
的事件
// 来自 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
生成一个可取消的 Context,传入 gen
方法,直到 gen
返回 5 时,调用 cancel
取消 gen
方法的执行。
// 来自 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
这么看起来,可以简单理解为在一个协程的循环中埋入结束标志,另一个协程去设置这个结束标志。
超时
有了 cancel 的铺垫,超时就好理解了,cancel 是手动取消,超时是自动取消,只要起一个定时的协程,到时间后执行 cancel 即可。
设置超时时间有2种方式:context.WithTimeout
与 context.WithDeadline
,WithTimeout 是设置一段时间后,WithDeadline 是设置一个截止时间点,WithTimeout 最终也会转换为 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 }
Go HttpClient 的另一种超时机制
基于 Context 可以设置任意代码段执行的超时机制,就可以设计一种脱离操作系统能力的请求超时能力。
超时机制简介
看一下 Go 的 HttpClient 超时配置说明:
client := 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
Carry data
Utilisez context.WithValue
pour les transporter et utilisez Value
pour obtenir la valeur. L'exemple dans le code source est le suivant suit : client := http.Client{
Timeout: 10 * time.Minute,
}
resp, err := client.Get("http://127.0.0.1:81/hello")
Tout d'abord, démarrez une coroutine pour exécuter une boucle infinie, écrivez continuellement des données sur le canal et en même temps écoutez le événement de ctx.Done()
// 来自 src/net/http/client.go deadline = c.deadline()
Puis générez un Context annulable via context.WithCancel
, passez la méthode gen
, jusqu'à ce que gen
renvoie 5, appelez cancel
Annule l'exécution de la méthode gen
. // 来自 src/net/http/client.go
stopTimer, didTimeout := setRequestCancel(req, rt, deadline)
Avec la préfiguration de l'annulation, le délai d'attente est facile à comprendre. L'annulation est une annulation manuelle et le délai d'attente est une annulation automatique tant qu'une coroutine planifiée. est démarré, exécutez simplement Cancel une fois le temps écoulé.
🎜Il existe deux façons de définir le délai d'attente :context.WithTimeout
et context.WithDeadline
est défini après une période de temps, WithDeadline est défini sur une date limite. , et WithTimeout sera finalement converti en WithDeadline. 🎜// 来自 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() } }()
Timeout
inclut le temps de connexion, de redirection et de lecture des données. interrompra la lecture des données après le délai d'attente. S'il est défini sur 0, il n'y a pas de limite de délai d'attente. 🎜🎜C'est-à-dire que ce délai d'attente est le 🎜délai d'expiration global d'une requête🎜, sans avoir à définir le délai d'expiration de connexion, le délai d'expiration de lecture, etc. séparément. 🎜🎜Cela peut être un meilleur choix pour les utilisateurs. Dans la plupart des scénarios, les utilisateurs n'ont pas besoin de se soucier de la partie à l'origine du délai d'attente, mais veulent seulement savoir quand la requête HTTP dans son ensemble peut être renvoyée. 🎜🎜🎜🎜Le principe sous-jacent du mécanisme de délai d'attente🎜🎜🎜🎜Utiliser l'exemple le plus simple pour illustrer le principe sous-jacent du mécanisme de délai d'attente. 🎜这里我起了一个本地服务,用 Go HttpClient 去请求,超时时间设置为 10 分钟,建议使 Debug 时设置长一点,否则可能超时导致无法走完全流程。
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)
这里返回的 stopTimer 就是可以手动 cancel 的方法,didTimeout 是判断是否超时的方法。这两个可以理解为回调方法,调用 stopTimer() 可以手动 cancel,调用 didTimeout() 可以返回是否超时。
设置的主要代码其实就是将请求的 Context 替换为 cancelCtx,后续所有的操作都将携带这个 cancelCtx:
// 来自 src/net/http/client.go var cancelCtx func() if oldCtx := req.Context(); timeBeforeContextDeadline(deadline, oldCtx) { req.ctx, cancelCtx = context.WithDeadline(oldCtx, deadline) }
同时,再起一个定时器,当超时时间到了之后,将 timedOut 设置为 true,再调用 doCancel(),doCancel() 是调用真正 RoundTripper (代表一个 HTTP 请求事务)的 CancelRequest,也就是取消请求,这个跟实现有关。
// 来自 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() } }()
Go 默认 RoundTripper CancelRequest 实现是关闭这个连接
// 位于 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) // ... }
代码的开头监听 ctx.Done,如果超时则直接返回,使用 for 循环主要是为了请求的重试。
后续的 getConn 是阻塞的,代码比较长,挑重点说,先看看有没有空闲连接,如果有则直接返回
// 位于 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)
再接着是一个 select 等待连接建立成功、超时或者主动取消,这就实现了在连接过程中的超时
// 位于 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. 读写数据
在上一条连接建立的时候,每个链接还偷偷起了两个协程,一个负责往连接中写入数据,另一个负责读数据,他们都监听了相应的 channel。
// 位于 src/net/http/transport.go go pconn.readLoop() go pconn.writeLoop()
其中 wirteLoop 监听来自主协程的数据,并往连接中写入
// 位于 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 } } }
同理,readLoop 读取响应数据,并写回主协程。读与写的过程中如果超时了,连接将被关闭,报错退出。
超时机制小结
Go 的这种请求超时机制,可随时终止请求,可设置整个请求的超时时间。其实现主要依赖协程、channel、select 机制的配合。总结出套路是:
- 主协程生成 cancelCtx,传递给子协程,主协程与子协程之间用 channel 通信
- 主协程 select channel 和 cancelCtx.Done,子协程完成或取消则 return
- 循环任务:子协程起一个循环处理,每次循环开始都 select cancelCtx.Done,如果完成或取消则退出
- 阻塞任务:子协程 select 阻塞任务与 cancelCtx.Done,阻塞任务处理完或取消则退出
以循环任务为例
Java 能实现这种超时机制吗
直接说结论:暂时不行。
首先 Java 的线程太重,像 Go 这样一次请求开了这么多协程,换成线程性能会大打折扣。
其次 Go 的 channel 虽然和 Java 的阻塞队列类似,但 Go 的 select 是多路复用机制,Java 暂时无法实现,即无法监听多个队列是否有数据到达。所以综合来看 Java 暂时无法实现类似机制。
总结
本文介绍了 Go 另类且有趣的 HTTP 超时机制,并且分析了底层实现原理,归纳出了这种机制的套路,如果我们写 Go 代码,也可以如此模仿,让代码更 Go。
原文地址:https://juejin.cn/post/7166201276198289445
更多编程相关知识,请访问:编程视频!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Lire et écrire des fichiers en toute sécurité dans Go est crucial. Les directives incluent : Vérification des autorisations de fichiers Fermeture de fichiers à l'aide de reports Validation des chemins de fichiers Utilisation de délais d'attente contextuels Le respect de ces directives garantit la sécurité de vos données et la robustesse de vos applications.

Comment configurer le pool de connexions pour les connexions à la base de données Go ? Utilisez le type DB dans le package base de données/sql pour créer une connexion à la base de données ; définissez MaxOpenConns pour contrôler le nombre maximum de connexions simultanées ; définissez MaxIdleConns pour définir le nombre maximum de connexions inactives ; définissez ConnMaxLifetime pour contrôler le cycle de vie maximum de la connexion ;

Comment utiliser Gomega pour les assertions dans les tests unitaires Golang Dans les tests unitaires Golang, Gomega est une bibliothèque d'assertions populaire et puissante qui fournit des méthodes d'assertion riches afin que les développeurs puissent facilement vérifier les résultats des tests. Installez Gomegagoget-ugithub.com/onsi/gomega Utilisation de Gomega pour les assertions Voici quelques exemples courants d'utilisation de Gomega pour les assertions : 1. Importation d'assertion d'égalité "github.com/onsi/gomega" funcTest_MyFunction(t*testing.T){

La différence entre le framework GoLang et le framework Go se reflète dans l'architecture interne et les fonctionnalités externes. Le framework GoLang est basé sur la bibliothèque standard Go et étend ses fonctionnalités, tandis que le framework Go se compose de bibliothèques indépendantes pour atteindre des objectifs spécifiques. Le framework GoLang est plus flexible et le framework Go est plus facile à utiliser. Le framework GoLang présente un léger avantage en termes de performances et le framework Go est plus évolutif. Cas : gin-gonic (framework Go) est utilisé pour créer l'API REST, tandis qu'Echo (framework GoLang) est utilisé pour créer des applications Web.

Les données JSON peuvent être enregistrées dans une base de données MySQL à l'aide de la bibliothèque gjson ou de la fonction json.Unmarshal. La bibliothèque gjson fournit des méthodes pratiques pour analyser les champs JSON, et la fonction json.Unmarshal nécessite un pointeur de type cible pour désorganiser les données JSON. Les deux méthodes nécessitent la préparation d'instructions SQL et l'exécution d'opérations d'insertion pour conserver les données dans la base de données.

Meilleures pratiques : créer des erreurs personnalisées à l'aide de types d'erreurs bien définis (package d'erreurs) fournir plus de détails consigner les erreurs de manière appropriée propager correctement les erreurs et éviter de masquer ou de supprimer les erreurs Wrap si nécessaire pour ajouter du contexte

La fonction FindStringSubmatch recherche la première sous-chaîne correspondant à une expression régulière : la fonction renvoie une tranche contenant la sous-chaîne correspondante, le premier élément étant la chaîne entière correspondante et les éléments suivants étant des sous-chaînes individuelles. Exemple de code : regexp.FindStringSubmatch(text,pattern) renvoie une tranche de sous-chaînes correspondantes. Cas pratique : Il peut être utilisé pour faire correspondre le nom de domaine dans l'adresse email, par exemple : email:="user@example.com", pattern:=@([^\s]+)$ pour obtenir la correspondance du nom de domaine [1].

Chemin d'apprentissage du backend: le parcours d'exploration du front-end à l'arrière-end en tant que débutant back-end qui se transforme du développement frontal, vous avez déjà la base de Nodejs, ...
