Maison >développement back-end >Golang >Requêtes multithread en cours et n'obtenant pas de RPS élevé

Requêtes multithread en cours et n'obtenant pas de RPS élevé

WBOY
WBOYavant
2024-02-09 16:48:10991parcourir

go 中的多线程请求并且没有获得高 RPS

L'éditeur PHP Xigua a remarqué que lors de l'utilisation du langage Go pour effectuer des requêtes multithread, il n'est parfois pas possible d'obtenir une vitesse de requête par seconde (RPS) élevée. Bien que le langage Go soit excellent dans le traitement simultané, dans certains cas, les requêtes multithread ne sont pas très efficaces. Cela peut être dû à la latence du réseau, à la concurrence entre les ressources, etc. Dans cet article, nous allons explorer ce problème et proposer quelques solutions possibles pour améliorer le RPS des requêtes multithread en langage Go.

Contenu de la question

J'essaie d'écrire un client multithread pour tester mon serveur. Lorsque j'utilise 2 goroutines, tout va bien, j'obtiens 50 000 rps et ma charge CPU est normale, mais lorsque je crée plus de 2 goroutines, le rps tombe à 3 000 mais ma charge CPU dépasse. Bien que lorsque j'exécute le code client plusieurs fois (comme exécuter le même code sur 3 consoles en même temps), j'obtiens plus de rps, comme 80 000 rps.

Voici mon code client

package main

import (
    "fmt"
    "net/http"
    "os"
    "sync"
    "time"
)

func main() {
    requesturl := fmt.sprintf("http://localhost:%d/home", 3333)
    var wg sync.waitgroup
    wg.add(4)
    req, err := http.newrequest(http.methodget, requesturl, nil)
    if err != nil {
        fmt.printf("client: could not create request: %s\n", err)
        os.exit(1)
    }
    for i := 0; i < 4; i++ {
        go func() {
            defer wg.done()
            client := http.client{
                timeout: 30 * time.second,
            }
            for {
                client.do(req)
            }
        }()
    }
    wg.wait()
}

Voici mon code côté serveur

package main

import (
    "errors"
    "fmt"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "log"
    "net/http"
    "os"
    "sync"
)

// log handling
func openlogfile(path string) (*os.file, error) {
    logfile, err := os.openfile(path, os.o_wronly|os.o_append|os.o_create, 0644)
    if err != nil {
        return nil, err
    }
    return logfile, nil
}

// variables of counter in metric
var okstatuscounter = prometheus.newcounter(
    prometheus.counteropts{
        name: "ok_request_count",
        help: "number of 200",
    },
)

func listener(serverlog *log.logger) http.handlerfunc {
    return func(w http.responsewriter, r *http.request) {
        //metric 
        okstatuscounter.inc()
        
        w.writeheader(http.statusok)
    }
}

func main() {
    //metric
    prometheus.mustregister(okstatuscounter)

    //log handling
    filesimpleserverlog, err := openlogfile("simpleserver/simpleserverlog.log")
    if err != nil {
        log.fatal(err)
    }
    serverlog := log.new(filesimpleserverlog, "[simple server]", log.lstdflags|log.lshortfile|log.lmicroseconds)

    var wg sync.waitgroup
    wg.add(1)

    //server:
    go func() {
        defer wg.done()
        mux := http.newservemux()
        mux.handlefunc("/home", listener(serverlog))
        mux.handle("/metrics", promhttp.handler())
        server := http.server{
            addr:    fmt.sprintf(":%d", 3333),
            handler: mux,
        }
        if err := server.listenandserve(); err != nil {
            if !errors.is(err, http.errserverclosed) {
                serverlog.printf("error running http server: %s\n", err)
            }
        }
    }()
    wg.wait()
}

Au début, je pensais que Go pourrait utiliser un seul port pour toutes les connexions client, mais lorsque je l'ai vérifié avec netstat, il utilisait plusieurs ports. J'ai essayé de chercher mais je n'ai pas trouvé de réponse appropriée

J'ai essayé sync.mutex :

var mu sync.Mutex
...
for i := 0; i < 1000; i++ {
        go func() {
            defer wg.Done()
            client := http.Client{
                //Timeout: 30 * time.Second,
            }
            for {
                mu.Lock()
                _, err := client.Do(req)
                if err != nil {
                    clientLog.Printf("client: error making http request: %s\n", err)
                    os.Exit(1)
                }
                mu.Unlock()
            }
        }()
    }
    wg.Wait()
...

Avec les changements ci-dessus, j'ai obtenu 13 000 rps et la charge de mon processeur était correcte, mais ce n'était tout simplement pas suffisant

Solution de contournement

Puisque vous n'envoyez des requêtes qu'à un seul hôte, la valeur par défaut du transport http ne fonctionnera pas pour toi. Il est préférable de régler les paramètres manuellement en fonction de votre situation :

t := http.DefaultTransport.(*http.Transport).Clone()
t.MaxIdleConns = 100
t.MaxConnsPerHost = 100
t.MaxIdleConnsPerHost = 100
    
httpClient = &http.Client{
  Timeout:   10 * time.Second,
  Transport: t,
}

Pour plus d'informations, vous pouvez lire ici.

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