当前位置: 首页 > news >正文

Go项目实现日志按时间及文件大小切割并压缩


关于日志的一些问题:

单个文件过大会影响写入效率,所以会做拆分,但是到多大拆分? 最多保留几个日志文件?最多保留多少天,要不要做压缩处理?

一般都使用 lumberjack[1]这个库完成上述这些操作


lumberjack


 //info文件writeSyncer
 infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/info.log"//日志文件存放目录,如果文件夹不存在会自动创建
  MaxSize:    2,                //文件大小限制,单位MB
  MaxBackups: 100,              //最大保留日志文件数量
  MaxAge:     30,               //日志文件保留天数
  Compress:   false,            //是否压缩处理
 })
 infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
 //error文件writeSyncer
 errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/error.log"//日志文件存放目录
  MaxSize:    1,                 //文件大小限制,单位MB
  MaxBackups: 5,                 //最大保留日志文件数量
  MaxAge:     30,                //日志文件保留天数
  Compress:   false,             //是否压缩处理
 })

测试日志到达指定大小后自动会切分


alt

例如,当info级别的日志文件到达2M时,会根据当时的时间戳,切分出一个info-2023-04-13T05-27-18.296.log。 后续新写入的info级别的日志将写入到info.log,直到又到达2M,继续会切分。


测试日志到达指定最大保留日志文件数量后,将作何操作


清掉log文件夹,修改error日志配置:

 errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/error.log"//日志文件存放目录
  MaxSize:    1,                 //文件大小限制,单位MB
  MaxBackups: 3,                 //最大保留日志文件数量
  MaxAge:     30,                //日志文件保留天数
  Compress:   false,             //是否压缩处理
 })

代码中只打印error日志,执行代码 进行观察

alt

继续执行

alt

继续执行

alt
alt

可见最早拆分出的那个error-2023-04-13T05-40-48.715.log文件不见了~

继续执行,切分出来的文件数量,也会始终保持3个


完整变化图:

alt

测试压缩处理的效果


清掉log文件夹,修改error日志配置:

 errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/error.log"//日志文件存放目录
  MaxSize:    5,                 //文件大小限制,单位MB
  MaxBackups: 10,                 //最大保留日志文件数量
  MaxAge:     30,                //日志文件保留天数
  Compress:   false,             //是否压缩处理
 })

代码中只打印error日志,执行代码,循环10000000次, 进行观察

alt

不压缩共占用814M存储空间


清掉log文件夹,修改Compress字段为true,执行代码:

alt

启用压缩后,仅占用了30M磁盘空间!

不太好的地方就是不方便直接查看了,需要解压后查看。但大大省了所占用的空间


golang zap日志库使用[2]


lumberjack这个库目前只支持按文件大小切割(按时间切割效率低且不能保证日志数据不被破坏,详情见https://github.com/natefinch/lumberjack/issues/54)

想按日期切割可以使用github.com/lestrrat-go/file-rotatelogs[3]这个库(目前不维护了)




file-rotatelogs实现按时间的切割


注意:

github.com/lestrrat-go/file-rotatelogs[4](2021年后不更新了) 和 github.com/lestrrat/go-file-rotatelogs[5](2018年以后就不更新了) 两个不一样。。前面那个是更新的,作者是一个人...

(有一个linux系统上的日志工具,也叫logrotate)

logrotate 是一个用于日志文件轮换的 Go 语言库,支持按时间轮换、按文件大小轮换和按行数轮换。还支持在轮换时压缩文件、删除旧文件、给文件添加时间戳等功能

用zap和go-file-rotatelogs实现日志的记录和日志按时间分割[6]


WithRotationCount和WithMaxAge两个选项不能共存,只能设置一个(都设置编译时不会出错,但运行时会报错。也是为了防止影响切分的处理逻辑):

panic: options MaxAge and RotationCount cannot be both set

package main

import (
 "fmt"
 "io"
 "net/http"
 "time"

 rotatelogs "github.com/lestrrat-go/file-rotatelogs"
 "go.uber.org/zap"
 "go.uber.org/zap/zapcore"
)

// 使用file-rotatelogs做切分

var sugarLogger *zap.SugaredLogger

func main() {
 fmt.Println("shuang提示:begin main")
 InitLogger()
 defer sugarLogger.Sync()

 for i := 0; i < 100000; i++ {
  simpleHttpGet("www.cnblogs.com")
  simpleHttpGet("https://www.baidu.com")
 }

}

// 例子,http访问url,返回状态
func simpleHttpGet(url string) {
 fmt.Println("begin simpleHttpGet:" + url)
 sugarLogger.Debugf("Trying to hit GET request for %s", url)
 resp, err := http.Get(url)
 if err != nil {
  sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
 } else {
  sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
  resp.Body.Close()
 }
}

func InitLogger() {
 encoder := getEncoder()

 //两个interface,判断日志等级
 //warnlevel以下归到info日志
 infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl < zapcore.WarnLevel
 })
 //warnlevel及以上归到warn日志
 warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl >= zapcore.WarnLevel
 })

 infoWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/info")
 warnWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/warn")

 //创建zap.Core,for logger
 core := zapcore.NewTee(
  zapcore.NewCore(encoder, infoWriter, infoLevel),
  zapcore.NewCore(encoder, warnWriter, warnLevel),
 )
 //生成Logger
 logger := zap.New(core, zap.AddCaller())
 sugarLogger = logger.Sugar()
}

