请教下golang goroutine泄露问题?

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

golang新手, 最近在写个练手的小项目, 需求是收集指定日志目录下最新的日志文件, 通过正则过滤日志内容, 把想要的信息存到一个map里.

但是这个程序在实际运行中我发现, 由于在catLog包里读取文件内容时里面我必须使用for死循环实现实时读取文件, 导致在getNewFile包里监控到的系统事件信息无法发送给catLog包,这样如果有新的日志文件产生, 它无法切换文件. 于是我在getNewFile包调用catLog包时启用goroutine, 这样就可以收到通知了.

结果导致每次读取文件新内容时, 程序都会启动一个新的goroutine调用catLog包, 并且不会自动退出,因为catLog包里是for死循环, 一直在累积增加.

请大佬们帮忙看下下面的代码, 该怎么修改可以控制goroutine增长, 并且还能实时监控最新的日志文件.

package getNewFile

import (
    "fmt"
    "log"
    "regexp"
    catlog "study/prometheus/exporter_watcherLog/catLog"
    "time"

    "github.com/fsnotify/fsnotify"
)

// 实时监控指定目录下文件并读取
func GetNewFile(pattren, logDir string) {
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        fmt.Println(err)
        return
    }
    defer watcher.Close()

    done := make(chan bool)

    re := regexp.MustCompile(pattren) // Compile 解析一个正则表达式,如果成功则返回一个可用于匹配文本的 Regexp 对象。如错误会panic
    go func() {
        for {
            select {
            case event := <-watcher.Events:
                isMatch := re.MatchString(event.Name)
                if isMatch {
                    if event.Op&fsnotify.Create == fsnotify.Create { // 监控创建文件动作
                    fmt.Println(event.Name)
                    go catlog.DealLog(event.Name)
                    }

                    if event.Op&fsnotify.Write == fsnotify.Write { // 监控写入文件动作
                    fmt.Println(event.Name)
                    go catlog.DealLog(event.Name)
                    }
                }

            case err := <-watcher.Errors:
                log.Println(err)
            }
            time.Sleep(time.Second * 1)
        }
    }()

    err = watcher.Add(logDir) // 监控指定目录
    if err != nil {
        log.Fatal(err)
    }
    <-done
}
package catlog

import (
    "fmt"
    "regexp"
    "runtime"
    "time"

    "sync"

    "github.com/hpcloud/tail"
)

var AllAddrLoglist map[string]ClassLogInfo
var Sm sync.Map
var Notify = false

type ClassLogInfo struct {
    Host string
    Time string
    Date string
    Cfc  string
}

// 实时读取文件内容, 并筛选出host, date, time, cfc存入map中
func DealLog(file string) {
    config := tail.Config{
        ReOpen:    true,                                 // 重新打开
        Follow:    true,                                 // 是否跟随
        Location:  &tail.SeekInfo{Offset: 0, Whence: 2}, // 从文件哪个地方开始读
        MustExist: false,                                // 文件不存在报错
        Poll:      true,
        // Logger:    tail.DiscardingLogger, // 禁用日志记录
    }
    tails, err := tail.TailFile(file, config) // 打开文件, 并用上面的配置
    if err != nil {
        fmt.Printf("tail file failed, err: %v\n", err)
        return
    }
    var (
        line *tail.Line
        ok   bool
    )

    ch := make(chan struct{}, 1) // 创建缓冲区大小, 控制并发, 最多发送x个消息就阻塞

    // 正则匹配, 最后只筛选出host;date;time;cfc, 其余的跳过.
    pattern := `(^[0-9]+/[0-9]+/[0-9]+)\s+([0-9]+:[0-9]+:[0-9]+:[0-9]+)\s+[A-Z]+\s+\[.*\]\s+INFO\s+LSG.*LSG\s+svr\s+update:\s+cfc=([0-9]+),cpfc=[0-9]+,clc=[0-9]+,cplc=[0-9]+,load=[0-9]+,status=[0-9]+,addr=(\d+\.\d+\.\d+\.\d+-\d),inst=\d+\.\d+\.\d+\.\d+-\d`
    // re := regexp.MustCompile(pattern) // 两个唯一区别, 如错误直接panic
    re, err := regexp.Compile(pattern) // Compile 解析一个正则表达式,如果成功则返回一个可用于匹配文本的 Regexp 对象。如错误会返回一个错误
    if err != nil {
        fmt.Println(err)
    }

    // 读取每行数据,最后写入到AllAddrLoglist中.
    for {
        ch <- struct{}{} // 写入消息到缓冲区

        line, ok = <-tails.Lines
        if !ok {
            fmt.Printf("tail file close reopen, filename: %s\n", tails.Filename)
            time.Sleep(time.Second)
            continue
        }
        matchArr := re.FindStringSubmatch(line.Text)
        if matchArr != nil { // 新增判断,替代re.MatchString(line.Text),原有的影响性能
            // 信息存入结构体
            testI := ClassLogInfo{
                Host: matchArr[4],
                Date: matchArr[1],
                Time: matchArr[2],
                Cfc:  matchArr[3],
            }
            // 利用sync.map, 直接使用, 无需声明, 避免读写同一个map冲突
            Sm.Store(matchArr[4], testI)
            <-ch // 从缓冲区读取消息
        }
    }
}

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

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

3352 次点击  
加入收藏 微博
10 回复  |  直到 2023-03-21 23:46:54
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传