Maison  >  Article  >  développement back-end  >  Découvrez comment implémenter un déploiement à chaud dans les applications Golang

Découvrez comment implémenter un déploiement à chaud dans les applications Golang

PHPz
PHPzoriginal
2023-04-05 13:50:02944parcourir

Dans le développement logiciel, le déploiement à chaud est une technologie très importante, qui nous permet de mettre à jour l'application au moment de l'exécution sans interrompre le fonctionnement. Au cours de ce processus, nous pouvons préserver l'état de l'application et déployer de nouvelles versions de l'application mise à jour sur nos serveurs sans aucun temps d'arrêt.

Ces dernières années, un langage de programmation appelé Golang est devenu de plus en plus populaire. Golang est un projet initié par Google. Son objectif de conception est de rendre plus facile et plus efficace l'écriture d'applications hautes performances et haute fiabilité. La fonctionnalité de déploiement à chaud de Golang est un sujet passionnant. Explorons comment implémenter le déploiement à chaud dans les applications Golang.

L’une des forces de Golang est son évolutivité naturelle et sa tolérance aux pannes. Ces avantages le rendent idéal pour créer des applications hautement disponibles. Bien que Golang ne puisse pas gérer toutes les erreurs du code avec autant de grâce que certains langages typés dynamiquement, il nous permet de déployer à chaud assez facilement.

Nous pouvons utiliser certains outils de la bibliothèque standard fournie par Golang pour implémenter un déploiement à chaud, notamment signal, reflex, selinux, etc. Le package signal peut être utilisé pour capturer les signaux du système d'exploitation, tandis que le package Reflect nous permet d'inspecter et de modifier le code au moment de l'exécution. Le package selinux peut être utilisé pour gérer la sécurité des applications.

Nous pouvons utiliser le package Reflect pour écrire un très petit exemple d'application. Dans cette application, nous pouvons utiliser le package Reflect pour charger le code d’une autre fonction. Cela nous permet de modifier le code au moment de l'exécution et de mettre à jour l'application.

Voici l'exemple de code :

package main

import (
    "fmt"
    "os"
    "os/signal"
    "reflect"
    "sync"
    "syscall"
    "time"
)

func main() {
    done := make(chan bool)
    wg := sync.WaitGroup{}

    wg.Add(1)
    go Monitor(done, &wg)

    for n := 0; n < 100; n++ {
        fmt.Println(n)
        time.Sleep(1 * time.Second)
    }

    done <- true
    wg.Wait()
}

func Monitor(done chan bool, wg *sync.WaitGroup) {
    defer wg.Done()

    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGHUP)

    for {
        select {
        case <-done:
            return
        case <-c:
            fmt.Println("Reloading...")
            Reload()
            fmt.Println("Reloaded!")
        }
    }
}

func Reload() {
    fmt.Println("Before reload")

    f, err := os.Open("test.go")
    if err != nil {
        fmt.Println(err)
        return
    }

    buf := make([]byte, 1024)
    incompleteLine := ""
    for {
        n, _ := f.Read(buf)
        if n == 0 {
            break
        }
        incompleteLine, _ = checkLines(buf[:n], incompleteLine)
    }

    fmt.Println("Loading new code")
    code := fmt.Sprintf(`package main

import (
    "fmt"
)

func run() {
    fmt.Println("New code is running")
}
`)
    Set("run", code)
}

func checkLines(buf []byte, incompleteLine string) (string, error) {
    line := incompleteLine + string(buf)

    complete := true
    for j := 0; j < len(line); j++ {
        if line[j] == '\n' {
            //run complete line...
            fmt.Println(line[:j])
            complete = false
        }
    }
    if complete {
        return "", nil
    }
    return line, nil
}

func Set(funcName string, code string) {
    codeVal := reflect.ValueOf(&code).Elem()
    ptrToCode := codeVal.Addr().Interface().(*string)

    // use function name as package name
    fn := funcName + ": "

    b := []byte(*ptrToCode)
    _, err := Compile(fn, b)
    if err != nil {
        fmt.Println(err)
    }

    // create a new function value of the same type as Run
    v := reflect.MakeFunc(reflect.TypeOf(Run), Compile(fn, b))

    // copy it in
    f := reflect.ValueOf(Run).Elem()
    f.Set(v)
}

func Compile(fn string, src []byte) (func(), error) {
    // no optimization means no inlining, etc, which means func values are inherently invalid
    f, err := CompileWithOpt(fn, src, 0)
    if err != nil {
        return nil, err
    }
    return f, nil
}

func CompileWithOpt(fn string, src []byte, opt int) (func(), error) {
    // we'll prepend some code to show the function name on panics
    src = append([]byte("func "+fn+"() {\n"), src...)
    src = append(src, '\n', '}')

    parsed, err := parser.ParseFile(token.NewFileSet(), "", src, parser.AllErrors)

    if err != nil {
        return nil, err
    }

    conf := types.Config{}
    info := &types.Info{}

    pkgs, err := conf.Check("", token.NewFileSet(), []*ast.File{parsed}, info)
    if err != nil {
        return nil, err
    }
    pkg := pkgs

    for _, n := range parsed.Decls {
        fn, ok := n.(*ast.FuncDecl)
        if !ok {
            continue
        }
        if fn.Name.Name != "run" {
            continue
        }
        var buf bytes.Buffer
        if err := printer.Fprint(&buf, token.NewFileSet(), fn); err != nil {
            return nil, err
        }

        fmt.Println("Compile", buf.String())
    }

    code := string(src)
    fn := func() {
        fmt.Println("Before run")

        err = eval(code, pkg, info)
        if err != nil {
            fmt.Println(err)
            return
        }

        fmt.Println("After run")
    }
    return fn, nil
}

func eval(code string, pkg *types.Package, info *types.Info) error {
    fset := token.NewFileSet()
    file, err := parser.ParseFile(fset, "", code, 0)
    if err != nil {
        fmt.Println(err)
        return err
    }

    conf := types.Config{
        Importer: importer.From("gc", nil, types.GcImport),
    }
    checker := types.NewChecker(&conf, fset, pkg, info)

    if _, err := checker.Files([]*ast.File{file}); err != nil {
        fmt.Println(err)
        return err
    }

    // compile/run, like in the previous example
    var buf bytes.Buffer
    if err := printer.Fprint(&buf, fset, file); err != nil {
        return err
    }

    fmt.Println(buf.String())

    return nil
}

func Run() {
    fmt.Println("Current code is running")
}

Dans cet exemple, nous pouvons voir que la fonction Reload() sera appelée lorsque l'application apportera des modifications. La fonction Reload() lit le nouveau code à partir d'un fichier appelé "test.go" et l'ajoute à l'application à l'aide du package Reflect.

Il est important de noter que le chargement d'un nouveau code dans l'application peut impliquer la compilation de code, ce qui aura un certain impact sur les performances de l'application. Cependant, les avantages du déploiement à chaud dépassent de loin les inconvénients en termes de performances.

Avant de terminer, il convient de souligner qu'il ne s'agit que d'un simple exemple de programme. En pratique, un déploiement à chaud doit prendre en compte de nombreux aspects, tels que la complexité de l'application, le nombre de fichiers à mettre à jour et les différentes parties de l'application.

En bref, en utilisant les packages de réflexion et de signal de Golang, nous pouvons facilement mettre en œuvre un déploiement à chaud. Bien que cela puisse avoir un certain impact sur les performances de l'application, cette technique nous permet de mettre à jour facilement le code sans fermer l'application.

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:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn