muduo库的高性能日志库(一)

概述

“日志”即文本,供人阅读的日志,通常用于故障诊断和追踪,也用于性能分析

服务端编程,日志必不可少,生产环境中应做到"Log Everything All The Time"。

muduo日志库采用C++的stream << 风格,出于性能原因未使用标准库iostream而使用自己写的LogStream class。

在多线程程序中,每个线程有自己的前端,整个程序共用一个后端。但难点在于将日志数据从多个前端高效地传输到后端,这是一个典型地多生产者-单消费者地问题:(1)对生产者而言,要尽量做到低延迟,低CPU开销,无阻塞;(2) 对消费者而言,要做到足够大地吞吐量,并占用较少资源。

muduo日志库实现思想

方案1:每次日志操作打开,写,关闭文件一次

每一次日志操作,都进行三个步骤:打开文件写文件关闭文件
当写入日志频率高时,磁盘IO占用高;
低延迟的操作中,IO效率导致延迟高;

注意:多线程文件操作默认是加锁安全的,可以同时多个线程打开文件,多个写,都无问题,但是乱序。 明显,多线程中这种反复打开文件是没有意义可言的。

方案2:每次日志操作写一次文件,文件全局打开一次

程序启动是打开文件,后续日志操作就对文件写一次。
文件操作默认是存在一定缓冲区,写当入字节总量达到一定值,才操作一次磁盘。因此,在单线程中当前方案相对于方案1有极大的效率提升。

在日志操作不频繁、消息短小时,方案2是可行的:程序启动时打开文件,运行时fwrite写日志到文件,程序退出时关闭文件。

使用c++中std::ostream的方式是不正确,因为其非线程安全,会导致日志消息交织混乱,除非加锁。 而c的fwrite是线程安全的多个线程中不加锁直接使用,但是日志数消息也会出现交织混乱。

方案3:减少磁盘IO操作次数

为了提高效率,需要尽量减少磁盘IO的操作。例如,可以使用一个线程安全的队列,一边负责写日志消息,另一边负责取消息日志。当取消息日志条数或者总长度达到一定的数量,我们才写一次磁盘,整体上来可以降低磁盘IO的操作次数

一种可行的方案是,使用::setbuffer函数将文件的缓冲区设置大一些。

另一种方案是使用队列。但是使用队列需要保证每条日志消息的完整性,也就是需要使用互斥锁来保证,这样会有小量的性能损耗;若在使用BlockingQuene时每次产生一条消息时都需要通过条件变量通知接收方,性能会有进一步的损耗。

多线程中,队列已经保证了单条日志的完整性,不会出现交织情况。在使用队列存储日志消息到磁盘时,实际是单线程的操作,可以使用fwrite非加锁版本::fwrite_unlocked函数进一步提高效率。

方案4:使用缓冲技术

方案3中,队列保证消息正确性完整性,可以使用::fwrite_unlocked函数提高日志性能。如果使用::setbuffer函数将缓冲区设置大一些,使用一种不带锁的"线程安全队列",同时也能使用::fwrite_unlocked函数写磁盘操作,那么多个环节效率就可以兼顾了。

这里就是muduo日志库的设计思路,多个线程共用一个日志前端,使用多缓冲技术。异步日志是必须的,所以需要一个缓冲区,在这里我们使用的是多缓冲技术,基本思路是准备多块Buffer,前端负责向Buffer中填数据,后端负责将Buffer中数据取出来写入文件。这种实现的好处在于在新建日志消息的时候不必等待磁盘IO操作,前端写的时候也不会阻塞。

例如使用双缓冲技术,准备两块缓冲A和B,缓冲A接收日志消息,缓冲B将日志消息写入磁盘。当A写满,交换A和B,后端将B写入磁盘文件,前端则往A写写的日志消息,如此反复。

muduo库的高性能

在这里插入图片描述

为了实现高性能,muduo日志库的实现的优化

  • 时间戳字符串中的日期时间, 两部分是缓存的,一秒之内的多条日志只需要重新格式化微秒部分
  • 日志消息的前4字段是定长的,因此可以避免在运行期间求字符串长度(不会反复调用strlen )。因为 编译器认识memcpy()函数,对于定长的内存拷贝,会在编译期把它inline展开为高效的目标代码。
  • 线程id是预先格式化为字符串,在输出日志消息时只需要拷贝几个字节
  • 每行日志消息的源文件部分采用了编译器计算来获得basename,避免运行期strrchr(3)开销。

muduo日志库文件脉络

下图给出了muduo多线程异步日志库的类结构组成图。图中红色标注的为类,蓝色为成员变量或函数。黑色特殊,是LogStream中的成员函数,用于将日志消息数据放入缓存中
在这里插入图片描述

日志库前端Logger:

(1)LogStream类
主要作用是重载<<操作符,流式化日志输出方式。将基本类型的数据格式成字符串通过append接口存入LogBuffer中。格式化整形数据使用了优化方法。
(2)Logger类
提供设置日志级别和输出目的地的静态方法,使用LogStream的<<操作符将文件名,日志级别,时间等信息格式好提前写入LogBuffer中。日志默认级别Logger::INFO,默认输出到stdout。
(3)Impl类
是Logger类的私有类,为了隐藏实现,在用户消息输出到缓冲前,添加日期、时间、级别、线程固定前缀字段,添加当前日志消息出处的源文件、行数等固定后缀字段。
(4)Buffer类
用于前端手机用户日志消息的缓存,是模板类FixedBuffer<>的实例化,默认缓存4000字节。

日志库后端 LogFile:

(1)LogFile类
日志文件管理类。以固定方式生成文件名并创建日志文件。实现日志滚动、日志缓存flush到日志文件的策略。
日志消息记录长度达到设定值、日志记录时间到达第二天进行日志滚动。当短时间内日志长度较小时,不能将日志信息长时间放如缓存中,因此日志每记录1024次数就检查一次距前一次flush到文件的时间是否超过3s,若是则flush到文件中。另外,可以选择是否使用互斥锁锁保证线程安全。

(2)AppendFIle类
是 LogFile 类的成员,最终用于操作本地日志文件的类,输出缓冲区大小64k字节。非线程安全的,目的是提高效率,原因后续解释。

多线程异步日志类AsyncLogging

考虑多线程下各种性能要求实现的一个非阻塞日志功能实现。前端仍然是Logger,只是将默认日志输出从stdout重定向到了AsyncLogging中。使用多缓冲技术,将输入缓冲、输出缓冲做高效的安全交换,供后端LogFIle写入到文件。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

binary~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值