// getEncoder
func getEncoder() zapcore.Encoder {
 encoderConfig := zap.NewProductionEncoderConfig()
 encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
 encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
 return zapcore.NewConsoleEncoder(encoderConfig)
}

// 得到LogWriter
func getLogWriter(filePath string) zapcore.WriteSyncer {
 warnIoWriter := getWriter(filePath)
 return zapcore.AddSync(warnIoWriter)
}

// 日志文件切割
func getWriter(filename string) io.Writer {

 //保存日志30天,每1分钟分割一次日志
 hook, err := rotatelogs.New(
  filename+"_%Y-%m-%d %H:%M:%S.log",
  // 为最新的日志建立软连接,指向最新日志文件
  rotatelogs.WithLinkName(filename),

  // 清理条件: 将已切割的日志文件按条件(数量or时间)直接删除
  //--- MaxAge and RotationCount cannot be both set  两者不能同时设置
  //--- RotationCount用来设置最多切割的文件数(超过的会被 从旧到新 清理)
  //--- MaxAge 是设置文件清理前的最长保存时间 最小分钟为单位
  //--- if both are 0, give maxAge a default 7 * 24 * time.Hour
  // WithRotationCount和WithMaxAge两个选项不能共存,只能设置一个(都设置编译时不会出错,但运行时会报错。也是为了防止影响切分的处理逻辑)
  //rotatelogs.WithRotationCount(10),       // 超过这个数的文件会被清掉
  rotatelogs.WithMaxAge(time.Hour*24*30), // 保存多久(设置文件清理前的最长保存时间 最小分钟为单位)

  // 切分条件(将日志文件做切割;WithRotationTime and WithRotationSize ~~两者任意一个条件达到都会切割~~)
  // 经过亲测后发现,如果日志没有持续增加,WithRotationTime设置较小(如10s),并不会按WithRotationTime频次切分文件。当日志不停增加时,会按照WithRotationTime设置来切分(即便WithRotationTime设置的很小)
  rotatelogs.WithRotationTime(time.Second*10),     // 10秒分割一次(设置日志切割时间间隔,默认 24 * time.Hour)
  rotatelogs.WithRotationSize(int64(1*1024*1024*1024)), // 文件达到多大则进行切割,单位为 bytes;
 )
 if err != nil {
  panic(err)
 }
 return hook
}

验证其切分功能:


将触发切分的文件大小设置得很大(110241024*1024 Byte即1 GB),切分时间设置得较小(10秒分割一次),执行代码,可以观察到日志文件的变化:

alt
alt

再将触发切分的文件大小设置得很小(1102450 Byte即50 KB),切分时间设置得较大(24h分割一次),执行代码,清掉之前的日志,再观察到日志文件的变化:

alt
alt

将触发切分的文件大小设置得很小(1102435 Byte即35 KB),同时切分时间也设置得很小(10s分割一次),执行代码,清掉之前的日志,再观察到日志文件的变化:

alt

