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

前情提要

上一节我们完成了如下目标
1 完成kafka消息读写
2 借助tailf实现文件监控,并模拟测试实时写文件以及文件备份时功能无误。

本节目标

1 编写系统结构,在主函数中加载配置
2 管理配置文件,实现热更新

实现文件管理,支持热更新

golang中vipper库提供了配置文件的读取和监控功能,我们可以监控配置文件从而实现热更新。
先规划下日志采集系统的目录结构

logcatchsys为项目根目录,其下logcatchsys文件夹中main.go为系统启动的主函数,该文件加载配置,根据配置启动协程,监控指定目录的日志文件,当配置更新时,main做热更新,如果路径从配置中删除,则中止对应的监控协程。如果有新的路径添加到配置文件,则启动协程监控,如果路径有修改,则中止原路径协程,启动新的协程监听修改后的路径。
logconfig为配置存放的路径,logconfig.go主要负责配置的管理,包括监控。

var onceLogConf sync.Once

type ConfigData struct {
ConfigKey string
ConfigValue string
ConfigCancel context.CancelFunc
}

在logconfig.go中定义了once操作的变量onceLogConf,该变量保证监控配置的协程退出后只执行一次析构。

ConfigData结构体存储了配置文件中路径的信息,ConfigKey表示路径名,ConfigValue表示路径值,ConfigCancel存储上下文的CancelFunc,因为一个路径对应一个日志文件,监控日志文件就要开启协程,我是通过context管理监控日志的协程的。
在config.yaml中记录的路径信息如下:

configpath:
logdir1: "../logdir1/log.txt"
logdir2: "../logdir2/log.txt"
logdir3: "../logdir3/log.txt"
logdir5: "../logdir3/log.txt"

logdir1对应ConfigKey

../logdir1/log.txt对应ConfigValue
接下来在logconfig.go中我实现了配置文件的加载

func ReadConfig(v *viper.Viper) (interface{}, bool) {
//设置读取的配置文件
v.SetConfigName("config")
//添加读取的配置文件路径
_, filename, _, _ := runtime.Caller(0)
fmt.Println(filename)
fmt.Println(path.Dir(filename))
v.AddConfigPath(path.Dir(filename))
//设置配置文件类型
v.SetConfigType("yaml")
if err := v.ReadInConfig(); err != nil {
fmt.Printf("err:%s\n", err)
return nil, false
} configPaths := v.Get("configpath")
if configPaths == nil {
return nil, false
} return configPaths, true
}

以及配置文件的监听

func WatchConfig(ctx context.Context, v *viper.Viper, pathChan chan interface{}) {

	defer func() {
onceLogConf.Do(func() {
fmt.Println("watch config goroutine exit")
if err := recover(); err != nil {
fmt.Println("watch config goroutine panic ", err)
}
close(pathChan)
})
}() //设置监听回调函数
v.OnConfigChange(func(e fsnotify.Event) {
//fmt.Printf("config is change :%s \n", e.String())
configPaths := v.Get("configpath")
if configPaths == nil {
return
}
pathChan <- configPaths
})
//开始监听
v.WatchConfig()
//信道不会主动关闭,可以主动调用cancel关闭
<-ctx.Done()
}

当配置文件config.yaml有变动时,OnConfigChange传入的匿名函数会触发,从而将configpath节点的value传入chan中,这样main函数可以从外部获取最新的配置文件。

ctx为上下文,当main函数执行上下文中止时,监控配置的协程会自动退出。

根据配置变动,实现热更新

在main.go中,定义了mainOnce控制主协程资源析构,并且通过ConstructMgr全局函数构造configMgr这样的map记录最新的配置信息。

var mainOnce sync.Once
var configMgr map[string]*logconfig.ConfigData 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)
_, cancel := context.WithCancel(context.Background())
configData.ConfigCancel = cancel
configMgr[conkey] = configData
}
}

接下来实现主函数

func main() {
v := viper.New()
configPaths, confres := logconfig.ReadConfig(v)
if configPaths == nil || !confres {
fmt.Println("read config failed")
return
}
configMgr = make(map[string]*logconfig.ConfigData)
ConstructMgr(configPaths)
ctx, cancel := context.WithCancel(context.Background())
pathChan := make(chan interface{})
go logconfig.WatchConfig(ctx, v, pathChan)
defer func() {
mainOnce.Do(func() {
if err := recover(); err != nil {
fmt.Println("main goroutine panic ", err) // 这里的err其实就是panic传入的内容
}
cancel()
})
}()

  

在主函数中读取配置,并且将配置的路径信息存储在configMgr中。接着启动了一个协程用来监控配置文件,并且我实现了主协程的资源回收。

我们在main中继续添加接受监控协程的数据逻辑。

for {
select {
case pathData, ok := <-pathChan:
if !ok {
return
}
fmt.Println("main goroutine receive pathData")
fmt.Println(pathData)
pathDataNew := pathData.(map[string]interface{}) 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)
_, cancel := context.WithCancel(context.Background())
configData.ConfigCancel = cancel
configMgr[conkey] = configData
continue
} if oldval.ConfigValue != conval.(string) {
oldval.ConfigValue = conval.(string)
oldval.ConfigCancel()
_, cancel := context.WithCancel(context.Background())
oldval.ConfigCancel = cancel
continue
} } for mgrkey, mgrval := range configMgr {
fmt.Println(mgrkey)
fmt.Println(mgrval)
}
}
}
}

  

主协程接受数据后对比新旧数据,将旧的配置中被删除的路径剔除,增加和修改新的路径。

日志监控留给之后处理,这里打印下更新后的配置信息。
整体运行下main函数,然后我们手动修改config.yaml,将logdir4修改为logdir5,可以看到如下信息

证明我们的热更新处理逻辑没有问题。下一节基于现有的逻辑,添加日志文件的监控处理,启动多个协程管理日志文件。
源码下载
https://github.com/secondtonone1/golang-/tree/master/logcatchsys
我的公众号

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

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

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

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

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

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

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

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

    前情回顾 前文我们完成了如下目标1 项目架构整体编写2 使框架支持热更新 本节目标 在前文的框架基础上,我们1 将之前实现的日志监控功能整合到框架中.2 一个日志对应一个监控协程,当配置热更新后根据新 ...

  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. PAT乙级1039

    题目链接 https://pintia.cn/problem-sets/994805260223102976/problems/994805283241443328 题解 用两个字符串表示两个箱子,一 ...

  2. js实现QQ跳转到支付宝APP并领取红包!附:动图demo

    前天我在sg开源了js实现微信跳转到支付宝并领红包的代码.https://segmentfault.com/a/11...于是朋友圈开始刷屏,各种套路,各种标题,再附上短链接,引起了很多人的好奇,然后 ...

  3. 【EXE报错】win10运行C#程序保存报错:HTTP 无法注册URL ,进程不具有此命名空间的访问权限

    在win10系统运行C#程序出现以下报错 异常信息 [1]异常信息:HTTP 无法注册 URL http://+:13000/Core/Real/HandheldService/.进程不具有此命名空间 ...

  4. 多个idea项目使用同一个tomcat

    配置好tomcat后,每个项目使用不同的虚拟路径.并且把tomcat端口改为不一样的就可以了.

  5. [Luogu] 引水入城

    https://www.luogu.org/problemnew/show/P1514 bfs + 线段覆盖 #include<bits/stdc++.h> using namespace ...

  6. bbs--点赞

    bbs---点赞 需求分析 页面展示 1 点赞  和   踩灭  按钮展示 1 用户未登录,不处理点赞踩灭,给用户提供登录接口 2 登录 1 第一次点点赞/踩灭 1 点赞成功 数据+1 提示点赞成功 ...

  7. 第三章 python数据规整化

    本章概要 1.去重 2.缺失值处理 3.清洗字符型数据的空格 4.字段抽取 去重 把数据结构中,行相同的数据只保留一行 函数语法: drop_duplicates() #导入pandas包中的read ...

  8. 用python来抓取“煎蛋网”上面的美女图片,尺度很大哦!哈哈

    所用Python环境为:python 3.3.2   用到的库为:urllib.request    re 废话不多说,先上代码: import urllib.request import re #获 ...

  9. Python常用模块之hashlib模块

    1.hashilib模块的功能 python的hashlib提供了常见的摘要算法,如MD5, SHA1等等. 什么是摘要算法呢?摘要算法又称哈希算法.散列算法.它通过一个函数,把任意长度的数据转换成一 ...

  10. 测试Promise与Async/await的基本使用

    想在项目中用, 发现自己不是很熟 promise基本使用 基本使用-思路 new Promise()返回了一个状态机 一个完全无法被外界影响的状态机 构造函数, 传入一个函数, 两个参数, 分别是re ...