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

golang中有很多优秀的日志库,例如zerolog,see-log,blog4go等。 这些日志首先具备强大的高并发能力,其次具备丰富的接口,输入格式,索引接口。 把外壳去掉,日志库的设计原理本质上是一样的。

我个人认为一个日志系统需要满足的特点:

  • 1 内容不确定(非格式化数据)。为能够方便索引,因此需要额外引入一些namespace和类型,时间戳等的字符串。
  • 2 要求并发量大,写入快,也就是日志需要有实时性,不能对现有业务造成阻塞。 因此日志需要设置成带缓冲的功能、多协程。 在日志的生成、粗加工、传递、归类、输出的过程中,减少传递、减少cpu锁、减少拷贝次数/内容 方面下功夫,持久化优化,能提升日志库的性能。
  • 3 日志需要一定的格式,因此在输出时需要顺序输出到终端、或日志文件。 落盘需要是一个任务去执行。
  • 4 日志文件不能太大,需要定期清理掉无效的日志。 因此日志按天、周为单位,每个单位写入对应以时间戳命名的文件中。
  • 5 参照linux系统自带的系统日志,对于历史日志(例如一周之前的日志),需要压缩成压缩文档。删掉非压缩的文档。
  • 6 输出目标可以灵活指定,可以是终端,也可以是日志目录,也可以是网络流。 输出格式可以是raw原始内容、也可以是json格式。
  • 7 考虑小白用户。例如一些运维人员,vim用的也许不是那么的熟悉,还有一些c端的小白用户估计linux系统都不晓得是啥,因此日志系统如果能带一个简单的web界面,日志索引提供输入框返回结果,这虽然不一定用的上,但是会显得比较完善。

补充:本文比较仓促,是以学习原理为目的(我写的文章侧重原理分析 和 实现。 当然,生产环境是另外一套另当别论,我优先考虑用别人写好的组件)。 因此上述7个特点并未在本文全部实现。

直接上源码:

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)
}

硬件:

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#

功能测试:
在这里插入图片描述

压力测试:

参考值20w条日志/秒。 磁盘,显卡,cpu等的性能等有对此时结果有影响。
同样的硬件配置,golang系统自带的l"log"包的速度是3.7w条日志/秒。

以日志文件为输出时:
(我要去给朋友过生日吃饭,先不写了)

猜你喜欢

转载自blog.csdn.net/jacky128256/article/details/121039849