试问,如何设计一个高并发的golang日志库

TangYiMo · · 1116 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

> golang中有很多优秀的日志库,例如zerolog,see-log,blog4go等。 这些日志首先具备强大的高并发能力,其次具备丰富的接口,输入格式,索引接口。 把外壳去掉,日志库的设计原理本质上是一样的。 **我个人认为一个日志系统需要满足的特点:** - 1 内容不确定(非格式化数据)。为能够方便索引,因此需要额外引入一些namespace和类型,时间戳等的字符串。 - 2 要求并发量大,写入快,也就是日志需要有实时性,不能对现有业务造成阻塞。 因此日志需要设置成带缓冲的功能、多协程。 在日志的生成、粗加工、传递、归类、输出的过程中,减少传递、减少cpu锁、减少拷贝次数/内容 方面下功夫,持久化优化,能提升日志库的性能。 - 3 日志需要一定的格式,因此在输出时需要顺序输出到终端、或日志文件。 落盘需要是一个任务去执行。 - 4 日志文件不能太大,需要定期清理掉无效的日志。 因此日志按天、周为单位,每个单位写入对应以时间戳命名的文件中。 - 5 参照linux系统自带的系统日志,对于历史日志(例如一周之前的日志),需要压缩成压缩文档。删掉非压缩的文档。 - 6 输出目标可以灵活指定,可以是终端,也可以是日志目录,也可以是网络流。 输出格式可以是raw原始内容、也可以是json格式。 - 7 考虑小白用户。例如一些运维人员,vim用的也许不是那么的熟悉,还有一些c端的小白用户估计linux系统都不晓得是啥,因此日志系统如果能带一个简单的web界面,日志索引提供输入框返回结果,这虽然不一定用的上,但是会显得比较完善。 > 补充:本文比较仓促,是以学习原理为目的(我写的文章侧重原理分析 和 实现。 当然,生产环境是另外一套另当别论,我优先考虑用别人写好的组件)。 因此上述7个特点并未在本文全部实现。 *直接上源码:* ```shell package main import ( "context" "fmt" "io" "os" "sync" "time" ) var ( colorOff = []byte("\033[0m") colorRed = []byte("\033[0;31m") colorGreen = []byte("\033[0;32m") colorOrange = []byte("\033[0;33m") colorBlue = []byte("\033[0;34m") colorPurple = []byte("\033[0;35m") colorCyan = []byte("\033[0;36m") colorGray = []byte("\033[0;37m") ) var ( plainFatal = []byte("[FATAL] ") plainError = []byte("[ERROR] ") plainWarn = []byte("[WARN] ") plainInfo = []byte("[INFO] ") plainDebug = []byte("[DEBUG] ") plainTrace = []byte("[TRACE] ") ) var ( MAX_INJECTION = 20000 ) // 着色器 func mixer(data []byte, color []byte) []byte { var result []byte return append(append(append(result, color...), data...), colorOff...) } // Red 将 data 染成红色 func Red(data []byte) []byte { return mixer(data, colorRed) } func Green(data []byte) []byte { return mixer(data, colorGreen) } func Orange(data []byte) []byte { return mixer(data, colorOrange) } func Blue(data []byte) []byte { return mixer(data, colorBlue) } func Purple(data []byte) []byte { return mixer(data, colorPurple) } func Cyan(data []byte) []byte { return mixer(data, colorCyan) } func Gray(data []byte) []byte { return mixer(data, colorGray) } type event struct { Plain []byte Color []byte Data []byte } type Logger struct { ctx context.Context start time.Time mu sync.Mutex prefix string cnt int64 fsig chan struct{} ch chan *event pevents *[]*event events [2][]*event out io.Writer rootdir string namespace string oj bool // output as json format } func NewLogger(ctx context.Context, out io.Writer, prefix string, cnt int64, dir string, ns string) *Logger { log := &Logger{ out: out, prefix: prefix, cnt: cnt, fsig: make(chan struct{}, 1), ch: make(chan *event, MAX_INJECTION), ctx: ctx, rootdir: dir, namespace: ns, } log.pevents = &log.events[0] go log.Run() return log } // SetOutput sets the output destination for the logger. func (l *Logger) SetOutput(w io.Writer) { l.mu.Lock() defer l.mu.Unlock() l.out = w } func (l *Logger) Run() { fsync := func() { l.mu.Lock() if len(*l.pevents) == 0 { l.mu.Unlock() return } pcurevents := l.pevents for i, _ := range l.events { if pcurevents != &l.events[i] { l.pevents = &l.events[i] break } } l.mu.Unlock() for _, v := range *pcurevents { if l.out == os.Stderr || l.out == os.Stdout { l.out.Write(v.Color) } else { l.out.Write(v.Plain) } l.out.Write([]byte(l.prefix)) l.out.Write([]byte(fmt.Sprint(time.Now()))) l.out.Write(v.Data) } *pcurevents = (*pcurevents)[0:0] } for i := 0; i < 10; i++ { go func() { for { select { case <-l.ctx.Done(): return case req := <-l.ch: l.mu.Lock() *l.pevents = append(*l.pevents, req) l.cnt++ l.mu.Unlock() } } }() } go func() { defer func() { fsync() }() for { select { case <-l.ctx.Done(): return case <-time.After(time.Second * 5): fsync() case <-l.fsig: fsync() } } }() } func (l *Logger) Output(e *event) { if len(l.ch)+1 >= MAX_INJECTION { l.fsig <- struct{}{} } l.ch <- e } func (l *Logger) Debug(v ...interface{}) { s := fmt.Sprint(v...) s += "\r\n" l.Output(&event{ Plain: plainDebug, Color: Purple(plainDebug), Data: []byte(s), }) } func (l *Logger) Info(v ...interface{}) { s := fmt.Sprint(v...) s += "\r\n" l.Output(&event{ Plain: plainInfo, Color: Green(plainInfo), Data: []byte(s), }) } func (l *Logger) Warn(v ...interface{}) { s := fmt.Sprint(v...) s += "\r\n" l.Output(&event{ Plain: plainWarn, Color: Orange(plainWarn), Data: []byte(s), }) } func (l *Logger) Error(v ...interface{}) { s := fmt.Sprint(v...) s += "\r\n" l.Output(&event{ Plain: plainError, Color: Red(plainError), Data: []byte(s), }) } var std = NewLogger(context.Background(), os.Stderr, "mylog->", 0, "", "misc") func Default() *Logger { return std } func main() { // 功能测试 std.Info("这里是一条Info日志, a") std.Info("这里是一条Info日志, b") std.Info("这里是一条Info日志, c") std.Info("这里是一条Info日志, d") std.Info("这里是一条Info日志, e") std.Debug("这里是一条Debuf日志, m") std.Error("这里是一条Error日志, n") std.Warn("这里是一条Warn日志, n") outstr := "" tf := func() { std.start = time.Now() wg := &sync.WaitGroup{} for i := 0; i < 200; i++ { wg.Add(1) go func() { for j := 0; j < 100000; j++ { std.Info("-----------------------------------------------------info 日志") } wg.Done() }() } time.Sleep(time.Microsecond * 200) wg.Wait() diff := time.Now().Sub(std.start).Seconds() outstr = fmt.Sprintf("logger is exit now, total %+v item, used %+v second, ", std.cnt, diff) } //fmt.Println("输出到终端,性能测试") //std.SetOutput(std.out) //tf() fmt.Println("输出到文件,性能测试") ///* fd, err := os.OpenFile("./test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) if err != nil { panic(err) } std.SetOutput(fd) tf() fd.Sync() //*/ fmt.Println(outstr) } ``` 硬件: ```shell root@jack-VirtualBox:~/comment/logger# lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 39 bits physical, 48 bits virtual CPU(s): 4 On-line CPU(s) list: 0-3 Thread(s) per core: 1 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 165 Model name: Intel(R) Core(TM) i3-10100 CPU @ 3.60GHz Stepping: 3 CPU MHz: 3600.006 BogoMIPS: 7200.01 Hypervisor vendor: KVM Virtualization type: full L1d cache: 128 KiB L1i cache: 128 KiB L2 cache: 1 MiB L3 cache: 24 MiB NUMA node0 CPU(s): 0-3 Vulnerability Itlb multihit: KVM: Mitigation: VMX unsupported Vulnerability L1tf: Not affected Vulnerability Mds: Not affected Vulnerability Meltdown: Not affected Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Full generic retpoline, STIBP disabled, RSB filling Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Not affected Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid ts c_known_freq pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single fsgsbase avx2 invpcid rdseed clflushopt md_clear flush_l1d arch_capabilities root@jack-VirtualBox:~/comment/logger# root@jack-VirtualBox:~/comment/logger# free -h total used free shared buff/cache available Mem: 7.8Gi 2.1Gi 1.8Gi 1.0Mi 3.9Gi 5.4Gi Swap: 1.6Gi 1.0Mi 1.6Gi root@jack-VirtualBox:~/comment/logger# ``` 功能测试: ![在这里插入图片描述](https://img-blog.csdnimg.cn/573fd75b884746e0b3ebee40bc09694b.png) 压力测试: > 参考值20w/秒。 磁盘,显卡,cpu等的性能等有对此时结果有影响。 以日志文件为输出时: (我要去给朋友过生日吃饭,先不写了)

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

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

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