前情回顾

前文我们完成了如下目标
1 项目架构整体编写
2 使框架支持热更新

本节目标

在前文的框架基础上,我们
1 将之前实现的日志监控功能整合到框架中。
2 一个日志对应一个监控协程,当配置热更新后根据新配置动态关闭和启动协程。
3 编写测试代码,模拟向文件中不断写入日志,并备份日志,观察监控功能是否健壮。

增加协程监控日志文件

我们将之前实现的日志监控功能整合到现有框架,文件结构如下

logdir为存储日志的文件夹,模拟不同系统记录的日志。实际生产中不同系统会自己记录日志并保存在指定文件夹中,logdir模拟的就是这些指定文件。
logtailf实现日志文件的监控功能。
writefile模拟不同系统向指定文件夹写入日志,用来测试日志写入和备份时,我们的采集系统是否健壮。
我们先看下logtailf.go

func WatchLogFile(datapath string, ctx context.Context) {
fmt.Println("begin goroutine watch log file ", datapath)
tailFile, err := tail.TailFile(datapath, tail.Config{
//文件被移除或被打包,需要重新打开
ReOpen: true,
//实时跟踪
Follow: true,
//如果程序出现异常,保存上次读取的位置,避免重新读取
Location: &tail.SeekInfo{Offset: 0, Whence: 2},
//支持文件不存在
MustExist: false,
Poll: true,
}) if err != nil {
fmt.Println("tail file err:", err)
return
} for true {
select {
case msg, ok := <-tailFile.Lines:
if !ok {
fmt.Printf("tail file close reopen, filename: %s\n", tailFile.Filename)
time.Sleep(100 * time.Millisecond)
continue
}
//fmt.Println("msg:", msg)
//只打印text
fmt.Println("msg:", msg.Text)
case <-ctx.Done():
fmt.Println("receive main gouroutine exit msg")
fmt.Println("watch log file ", datapath, " goroutine exited")
return
} }
}

只有一个函数WatchLogFile用来监控指定路径的日志文件,两个参数分别为日志路径和上下文开关,开关用来关闭这个协程,整体功能和前几篇讲述的一样,不做赘述。
接下来我们在main.go中填加协程启动逻辑监控日志文件

func ConstructMgr(configPaths interface{}) {
configDatas := configPaths.(map[string]interface{})
for conkey, confval := range configDatas {
configData := new(logconfig.ConfigData)
configData.ConfigKey = conkey
configData.ConfigValue = confval.(string)
ctx, cancel := context.WithCancel(context.Background())
configData.ConfigCancel = cancel
configMgr[conkey] = configData
go logtailf.WatchLogFile(configData.ConfigValue,
ctx)
}
}

ConstructMgr新增了协程启动逻辑,并且将协程的ctx保存在map中,这样主协程可以根据热更新启动和关闭这个协程。然后我完善了main函数中的析构函数

defer func() {
mainOnce.Do(func() {
if err := recover(); err != nil {
fmt.Println("main goroutine panic ", err) // 这里的err其实就是panic传入的内容
}
cancel()
for _, oldval := range configMgr {
oldval.ConfigCancel()
}
configMgr = nil
})
}()

  析构函数里遍历map,关闭所有监控协程,并且将map设置为nil回收资源。在main函数中添加热更新控制协程开关。

 

for oldkey, oldval := range configMgr {
_, ok := pathDataNew[oldkey]
if ok {
continue
}
oldval.ConfigCancel()
delete(configMgr, oldkey)
} for conkey, conval := range pathDataNew {
oldval, ok := configMgr[conkey]
if !ok {
configData := new(logconfig.ConfigData)
configData.ConfigKey = conkey
configData.ConfigValue = conval.(string)
ctx, cancel := context.WithCancel(context.Background())
configData.ConfigCancel = cancel
configMgr[conkey] = configData
fmt.Println(conval.(string))
go logtailf.WatchLogFile(configData.ConfigValue,
ctx)
continue
} if oldval.ConfigValue != conval.(string) {
oldval.ConfigValue = conval.(string)
oldval.ConfigCancel()
ctx, cancel := context.WithCancel(context.Background())
oldval.ConfigCancel = cancel
go logtailf.WatchLogFile(conval.(string),
ctx)
continue
}
}

  

对比新旧配置,如果路径取消,则停止监控其的协程,如果路径新增或修改,则启动新的协程。
这样我们启动程序看到如下效果

编写测试文件,模拟日志写入和备份

我们在writefile.go中实现日志写入和备份,这部分内容前几篇有讲解,我只是将原来实现的功能搬过来

func writeLog(datapath string, wg *sync.WaitGroup) {
filew, err := os.OpenFile(datapath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
fmt.Println("open file error ", err.Error())
return
}
defer func() {
wg.Done()
}()
w := bufio.NewWriter(filew)
for i := 0; i < 20; i++ {
timeStr := time.Now().Format("2006-01-02 15:04:05")
fmt.Fprintln(w, "Hello current time is "+timeStr)
time.Sleep(time.Millisecond * 100)
w.Flush()
}
logBak := time.Now().Format("20060102150405") + ".txt"
logBak = path.Join(path.Dir(datapath), logBak)
filew.Close()
err = os.Rename(datapath, logBak)
if err != nil {
fmt.Println("Rename error ", err.Error())
return
}
} func main() {
v := viper.New()
configPaths, confres := logconfig.ReadConfig(v)
if !confres {
fmt.Println("config read failed")
return
}
wg := &sync.WaitGroup{} for _, confval := range configPaths.(map[string]interface{}) {
wg.Add(1)
go writeLog(confval.(string), wg)
}
wg.Wait()
}

  

根据配置文件启动多个协程,向日志文件中不断写入日志,写入20条后备份日志,我们启动日志收集程序和这个测试程序,可以看到日志不断被写入,日志收集程序不断打印日志新增的内容

日志备份为不同的文件

可以看到日志收集系统在日志不断写入时,可以健壮运行

热更新控制监控协程打开和关闭

现在config.yaml文件中路径记录如下

configpath:
logdir1: "D:/golangwork/src/golang-/logcatchsys/logdir1/log.txt"
logdir2: "D:/golangwork/src/golang-/logcatchsys/logdir2/log.txt"
logdir3: "D:/golangwork/src/golang-/logcatchsys/logdir3/log.txt"

  我们启动日志收集程序,模拟热更新配置,将logdir3这条数据修改, 配置变为如下

configpath:
logdir1: "D:/golangwork/src/golang-/logcatchsys/logdir1/log.txt"
logdir2: "D:/golangwork/src/golang-/logcatchsys/logdir2/log.txt"
logdir4: "D:/golangwork/src/golang-/logcatchsys/logdir4/log.txt"

  

我们可以看到程序作出检测,并关闭了曾经监控logdir3的协程,并启动了新的协程监控logdir4,而其他的日志监控协程不受影响,正常运行。

源码下载地址
https://github.com/secondtonone1/golang-/tree/master/logcatchsys
我的公众号

Go项目实战:打造高并发日志采集系统(四)的更多相关文章

  1. Go项目实战:打造高并发日志采集系统(一)

    项目结构 本系列文章意在记录如何搭建一个高可用的日志采集系统,实际项目中会有多个日志文件分布在服务器各个文件夹,这些日志记录了不同的功能.随着业务的增多,日志文件也再增多,企业中常常需要实现一个独立的 ...

  2. Go项目实战:打造高并发日志采集系统(六)

    前情回顾 前文我们完成了日志采集系统的日志文件监控,配置文件热更新,协程异常检测和保活机制. 本节目标 本节加入kafka消息队列,kafka前文也介绍过了,可以对消息进行排队,解耦合和流量控制的作用 ...

  3. Go项目实战:打造高并发日志采集系统(二)

    日志统计系统的整体思路就是监控各个文件夹下的日志,实时获取日志写入内容并写入kafka队列,写入kafka队列可以在高并发时排队,而且达到了逻辑解耦合的目的.然后从kafka队列中读出数据,根据实际需 ...

  4. Go项目实战:打造高并发日志采集系统(三)

    前文中已经完成了文件的监控,kafka信息读写,今天主要完成配置文件的读写以及热更新.并且规划一下系统的整体结构,然后将之前的功能串起来形成一套完整的日志采集系统. 前情提要 上一节我们完成了如下目标 ...

  5. Go项目实战:打造高并发日志采集系统(五)

    前情回顾 前文我们完成了如下功能1 根据配置文件启动多个协程监控日志,并启动协程监听配置文件.2 根据配置文件热更新,动态协调日志监控.3 编写测试代码,向文件中不断写入日志并备份日志,验证系统健壮性 ...

  6. 《实战java高并发程序设计》源码整理及读书笔记

    日常啰嗦 不要被标题吓到,虽然书籍是<实战java高并发程序设计>,但是这篇文章不会讲高并发.线程安全.锁啊这些比较恼人的知识点,甚至都不会谈相关的技术,只是写一写本人的一点读书感受,顺便 ...

  7. 《实战Java高并发程序设计》读书笔记

    文章目录 第二章 Java并行程序基础 2.1 线程的基本操作 2.1.1 线程中断 2.1.2 等待(wait)和通知(notify) 2.1.3 等待线程结束(join)和谦让(yield) 2. ...

  8. 【实战Java高并发程序设计 7】让线程之间互相帮助--SynchronousQueue的实现

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

  9. 【实战Java高并发程序设计6】挑战无锁算法:无锁的Vector实现

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

随机推荐

  1. marquee标签实现跑马灯效果--无缝滚动

    今天在做微信端的大转盘抽奖时,想把所有用户的抽奖记录做成无缝滚动的效果,无奈我的js功底太差,一时想不出实现的方法,便百度各种相似效果.但无意中发现了一个html标签——<marquee> ...

  2. BZOJ3032 七夕祭[中位数]

    发现是一个类似于“纸牌均分”的问题.然后发现,只要列数整除目标.行数整除目标就一定可以. 如果只移动列,并不会影响行,也就是同一行不会多不会少.只移动行同理. 所以可以把两个问题分开来看,处理起来互不 ...

  3. mysql_config_editor设置

    [root@node01 etc]# mysql_config_editor set -G mysql3307 -S /tmp/mysql3307.sock -uroot -pEnter passwo ...

  4. “景驰科技杯”2018年华南理工大学程序设计竞赛 A. 欧洲爆破(思维+期望+状压DP)

    题目链接:https://www.nowcoder.com/acm/contest/94/A 题意:在一个二维平面上有 n 个炸弹,每个炸弹有一个坐标和爆炸半径,引爆它之后在其半径范围内的炸弹也会爆炸 ...

  5. git下载代码的两种方式以及eclipse集成git

    1.第一种使用tortoiseGit插件: 链接:https://pan.baidu.com/s/1ANDydwfaaVcUaqZDJWc_BQ 提取码:qgxt a.首先在setting中的Git中 ...

  6. MyBatis插件原理

    官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#plugins MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认 ...

  7. Java中判断两个Long类型是否相等

    在项目中将两个long类型的值比较是否相等,结果却遇到了疑问? 下面就陪大家看看一个神奇的现象! 1.1问题?为什么同样的类型,同样的值,却不相等呢? 1.2那么我们就需要探索一下源码了 源码中显示, ...

  8. jQuery系列(十四):jQuery中的ajax

    1.什么是ajax AJAX = 异步的javascript和XML(Asynchronous Javascript and XML) 简言之,在不重载整个网页的情况下,AJAX通过后台加载数据,并在 ...

  9. 人脸检测之Haar-like,Adaboost,级联(cascade)

    最新版本整理完毕,见: http://face2ai.com/MachineLearning-Haar-like-Adaboost-cascade 0:写在前面的话           写在前面的牢骚 ...

  10. Postman请求运行顺序及Workflow

    作为一款接口调试利器, Postman的更新迭代速度很快, 不断加入了很多新的功能.使的api设计,测试,监控, Mock,以及团队协作更加方便. 修改执行顺序 在遇到有接口依赖的情况, 我们往往需要 ...