当前日志容量大于配置的容量时,会生成新的日志文件,如果时间一样,在时间后缀后面会自动加上一个数字后缀,以此区分同一时间的不同日志文件,如果时间不一样,则生成新的时间后缀文件 (golang实现分割日志[7])

日志文件中是会出现有的命中时间规则,有的命中文件大小规则的情况,两者命名格式不同,参考上图


切分之后执行压缩命令


alt

默认是没有的,不像lumberjack那样提供Compress选项

前面所提的还支持在轮换时压缩文件、删除旧文件、给文件添加时间戳等功能需要自己实现。 提供了一个WithHandler回调函数,发生切分后会触发该函数,可以在其中进项压缩等操作

alt

改一下代码(不再请求网站因为速度太慢,直接在for里面写日志)

不启用压缩:

alt

启用压缩,效果显著:

alt

相关代码:

package main

import (
 "archive/zip"
 "fmt"
 "io"
 "net/http"
 "os"
 "path/filepath"
 "reflect"
 "time"

 "github.com/davecgh/go-spew/spew"
 rotatelogs "github.com/lestrrat-go/file-rotatelogs"
 "go.uber.org/zap"
 "go.uber.org/zap/zapcore"
)

// 使用file-rotatelogs做切分

var sugarLogger *zap.SugaredLogger

func main() {
 fmt.Println("shuang提示:begin main")
 InitLogger()
 defer sugarLogger.Sync()

 for i := 0; i < 10000000; i++ {

  sugarLogger.Infof("测试压缩后少占用的空间,这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本,i is %d", i)

  //simpleHttpGet("www.cnblogs.com", i)
  //simpleHttpGet("https://www.baidu.com", i)
 }

 time.Sleep(10000e9)

}

// 例子,http访问url,返回状态
func simpleHttpGet(url string, i int) {
 //fmt.Println("begin simpleHttpGet:" + url)
 sugarLogger.Debugf("Trying to hit GET request for %s, i is %d", url, i)
 resp, err := http.Get(url)
 if err != nil {
  sugarLogger.Errorf("Error fetching URL %s : Error = %s, i is %d", url, err, i)
 } else {
  sugarLogger.Infof("Success! statusCode = %s for URL %s,i is %d", resp.Status, url, i)
  resp.Body.Close()
 }
}

func InitLogger() {
 encoder := getEncoder()

 //两个interface,判断日志等级
 //warnlevel以下归到info日志
 infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl < zapcore.WarnLevel
 })
 //warnlevel及以上归到warn日志
 warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl >= zapcore.WarnLevel
 })

 infoWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/info")
 warnWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/warn")

 //创建zap.Core,for logger
 core := zapcore.NewTee(
  zapcore.NewCore(encoder, infoWriter, infoLevel),
  zapcore.NewCore(encoder, warnWriter, warnLevel),
 )
 //生成Logger
 logger := zap.New(core, zap.AddCaller())
 sugarLogger = logger.Sugar()
}

// getEncoder
func getEncoder() zapcore.Encoder {
 encoderConfig := zap.NewProductionEncoderConfig()
 encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
 encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
 return zapcore.NewConsoleEncoder(encoderConfig)
}

// 得到LogWriter
func getLogWriter(filePath string) zapcore.WriteSyncer {
 warnIoWriter := getWriter(filePath)
 return zapcore.AddSync(warnIoWriter)
}

// 日志文件切割
func getWriter(filename string) io.Writer {

 //保存日志30天,每1分钟分割一次日志
 hook, err := rotatelogs.New(
  filename+"_%Y-%m-%d %H:%M:%S.log",
  // 为最新的日志建立软连接,指向最新日志文件
  rotatelogs.WithLinkName(filename),

  // 清理条件: 将已切割的日志文件按条件(数量or时间)直接删除
  //--- MaxAge and RotationCount cannot be both set  两者不能同时设置
  //--- RotationCount用来设置最多切割的文件数(超过的会被 从旧到新 清理)
  //--- MaxAge 是设置文件清理前的最长保存时间 最小分钟为单位
  //--- if both are 0, give maxAge a default 7 * 24 * time.Hour
  // WithRotationCount和WithMaxAge两个选项不能共存,只能设置一个(都设置编译时不会出错,但运行时会报错。也是为了防止影响切分的处理逻辑)
  //rotatelogs.WithRotationCount(10),       // 超过这个数的文件会被清掉
  rotatelogs.WithMaxAge(time.Hour*24*30), // 保存多久(设置文件清理前的最长保存时间 最小分钟为单位)

  // 切分条件(将日志文件做切割;WithRotationTime and WithRotationSize ~~两者任意一个条件达到都会切割~~)
  // 经过亲测后发现,如果日志没有持续增加,WithRotationTime设置较小(如10s),并不会按WithRotationTime频次切分文件。当日志不停增加时,会按照WithRotationTime设置来切分(即便WithRotationTime设置的很小)
  rotatelogs.WithRotationTime(time.Second*10),           // 10秒分割一次(设置日志切割时间间隔,默认 24 * time.Hour)
  rotatelogs.WithRotationSize(int64(1*1024*35000*1024)), // 文件达到多大则进行切割,单位为 bytes;

  // 其他可选配置
  //default: rotatelogs.Local ,you can set rotatelogs.UTC
  //rotatelogs.WithClock(rotatelogs.UTC),
  //rotatelogs.WithLocation(time.Local),
  //--- 当rotatelogs.New()创建的文件存在时,强制创建新的文件 命名为原文件的名称+序号,如a.log存在,则创建创建 a.log.1
  //rotatelogs.ForceNewFile(),

  rotatelogs.WithHandler(rotatelogs.Handler(rotatelogs.HandlerFunc(func(e rotatelogs.Event) {
   if e.Type() != rotatelogs.FileRotatedEventType {
    return
   }

   fmt.Println("切割完成,进行打包压缩操作")

   spew.Dump("e is:", e)

   prevFile := e.(*rotatelogs.FileRotatedEvent).PreviousFile()

   if prevFile != "" {
    // 进行压缩
    paths, fileName := filepath.Split(prevFile)
    //_ = paths
    //err := Zip("archive.zip", paths, prevFile)
    err := ZipFiles(paths+fileName+".zip", []string{prevFile})
    fmt.Println("err is", err)

    if err == nil {
     os.RemoveAll(prevFile)
    }

   }

   fmt.Println("e的类型为:", reflect.TypeOf(e))

   fmt.Println("------------------")
   fmt.Println()
   fmt.Println()
   fmt.Println()

   //ctx := CleanContext{
   // Dir:         LogsConfig.LogOutputDir,
   // DirMaxSizeG: LogsConfig.LogDirMaxSizeG,
   // DirMaxCount: LogsConfig.LogDirMaxFileCount,
   //}
   //strategyOne := CleanStrategyOne{}
   //result, err := NewCleanStrategy(&ctx, &strategyOne).
   // Clean().
   // Result()
   //Warn("文件切割,清理文件策略one已经执行完毕; 结果:%v; 错误:%v", result, err)
  }))),
 )

 if err != nil {
  panic(err)
 }
 return hook
}

// ZipFiles compresses one or many files into a single zip archive file.
// Param 1: filename is the output zip file's name.
// Param 2: files is a list of files to add to the zip.
func ZipFiles(filename string, files []string) error {

 newZipFile, err := os.Create(filename)
 if err != nil {
  return err
 }
 defer newZipFile.Close()

 zipWriter := zip.NewWriter(newZipFile)
 defer zipWriter.Close()

 // Add files to zip
 for _, file := range files {
  if err = AddFileToZip(zipWriter, file); err != nil {
   return err
  }
 }
 return nil
}

func AddFileToZip(zipWriter *zip.Writer, filename string) error {

 fileToZip, err := os.Open(filename)
 if err != nil {
  return err
 }
 defer fileToZip.Close()

 // Get the file information
 info, err := fileToZip.Stat()
 if err != nil {
  return err
 }

 header, err := zip.FileInfoHeader(info)
 if err != nil {
  return err
 }

 // Using FileInfoHeader() above only uses the basename of the file. If we want
 // to preserve the folder structure we can overwrite this with the full path.
 header.Name = filename

 // Change to deflate to gain better compression
 // see http://golang.org/pkg/archive/zip/#pkg-constants
 header.Method = zip.Deflate

 writer, err := zipWriter.CreateHeader(header)
 if err != nil {
  return err
 }
 _, err = io.Copy(writer, fileToZip)
 return err
}

//
 Zip compresses the specified files or dirs to zip archive.
 If a path is a dir don't need to specify the trailing path separator.
 For example calling Zip("archive.zip", "dir", "csv/baz.csv") will get archive.zip and the content of which is
 baz.csv
 dir
 ├── bar.txt
 └── foo.txt
 Note that if a file is a symbolic link it will be skipped.
//
 https://blog.csdn.net/K346K346/article/details/122441250
//func Zip(zipPath string, paths ...string) error {
// // Create zip file and it's parent dir.
// if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {
//  return err
// }
// archive, err := os.Create(zipPath)
// if err != nil {
//  return err
// }
// defer archive.Close()
//
// // New zip writer.
// zipWriter := zip.NewWriter(archive)
// defer zipWriter.Close()
//
// // Traverse the file or directory.
// for _, rootPath := range paths {
//  // Remove the trailing path separator if path is a directory.
//  rootPath = strings.TrimSuffix(rootPath, string(os.PathSeparator))
//
//  // Visit all the files or directories in the tree.
//  err = filepath.Walk(rootPath, walkFunc(rootPath, zipWriter))
//  if err != nil {
//   return err
//  }
// }
// return nil
//}
//
//func walkFunc(rootPath string, zipWriter *zip.Writer) filepath.WalkFunc {
// return func(path string, info fs.FileInfo, err error) error {
//  if err != nil {
//   return err
//  }
//
//  // If a file is a symbolic link it will be skipped.
//  if info.Mode()&os.ModeSymlink != 0 {
//   return nil
//  }
//
//  // Create a local file header.
//  header, err := zip.FileInfoHeader(info)
//  if err != nil {
//   return err
//  }
//
//  // Set compression method.
//  header.Method = zip.Deflate
//
//  // Set relative path of a file as the header name.
//  header.Name, err = filepath.Rel(filepath.Dir(rootPath), path)
//  if err != nil {
//   return err
//  }
//  if info.IsDir() {
//   header.Name += string(os.PathSeparator)
//  }
//
//  // Create writer for the file header and save content of the file.
//  headerWriter, err := zipWriter.CreateHeader(header)
//  if err != nil {
//   return err
//  }
//  if info.IsDir() {
//   return nil
//  }
//  f, err := os.Open(path)
//  if err != nil {
//   return err
//  }
//  defer f.Close()
//  _, err = io.Copy(headerWriter, f)
//  return err
// }
//}


完整demo项目代码 以zap为例,展示如何切割日志文件。 使用Go生态两个使用最高的切分库[8]


关于压缩:

压缩解压文件[9]

Golang 学习笔记(五)- archive/zip 实现压缩及解压[10]

Golang zip 压缩与解压[11]




更多参考:

zap日志切割,同时支持按日期拆分,也支持按日志固定大小拆分,支持定时清理[12]

go-logrus 日志框架封装使用[13]

Go zap日志[14]

设计自用的golang日志模块[15]

golang log rotate file[16]

golang高性能日志库zap的使用[17]

参考资料

[1]

lumberjack: https://github.com/natefinch/lumberjack

[2]

golang zap日志库使用: https://segmentfault.com/a/1190000040443996

[3]

github.com/lestrrat-go/file-rotatelogs: https://github.com/lestrrat/go-file-rotatelogs

[4]

github.com/lestrrat-go/file-rotatelogs: https://github.com/lestrrat-go/file-rotatelogs

[5]

github.com/lestrrat/go-file-rotatelogs: https://github.com/lestrrat/go-file-rotatelogs

[6]

用zap和go-file-rotatelogs实现日志的记录和日志按时间分割: https://blog.csdn.net/weixin_43881017/article/details/110200176

[7]

golang实现分割日志: https://blog.csdn.net/qq_42119514/article/details/121372416

[8]

以zap为例,展示如何切割日志文件。 使用Go生态两个使用最高的切分库: https://github.com/cuishuang/zap-demo/tree/main

[9]

压缩解压文件: https://www.topgoer.com/%E5%85%B6%E4%BB%96/%E5%8E%8B%E7%BC%A9%E8%A7%A3%E5%8E%8B%E6%96%87%E4%BB%B6.html

[10]

Golang 学习笔记(五)- archive/zip 实现压缩及解压: https://learnku.com/articles/23434/golang-learning-notes-five-archivezip-to-achieve-compression-and-decompression

[11]

Golang zip 压缩与解压: https://blog.csdn.net/K346K346/article/details/122441250

[12]

zap日志切割,同时支持按日期拆分,也支持按日志固定大小拆分,支持定时清理: https://blog.csdn.net/qq_22186119/article/details/122003691

[13]

go-logrus 日志框架封装使用: https://www.jianshu.com/p/722250f0b609

[14]

Go zap日志: https://blog.csdn.net/qq_41004932/article/details/119760061

[15]

设计自用的golang日志模块: https://studygolang.com/articles/12537

[16]

golang log rotate file: https://juejin.cn/s/golang%20log%20rotate%20file

[17]

golang高性能日志库zap的使用: https://www.jianshu.com/p/910b626f67d9

本文由 mdnice 多平台发布

相关文章:

Go项目实现日志按时间及文件大小切割并压缩

关于日志的一些问题: 单个文件过大会影响写入效率&#xff0c;所以会做拆分&#xff0c;但是到多大拆分? 最多保留几个日志文件&#xff1f;最多保留多少天&#xff0c;要不要做压缩处理&#xff1f; 一般都使用 lumberjack[1]这个库完成上述这些操作 lumberjack //info文件wr…...

容器化的好处

容器化&#xff0c;是指使用容器技术&#xff08;Docker/containerd等&#xff09;运行应用程序&#xff08;容器&#xff09;&#xff0c;并使用容器编排技术&#xff08;例如 K8s&#xff09;来管理这些容器。 我在之前的文章 《使用 Dockerfile 构建生产环境镜像》 提及普通…...

TPlink DDNS 内网穿透?外网访问设置方法

有很多小伙伴都想知道&#xff1a;TPlink路由器怎么设置DDNS内网穿透&#xff1f;今天&#xff0c;小编就给大家分享一下TPlink DDNS 外网访问设置方法&#xff0c;下面是图文教程&#xff0c;帮助新手快速入门DDNS设置。 本文介绍的是云路由器TP-LINK DDNS的设置方法。TP-LIN…...

以CS32F031为例浅说国产32位MCU的内核处理器

芯片内核又称CPU内核&#xff0c;它是CPU中间的核心芯片&#xff0c;是CPU最重要的组成部分。由单晶硅制成&#xff0c;CPU所有的计算、接受/存储命令、处理数据都由核心执行。各种CPU核心都具有固定的逻辑结构&#xff0c;一级缓存、二级缓存、执行单元、指令级单元和总线接口…...

享元模式(Flyweight)

享元模式是一种结构型设计模式&#xff0c;主要通过共享技术有效地减少大量细粒度对象的复用&#xff0c;以减少内存占用和提高性能。由于享元模式要求能够共享的对象必须是细粒度对象&#xff0c;因此它又称为轻量级模式。 Flyweight is a structural pattern, which effecti…...

Cilium系列-11-启用带宽管理器

系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能. 但是通过对 Cilium 不同模式的切换/功能的启用, 可以进一步提升 Cilium 的网络性能. 具体调优项包括不限于: 启用本地路由(Native Routing)完全替换 KubeProx…...

无人机自动返航的关键技术有哪些

无人机的广泛应用使得无人机自动返航技术变得至关重要。在各种应对意外情况的背景下&#xff0c;无人机自动返航技术的发展对确保无人机的安全&#xff0c;以及提高其应用范围具有重要意义。接下来&#xff0c;便为大家详细介绍无人机自动返航所运用到的关键技术。 一、定位与导…...

Vision Transformer (ViT):图像分块、图像块嵌入、类别标记、QKV矩阵与自注意力机制的解析

作者&#xff1a;CSDN _养乐多_ 本文将介绍Vision Transformers &#xff08;ViT&#xff09;中的关键点。包括图像分块&#xff08;Image Patching&#xff09;、图像块嵌入&#xff08;Patch Embedding&#xff09;、类别标记、&#xff08;class_token&#xff09;、QKV矩…...

Mybatis:一对多映射处理

Mybatis&#xff1a;一对多映射处理 前言一、概述二、创建数据模型三、问题四、解决方案1、方案一&#xff1a;collection&#xff08;嵌套结果&#xff09;2、方案二&#xff1a;分步查询&#xff08;嵌套查询&#xff09; 前言 本博主将用CSDN记录软件开发求学之路上的亲身所…...

HTML+CSS+JavaScript:全选与反选案例

一、需求 1、单击全选按钮&#xff0c;下面三个复选框自动选中&#xff0c;再次单击全选按钮&#xff0c;下面三个复选框自动取消选中 2、当下面三个复选框全都选中时&#xff0c;全选按钮自动选中&#xff0c;下面三个复选框至少有一个未选中&#xff0c;全选按钮自动取消选…...

Python 程序设计入门(001)—— 安装 Python(Windows 操作系统)

Python 程序设计入门&#xff08;001&#xff09;—— 安装 Python&#xff08;Windows 操作系统&#xff09; 目录 Python 程序设计入门&#xff08;001&#xff09;—— 安装 Python&#xff08;Windows 操作系统&#xff09;一、下载 Python 安装包二、安装 Python三、测试&…...

【redis】创建集群

这里介绍的是创建redis集群的方式&#xff0c;一种是通过create-cluster配置文件创建部署在一个物理机上的伪集群&#xff0c;一种是先在不同物理机启动单体redis&#xff0c;然后通过命令行使这些redis加入集群的方式。 一&#xff0c;通过配置文件创建伪集群 进入redis源码…...

linux 配置nacos遇见的问题及解决办法

本次的集群是启动一个服务的三个不同端口&#xff0c;配置如下&#xff1a; 一.application.properties 加上下列配置&#xff0c;目的是使用自己的mysql数据库&#xff1a; spring.datasource.platformmysql db.num1 db.url.0jdbc:mysql://127.0.0.1:3306/nacos_config?s…...

小程序开发趋势:探索人工智能在小程序中的应用

第一章&#xff1a;引言 小程序开发近年来取得了快速的发展&#xff0c;成为了移动应用开发的重要一环。随着人工智能技术的飞速发展&#xff0c;越来越多的企业开始探索如何将人工智能应用于小程序开发中&#xff0c;为用户提供更智能、便捷的服务。本文将带您一起探索人工智能…...

基于埋点日志数据的网络流量统计 - PV、UV

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 一、 网站总流量数统计 - PV 1. 需求分析 2. 代码实现 方式一 方式二 方式三&#xff1a;使用process算子实现 方式四&#xff1a;使用process算子实现 二、网站独立访客数统计 - UV 1. …...

cuda入门demo(2)——最基础的二方向sobel

⚠️主要是自己温习用&#xff0c;只保证代码正确性&#xff0c;不保证讲解的详细性。 今天继续总结cuda最基本的入门demo。很多教程会给你说conv怎么写&#xff0c;实际上sobel也是conv&#xff0c;并且conv本身已经用torch实现了。 之前在课题中尝试了sobel的变体&#xff0…...

软件外包开发的后台开发语言

在软件外包开发中&#xff0c;后台语言的选择通常取决于项目需求、客户偏好、团队技能和开发效率。今天和大家分享一些常用的后台语言及选择它们的原因&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。…...

自动驾驶感知系统-全球卫星定位系统

卫星定位系统 车辆定位是让无人驾驶汽车获取自身确切位置的技术&#xff0c;在自动驾驶技术中定位担负着相当重要的职责。车辆自身定位信息获取的方式多样&#xff0c;涉及多种传感器类型与相关技术。自动驾驶汽车能够持续安全可靠运行的一个关键前提是车辆的定位系统必须实时…...

数据结构 | 基本数据结构——队列

目录 一、何谓队列 二、队列抽象数据类型 三、用Python实现队列 四、模拟&#xff1a;传土豆 五、模拟&#xff1a;打印任务 5.1 主要模拟步骤 5.2 Python实现 一、何谓队列 队列是有序集合&#xff0c;添加操作发生在“尾部”&#xff0c;移除操作则发生在“头部”。新…...

QT在label上透明绘图(二)

前面步骤参考前一篇文章 QT在label上透明绘图 一、给TransparentLabel类添加double transparency;变量&#xff0c; 二、ui添加doublespinbox&#xff0c;调整透明参数 void MainWindow::on_doubleSpinBox_valueChanged(double arg1) {transparentLabel->transparencyarg1;…...

微信小程序使用editor富文本编辑器 以及回显 全屏弹窗的模式

<!--富文本接收的位置--><view class"white-box"><view class"title"><view class"yellow-fence"></view><view class"v1">教研记录</view></view><view class"add-btn"…...

在CSDN学Golang场景化解决方案(基于gin框架的web开发脚手架)

一&#xff0c;中间件统一实现Oauth2身份验证 在Golang基于Gin框架开发Web应用程序时&#xff0c;可以使用gin-oauth2来实现Oauth2身份验证。下面是简单的步骤&#xff1a; 安装gin-oauth2包&#xff1a;go get github.com/appleboy/gin-oauth2导入依赖&#xff1a;import &q…...

关于Express 5

目录 1、概述 2、Express 5的变化 2.1 弃用或删除内容的列表&#xff1a; app.param&#xff08;name&#xff0c;fn&#xff09;名称中的前导冒号&#xff08;&#xff1a;&#xff09; app.del() app.param&#xff08;fn&#xff09; 复数方法名 res.json&#xff0…...

ftrace 原理详细分析

》内核新视界文章汇总《 文章目录 ftrace 原理分析1 简介2 ftrace 的编译器支持2.1 HAVE_FUNCTION_TRACER 选项对 ftrace 的支持2.2 HAVE_DYNAMIC_FTRACE 选项对动态 ftrace 的支持 3 ftrace 的初始化4 function trace 流程5 总结 ftrace 原理分析 1 简介 ftrace 是一个内核…...

UWB定位技术和蓝牙AOA有哪些不同?-高精度室内定位技术对比

UWB超宽带定位 UWB&#xff08;Ultra Wide Band &#xff09;即超宽带技术&#xff0c;它是一种无载波通信技术&#xff0c;利用纳秒级的非正弦波窄脉冲传输数据&#xff0c;因此其所占的频谱范围很宽。传统的定位技术是根据信号强弱来判别物体位置&#xff0c;信号强弱受外界…...

【RabbitMQ】golang客户端教程2——工作队列

任务队列/工作队列 在上一个教程中&#xff0c;我们编写程序从命名的队列发送和接收消息。在这一节中&#xff0c;我们将创建一个工作队列&#xff0c;该队列将用于在多个工人之间分配耗时的任务。 工作队列&#xff08;又称任务队列&#xff09;的主要思想是避免立即执行某些…...

芯旺微冲刺IPO,车规级MCU竞争白热化下的“隐忧”凸显

在汽车智能化和电动化发展带来的巨大蓝海市场下&#xff0c;产业链企业迎来了一波IPO小高潮。 日前&#xff0c;上海芯旺微电子技术股份有限公司&#xff08;以下简称“芯旺微”&#xff09;在科创板的上市申请已经被上交所受理&#xff0c;拟募资17亿元&#xff0c;用于投建车…...

HTML <s> 标签

例子 可以像这样标记删除线文本&#xff1a; 在 HTML 5 中&#xff0c;<s>仍然支持</s>已经不支持这个标签了。 浏览器支持 元素ChromeIEFirefoxSafariOpera<s>YesYesYesYesYes 所有浏览器都支持 <s> 标签。 定义和用法 <s> 标签可定义加…...

微信小程序 - scroll-view组件之上拉加载下拉刷新(解决上拉加载不触发)

前言 最近在做微信小程序项目中&#xff0c;有一个功能就是做一个商品列表分页限流然后实现上拉加载下拉刷新功能&#xff0c;遇到了一个使用scroll-viwe组件下拉刷新事件始终不触发问题&#xff0c;网上很多说给scroll-view设置一个高度啥的就可以解决&#xff0c;有些人设置了…...

rust usize与i64怎么比较大小?

在Rust中&#xff0c; usize 和 i64 是不同的整数类型&#xff0c;它们的位数和表示范围可能不同。因此&#xff0c;直接比较 usize 和 i64 是不允许的。如果需要比较它们的大小&#xff0c;可以将它们转换为相同的类型&#xff0c;然后进行比较。 要将 usize 转换为 i64 &…...