沙滩星空的博客沙滩星空的博客

Golang程序的优雅退出

package main
import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)
 
func Logic() {
    for {
        time.Sleep(time.Second*2)
        fmt.Println("logic time=",time.Now().Unix())
    }
}
func main() {
    // 逻辑处理协程
    go Logic()
 
    // 监控两个信号
    // TERM信号(kill + 进程号 触发)
    // 中断信号(ctrl + c 触发)
    osc := make(chan os.Signal, 1)
    signal.Notify(osc,syscall.SIGTERM, syscall.SIGINT)
    s := <- osc
    fmt.Println("监听到退出信号,s=",s)
 
    // 退出前的清理操作
    // clean()
 
    fmt.Println("main程序退出")
}

通过go1.16+的NotifyContext方法和errgroup包实现服务的优雅停止

package main

import (
    "context"
    "errors"
    "fmt"
    "github.com/gin-gonic/gin"
    "golang.org/x/sync/errgroup"
    "log"
    "net/http"
    "os/signal"
    "syscall"
    "time"
)

type ginHttp struct {
    server *http.Server
}

func NewGinHttpServer(listenIp string, listenPort int, f func(engine *gin.Engine)) *ginHttp {
    engine := gin.Default()
    f(engine)
    s := &http.Server{
        Addr:    fmt.Sprintf("%s:%d", listenIp, listenPort),
        Handler: engine,
    }

    return &ginHttp{
        server: s,
    }
}

func (g *ginHttp) Serve() <-chan error {
    // chan长度大于等于1, 不然errch不存在读取取,将永远阻塞在这里,造成goroutine泄露
    errch := make(chan error, 1)
    go func() {
        err := g.server.ListenAndServe()
        if err != nil {
            errch <- err
            log.Printf("服务停止:%v", err)
        }
        close(errch)
    }()

    return errch
}

func (g *ginHttp) Stop() error {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    defer cancel()
    return g.server.Shutdown(ctx)
}

func main() {
    // 创建通过监听信号syscall.SIGINT, syscall.SIGEMT来停止的context
    ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGEMT)
    defer cancel()

    // 通过errgroup来运行多个服务
    g, ctx := errgroup.WithContext(ctx)

    // 服务一
    g.Go(func() error {
        tr := time.NewTimer(time.Second * 30)
        select {
        // 监听context是否被取消, 取消则终止服务
        case <-ctx.Done():
            /*
                停止服务操作
                stop()
            */
            log.Println("收到context取消信号,停止服务一")
            return ctx.Err()

        // 模拟服务
        case <-tr.C:
            // 模拟30秒后服务出错返回, errgroup会对errgroup.WithContext返回的context进行取消
            log.Println("服务一出错: xxx")
            return errors.New("xxx")
        }
    })

    // 服务二: 基于gin框架的web服务
    g.Go(func() error {
        ginSvr := NewGinHttpServer("0.0.0.0", 8080, func(engine *gin.Engine) {
            engine.GET("/hello", func(c *gin.Context) {
                c.String(http.StatusOK, "Hello World!")
            })
        })
        select {
        // 监听context是否被取消, 取消则终止服务
        case <-ctx.Done():
            //停止服务操作
            log.Println("收到context取消信号,停止服务二")
            err := ginSvr.Stop()
            if err != nil {
                log.Printf("停止服务二出错: %v", err)
            } else {
                log.Println("成功停止服务二")
            }
            return ctx.Err()

        // http服务
        case err := <-ginSvr.Serve():
            // 如果服务完成并未出错, errgroup不会对返回的context进行取消操作
            if err != nil {
                log.Printf("服务二出错: %v", err)
            }
            return err
        }
    })

    // 等待所有服务完成,或者某个服务报错并终止所有服务
    err := g.Wait()
    log.Printf("程序退出:%v", err)
}

退出程序不应使用 kill -9 暴力杀死程序, 因为此动作触发的SIGKILL信号无法被处理

golang程序如何优雅地退出 https://blog.csdn.net/yzf279533105/article/details/117791707
Golang并发模型:并发协程的优雅退出 https://studygolang.com/articles/16646
Golang信号处理和优雅退出守护进程 https://studygolang.com/articles/10076
go程序优雅退出 https://jageros.github.io/note/docs/golang/grace-exit/
未经允许不得转载:沙滩星空的博客 » Golang程序的优雅退出

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址