Maison  >  Article  >  développement back-end  >  Explication détaillée de la façon d'implémenter le contrôle du délai d'attente Go

Explication détaillée de la façon d'implémenter le contrôle du délai d'attente Go

藏色散人
藏色散人avant
2021-04-06 11:18:293817parcourir
Ce qui suit est

Golang La colonne tutoriel vous présentera comment réaliser GO au fil du temps, j'espère que cela sera utile aux amis dans le besoin !

Explication détaillée de la façon d'implémenter le contrôle du délai d'attente Go

Pourquoi avons-nous besoin d'un contrôle des délais d'attente ?

    Le temps de requête est trop long, l'utilisateur a peut-être quitté cette page, le serveur consomme toujours des ressources pour le traitement et les résultats obtenus n'ont aucun sens
  • Le traitement côté serveur pendant trop longtemps, cela occupera trop de ressources, ce qui entraînera une concurrence réduite et même des accidents d'indisponibilité

Nécessité du contrôle du délai d'attente Go

Go est normalement utilisé pour écrire des backends Pour le service, une requête est généralement complétée par plusieurs sous-tâches en série ou en parallèle. Chaque sous-tâche peut être une autre requête interne. Ensuite, lorsque la requête expire, nous devons revenir rapidement et libérer les ressources occupées, telles que la goroutine, le descripteur de fichier, etc.

Contrôle commun des délais d'attente côté serveur

    Traitement logique en cours
  • Lecture et écrire des clients Requêtes secondaires, telles que les requêtes HTTP ou RPC
  • Appeler d'autres requêtes du serveur, notamment l'appel de RPC ou l'accès à la base de données, etc.

Que se passe-t-il s'il y a n'y a-t-il pas de contrôle de délai d'attente ?

Pour simplifier cet article, nous prenons une fonction de requête

comme exemple. Peu importe à quoi elle est utilisée, comme son nom l'indique, son traitement peut être plus lent. hardWork

func hardWork(job interface{}) error {
    time.Sleep(time.Minute)
    return nil}func requestWork(ctx context.Context, job interface{}) error {
  return hardWork(job)}
À ce moment, le client verra toujours l'image familière

Explication détaillée de la façon dimplémenter le contrôle du délai dattente Go

La plupart des utilisateurs ne regarderont pas le chrysanthème pendant une minute et abandonneront tôt Si vous partez, vous vous retrouverez avec un tas d'occupations de ressources sur l'ensemble du lien d'appel. Cet article n'entrera pas dans d'autres détails et se concentrera uniquement sur la mise en œuvre du délai d'attente.

Voyons comment implémenter le délai d'attente et quels sont les pièges.

Implémentation de la première version

Vous pouvez arrêter de lire et essayer de réfléchir à la façon d'implémenter le délai d'attente de cette fonction :

func requestWork(ctx context.Context, job interface{}) error {
    ctx, cancel := context.WithTimeout(ctx, time.Second*2)
    defer cancel()

    done := make(chan error)
    go func() {
        done Allons. écrivez une fonction principale pour la tester<p></p><pre class="brush:php;toolbar:false">func main() {
    const total = 1000
    var wg sync.WaitGroup
    wg.Add(total)
    now := time.Now()
    for i := 0; i Exécutez-la pour voir l'effet<p></p><pre class="brush:php;toolbar:false">➜ go run timeout.go
elapsed: 2.005725931s
Le délai d'attente a pris effet. Mais est-ce fait ?

fuite de goroutines

Ajoutons une ligne de code à la fin de la fonction principale pour voir combien de goroutines ont été exécutées

time.Sleep(time.Minute*2)fmt.Println("number of goroutines:", runtime.NumGoroutine())
sleep 2 minutes, c'est attendre que tout se termine. La tâche se termine, puis nous imprimons le nombre actuel de goroutines. Exécutons-le et voyons le résultat

➜ go run timeout.go
elapsed: 2.005725931s
number of goroutines: 1001
La goroutine a fui, voyons pourquoi cela se produit ? Tout d'abord, la fonction

se termine après 2 secondes d'expiration. Une fois la fonction requestWork terminée, alors requestWork n'aura pas de goroutine pour la recevoir. Lorsque la ligne de code done channel sera exécutée, elle le sera. bloqué et ne peut pas être écrit. Dans ce cas, chaque demande de délai d'attente occupera toujours une goroutine. Lorsque les ressources sont épuisées, l'ensemble du service ne répond plus. done

Alors comment y remédier ? En fait, c'est très simple. Il suffit de définir

sur 1 lorsque make chan, comme suit : buffer size

done := make(chan error, 1)
De cette façon,

peut écrire sans bloquer la goroutine, qu'elle expire ou non. À ce stade, quelqu'un peut vous demander s'il y aura un problème si vous écrivez sur un canal qui n'a pas de goroutine pour le recevoir. Dans Go, le canal n'est pas comme nos descripteurs de fichiers courants. Il n'a pas besoin d'être fermé, il l'est. juste un objet. done Il sert uniquement à indiquer au destinataire qu'il n'y a rien à écrire, et n'a pas d'autre but. <code>close(channel)

Après avoir modifié cette ligne de code, testons-la à nouveau :

➜ go run timeout.go
elapsed: 2.005655146s
number of goroutines: 1
Le problème de fuite de goroutine est résolu !

la panique ne peut pas être attrapée

Changeons l'implémentation de la fonction

en hardWork

panic("oops")
Modifions la fonction

ainsi que le code à attraper l'exception comme suit : main

go func() {
  defer func() {
    if p := recover(); p != nil {
      fmt.Println("oops, panic")
    }
  }()

  defer wg.Done()
  requestWork(context.Background(), "any")}()
Si vous l'exécutez à ce moment, vous constaterez que la panique ne peut pas être capturée. La raison est que la panique générée dans la goroutine à l'intérieur de

ne peut pas être capturée par d'autres goroutines. . requestWork

La solution est d'ajouter

à requestWork De même, le panicChan de panicChan doit être 1, comme suit : buffer size.

func requestWork(ctx context.Context, job interface{}) error {
    ctx, cancel := context.WithTimeout(ctx, time.Second*2)
    defer cancel()

    done := make(chan error, 1)
    panicChan := make(chan interface{}, 1)
    go func() {
        defer func() {
            if p := recover(); p != nil {
                panicChan <p>改完就可以在 <code>requestWork</code> 的调用方处理 <code>panic</code> 了。</p><h2>
<span class="header-link octicon octicon-link"></span>超时时长一定对吗?</h2><p>上面的 <code>requestWork</code> 实现忽略了传入的 <code>ctx</code> 参数,如果 <code>ctx</code> 已有超时设置,我们一定要关注此传入的超时是不是小于这里给的2秒,如果小于,就需要用传入的超时,<code>go-zero/core/contextx</code> 已经提供了方法帮我们一行代码搞定,只需修改如下:</p><pre class="brush:php;toolbar:false">ctx, cancel := contextx.ShrinkDeadline(ctx, time.Second*2)

Data race

这里 requestWork 只是返回了一个 error 参数,如果需要返回多个参数,那么我们就需要注意 data race,此时可以通过锁来解决,具体实现参考 go-zero/zrpc/internal/serverinterceptors/timeoutinterceptor.go,这里不做赘述。

完整示例

package mainimport (
    "context"
    "fmt"
    "runtime"
    "sync"
    "time"

    "github.com/tal-tech/go-zero/core/contextx")func hardWork(job interface{}) error {
    time.Sleep(time.Second * 10)
    return nil}func requestWork(ctx context.Context, job interface{}) error {
    ctx, cancel := contextx.ShrinkDeadline(ctx, time.Second*2)
    defer cancel()

    done := make(chan error, 1)
    panicChan := make(chan interface{}, 1)
    go func() {
        defer func() {
            if p := recover(); p != nil {
                panicChan <h2>
<span class="header-link octicon octicon-link"></span>更多细节</h2><p>请参考 <code>go-zero</code> 源码:</p>
  • go-zero/core/fx/timeout.go
  • go-zero/zrpc/internal/clientinterceptors/timeoutinterceptor.go
  • go-zero/zrpc/internal/serverinterceptors/timeoutinterceptor.go

项目地址

github.com/tal-tech/go-zero

欢迎使用 go-zerostar 支持我们!

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer