询问一个关于 golang经常自动重启

ccoding · 2023-03-07 16:58:22 · 2111 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2023-03-07 16:58:22 的主题,其中的信息可能已经有所发展或是发生改变。

systemd 守护的一个使用golang 实现的tcp服务, 这个服务使用了github.com/jinzhu/gorm ,每隔一小时 服务就会被重启一次,毫无头绪,无从下手,哪位大神可指点指点,怎么排查


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2111 次点击  
加入收藏 微博
17 回复  |  直到 2023-03-08 16:23:44
jjmgx
jjmgx · #1 · 2年之前

在容易出错的地方要加上容错的机制,使用panic recover机制抓住错误信息就可以定位问题在哪里,然后再解决问题。 个人觉得常出错的地方有使用了nil的数据、数组越界了是最常见的,另外还有一些不容易发现的比如没有关闭链接之类的。 最好是能上源码,如果不关乎行业机密的话。

ccoding
ccoding · #2 · 2年之前

panic 在日志里没有发现,有,我写个伪代码吧

func main() {
     for {
        conn, e := ln.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                logger.Init("异常").Info("接收错误信息:%v" + fmt.Sprintf("accept temp err: %v", ne))
                continue
            }
        }
        // 解析连接消息 路由模式开启
        go goHandleConn(conn)
    }
}

func goHandleConn(conn net.Conn) {
defer func() {
        if conn != nil {
            conn.Close()
        }
        if err := recover(); err != nil {
            //获取程序名称
            appExeName := os.Args[0]
            pid := os.Getegid()
            logger.Init("POSP中台").Error("goHandleConn:引发panic异常",
                zap.Reflect("error", err),
                zap.Reflect("app", appExeName),
                zap.Reflect("pid", pid),
                zap.Reflect("strack", string(debug.Stack())),
                zap.Reflect("LogId", logId),
            )
            notice.NoticeMsg("goHandleConn:引发panic异常", fmt.Sprintf("LogId:%s %v:app:%v:pid:%v:strack:", logId, err, appExeName, pid, string(debug.Stack())))
        }
        return
    }()

      // todo 查询数据库 这个地方用到gorm

      _, err = conn.Write(recBuf)
     return 
}
ccoding
ccoding · #3 · 2年之前
//设置数据库连接池最大连接数
    _db.DB().SetMaxOpenConns(setMaxOpenConns)
    //连接池最大允许空闲连接数,如果没有sql任务需要执行的连接数大于指定的值,超过连接会被连接池关闭
    _db.DB().SetMaxIdleConns(setMaxIdleConns)
​
ccoding
ccoding · #4 · 2年之前
jjmgxjjmgx #1 回复

在容易出错的地方要加上容错的机制,使用panic recover机制抓住错误信息就可以定位问题在哪里,然后再解决问题。 个人觉得常出错的地方有使用了nil的数据、数组越界了是最常见的,另外还有一些不容易发现的比如没有关闭链接之类的。 最好是能上源码,如果不关乎行业机密的话。

上面是我伪代码,非常奇怪,定时重启,一个小时来一下

ccoding
ccoding · #5 · 2年之前

image.png 监控来看并未出现明显异常的内存,或者资源波动在重启的时候

ccoding
ccoding · #6 · 2年之前

我现在怀疑是不是跟这个gorm有一定的关系,我没有加

_db.DB().SetConnMaxLifetime(time.Hour)

上面这行代码我没有加

tuzhiya
tuzhiya · #7 · 2年之前

自动重启?怎么知道重启的

ccoding
ccoding · #8 · 2年之前
tuzhiyatuzhiya #7 回复

自动重启?怎么知道重启的

我记录了一个日志,_db.DB().SetConnMaxLifetime(time.Hour)这个我已经验证过了,跟它没有关系,我加上了以后还是会重启,

另外我日志使用了 gopkg.in/natefinch/lumberjack.v2 来记录的日志

我TCP监听服务在启动的时候会 输入一行日志

类似这样:

logger.Init("PProf").Info("开始启动PProf监控", zap.Reflect("port", port))

我现在在逐步的用最笨的方式,挨个移除模块,现在已经把mysql连接池移除了,观察是否还会重启

ccoding
ccoding · #9 · 2年之前

刚又出现了,伴随着的大概率就会出现CPU 爆满,刚想用pprof查线下,结果连pprof都异常了,干瞪眼儿了。。。。

saberlong
saberlong · #10 · 2年之前

如果不确定是不是未捕获的panic导致的话。加个环境变量GOTRACEBACK=crash后运行。这样崩溃后会产生croedump。然后用gdb或dlv还原现场。注意崩溃时内存占用大小,这个和croedump大小有关。

如果不是panic,那么通过dmesg查看系统日志

jan-bar
jan-bar · #11 · 2年之前

神奇的现象,我遇到所有go程序重启都会有panic打印,很快就能发现问题。难道说你这个程序时操作系统杀死,go进程内部还没来得急出现panic逻辑么?我觉得你可以捕获一些常用的信号,捕获成功加上打印试试。只要不是kill -9 pid这种信号,都可以捕获成功吧。

jjmgx
jjmgx · #12 · 2年之前

todo里面如果有goroutine,就将goroutine里的代码研究下。因为recover应该是不能抓到子协程的错误的。

xwszt
xwszt · #13 · 2年之前

1、为了找到原因,笨的方法就是在每个goroutine里加上defer,捕捉recover。 2、避免整个应用宕掉,在goHandleConn(conn)方法里追加defer,捕捉recover,保证for循环不会抛出panic.

ccoding
ccoding · #14 · 2年之前
xwsztxwszt #13 回复

1、为了找到原因,笨的方法就是在每个goroutine里加上defer,捕捉recover。 2、避免整个应用宕掉,在goHandleConn(conn)方法里追加defer,捕捉recover,保证for循环不会抛出panic.

goHandleConn伪代码 已经加上了,没有recover到任何信息

defer func() {
        if conn != nil {
            conn.Close()
        }
        if err := recover(); err != nil {
            //获取程序名称
            appExeName := os.Args[0]
            pid := os.Getegid()
            logger.Init("POSP中台").Error("goHandleConn:引发panic异常",
                zap.Reflect("error", err),
                zap.Reflect("app", appExeName),
                zap.Reflect("pid", pid),
                zap.Reflect("strack", string(debug.Stack())),
                zap.Reflect("LogId", logId),
            )
            notice.NoticeMsg("goHandleConn:引发panic异常", fmt.Sprintf("LogId:%s %v:app:%v:pid:%v:strack:", logId, err, appExeName, pid, string(debug.Stack())))
        }
        return
    }()
ccoding
ccoding · #15 · 2年之前

我现在只能用最原始,最无脑的办法 就是排除法懵,已经单独启动了一个应用,代码很干净,为了排除日志扩展可能得问题,我临时换了一个扩展,就等结果了,如果一个小时候还会重启,那我就崩塌了,我只能怀疑是systemd有问题,就换个别的守护试试

package main

import (
    "fmt"
    "github.com/sirupsen/logrus"
    "io"
    "net"
    "os"
    "runtime"
)

func main() {
    var log = logrus.New()
    log.SetFormatter(&logrus.JSONFormatter{
        TimestampFormat: "2006-01-02 15:04:05",
    })
    log.SetOutput(os.Stdout)
    file, err := os.OpenFile("logruss.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    writers := []io.Writer{
        file,
        os.Stdout}
    //同时写文件和屏幕
    fileAndStdoutWriter := io.MultiWriter(writers...)
    if err == nil {
        log.SetOutput(fileAndStdoutWriter)
    } else {
        log.Info("Failed to log to file")
    }
    log.WithFields(logrus.Fields{"tips": "服务器启动连接监听", "CPU": runtime.NumCPU()}).Info("POSP")
    ln, err := net.Listen("tcp", "127.0.0.1:8888")
    if err != nil {
        logrus.Error("服务异常", logrus.Fields{"err": err.Error()})
    }
    for {
        conn, e := ln.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                logrus.Error("接受到错误信息")
                continue
            }
        }
        go goHandlerConn(conn)
    }
}
func goHandlerConn(conn net.Conn) {
    defer func() {
        if conn != nil {
            conn.Close()
        }
        if err := recover(); err != nil {
            logrus.Error("发现异常-1")
        }
        return
    }()
    fmt.Println("我是执行的么")
}
ccoding
ccoding · #16 · 2年之前

果然,上面的代码也重启了。。。。

{"CPU":4,"level":"info","msg":"POSP","time":"2023-03-08 12:12:13","tips":"服务器启动连接监听"}
{"CPU":4,"level":"info","msg":"POSP","time":"2023-03-08 13:12:23","tips":"服务器启动连接监听"}
ccoding
ccoding · #17 · 2年之前

问题已解决,感谢楼上的各位的热心帮助,下面是我得解决方法,确定问题就在于systemd,当启动的时候 虽然日志成功,但是systemd并未收到信号量running,这个时候会启动一个一小时启动一次的方式,这个感兴趣的可以详细的了解

//导入一个包
go get github.com/coreos/go-systemd/daemon
//看门狗,不用纠结logger,这个换成自己的日志输出或者,fmt打印都可以,每隔5秒告诉一次systemd健康状态,这个要使用 go 单独启动一个协成来执行
func main(){
    //TDDO启动TCP服务监听
   //启动看门狗
  go goWatchSignal()
   for {
        conn, e := ln.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                logrus.Error("接受到错误信息")
                continue
            }
        }
        go goHandlerConn(conn)
    }
}

func goWatchSignal() {

    //发送运行信号通知
    _, err := daemon.SdNotify(false, daemon.SdNotifyReady)
    if err != nil {
        logger.Init("WatchSignal").Warn("SdNotifyReady-发送运行信号失败", zap.Reflect("err", err.Error()))
    }
    for {
        _, er := daemon.SdNotify(false, daemon.SdNotifyWatchdog)
        if er != nil {
            logger.Init("WatchSignal").Warn("SdNotifyWatchdog-发送指令异常", zap.Reflect("err", err.Error()))
        }
        time.Sleep(5 * time.Second)
    }
}
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传