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

一个日志库答题分为前端(Logging.{h,cc})和后端(LogFile.{h,cc})两部分。前端是供应用程序使用的接口,生成日志消息;后端则负责把日志消息消息写到目的地。

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

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

功能需求

  • 多种日志级别可配置
  • 日志输出目的地只能是本地文件
  • 支持日志文件rooling(如按天、按大小),以简化日志归档
  • 日志文件命名(进程名+创建日期+创建时间+机器名+进程id+后缀log)
  • 日志消息格式固定(日期+时间+线程id+日志级别+源文件名和行号+日志信息)

关于日志消息,时间可以精确到微秒,使用GMT时间(排除不同时区、分布式部署的时间差异),避免出现正则表达式中的元字符(如“[”和“]”)。

性能需求

  • 每秒写千万条日志无明显性能损失
  • 能应对一个进程产生大量日志数据的场景
  • 不阻塞正常的执行流程
  • 在多线程程序中,不造成争用
  • 可以瞬时写满磁盘带宽

这里是对日志前端、日志后端的性能要求。这里介绍磁盘带宽和日志量的问题,7200rpm 的大容量机械硬盘写入速度可以达到200MB/s,SSD固态硬盘可以达到4GB/s,以日志库短时间瞬时达到200MB/s写入速度为例,如果日志平均长度100字节,那么1s可写210万条日志。若以4GB/s写入速度计算,1s可写4000万条日志。

muduo日志库实现

主要介绍一个日志库设计思路,不同设计方案、不同场景(多线程、读与写频率)的性能优缺点,设计方案演进等。

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

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

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

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

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

下面给出单线程中,测试 100*1000次日志操作,不同文件打开方式,方案1和方案2的效率比较。可以明显看出,在写入数据量小时,数据写入磁盘文件效率是差的离谱的,更不用讨论在日志消息长度大(更多的磁盘操作)的时候了。

在wsl中测试,文件打开、关闭平均耗时 50us左右,写一条消息以0.05us计算,两者时间效率差1000倍(实体机上文件系统效率比wsl高,可能差距会更大,未验证 )。

方案 打开方式 写入字节长度 平均时间 / us
方案1 “w” 1 90.2
方案1 “w” 10 91.1
方案1 “w” 20 92.5
方案1 “w” 50 90.213390
方案2 “w” 1 0.023
方案2 “w” 10 0.030
方案2 “w” 20 0.052
方案2 “w” 50 0.081

再给出以追加模式打开文件的测试情况,以作对比

方案 打开方式 写入字节长度 平均时间 / us
方案1 “a+” 1 54.6
方案1 “a+” 10 53.4
方案1 “a+” 20 58.7
方案1 “a+” 50 51.3
方案2 “a+” 1 0.031
方案2 “a+” 10 0.059
方案2 “a+” 20 0.072
方案2 “a+” 50 0.165

在日志操作不频繁、消息短小时,方案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日志库结构

mudo日志库设计中处理上面提到的性能上的介绍仅是一部分,还有更多的优化需要在后面具体设计实现中介绍。这里简单介绍日志库的组成和基本结构。

日志库结构

下图给出了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写入到文件。

后面博文将现详细说明以上代码实现。

日志消格式化的优化措施

日志消息的字符串的拼装,有几点优化措施值得一提:

  • 时间戳字符串中的日期和时间是缓存的,一秒之内的日志只需要格式化微秒部分。

    见Logger::Impl::formatTime()实现。

  • 日志消息的前4个字段是定长的,避免运行期求字符串长度(不用反复调用strlen)。编译器认识memcpy函数,对于定长的内存复制,编译期将inline内联展开成高效的年目标代码。

    见class T类的 Logstream& operator<<(LogStream& s, T v)函数。

  • 线程id是预先格式化缓存的字符串,输出日志消息只需要拷贝几个字节。

    见Currendthread类介绍。

  • 每条日志消息的源文件名和行数通过编译期计算得到(strrchr),避免运行时的开销。实际是使用日志宏语句,替换得到当前代码所在的 __FILE__, __LINE__字符串。

    见SourceFile类。

muduo学习笔记:base部分之高性能日志库相关推荐

  1. muduo学习笔记:net部分之Http--HttpServer

    前面[muduo学习笔记:net部分之Http–HttpRequest.HttpResponse 和 HttpContext]介绍了TCP数据数据Buffer承载的HTTP报文的解析,本文结合TcpS ...

  2. Go 学习笔记(54)— Go 第三方库之 uber-go/zap/lumberjack(记录日志到文件、支持自动分割日志、支持日志级别、打印调用文件、函数和行号)

    1. 简要说明 zap 是 uber 开源的 Go 高性能日志库,支持不同的日志级别, 能够打印基本信息等,但不支持日志的分割,这里我们可以使用 lumberjack 也是 zap 官方推荐用于日志分 ...

  3. 深度|从Go高性能日志库zap看如何实现高性能Go组件

    导语:zap是uber开源的Go高性能日志库.本文作者深入分析了zap的架构设计和具体实现,揭示了zap高效的原因.并且对如何构建高性能Go语言库给出自己的建议. 作者简介:李子昂,美图公司架构平台系 ...

  4. 深度 | 从Go高性能日志库zap看如何实现高性能Go组件

    导语:zap是uber开源的Go高性能日志库.本文作者深入分析了zap的架构设计和具体实现,揭示了zap高效的原因.并且对如何构建高性能Go语言库给出自己的建议. 作者简介:李子昂,美图公司架构平台系 ...

  5. MySQL学习笔记(六)-MySQL中库和表的管理

    MySQL学习笔记(六)-MySQL中库和表的管理 作者:就叫易易好了 日期:2020/11/23 1 2 DDL即数据定义语言 创建:create 修改:alter 删除:drop 库和表的管理: ...

  6. USB学习笔记(5)STM32 USB库移植步骤

    USB学习笔记(5)STM32 USB库移植步骤 本文是将官网的USB驱动库文件移植到自己的工程里面,实现USB功能的具体移植步骤说明,实现Custom_HID功能. 1.先准备一个工程模板 获取工程 ...

  7. oracle如何往dg加盘_学习笔记:Oracle DG系统 主备库中表空间和数据文件增加删除等管...

    天萃荷净 Oracle Data Guard表空间和数据文件管理汇总 汇总日常工作中操作,在Oracle DG结构系统中,如何删除备库表空间和数据文件,如何管理主库与备库之间的文件系统,详见文章内容. ...

  8. golang高性能日志库zap的使用

    本文作者:陈进坚 个人博客:https://jian1098.github.io CSDN博客:https://blog.csdn.net/c_jian 简书:https://www.jianshu. ...

  9. 号称C/C++高性能日志库

    ** 以下都是号称高性能日志库 ** 腾讯 C++日志库:Mars XLOG https://github.com/Tencent/mars 美团 C/C++日志库 https://github.co ...

最新文章

  1. ViewPager 的点击事件回调
  2. Cpp 对象模型探索 / 编译器为对象创建缺省析构函数的条件
  3. 有关计算机代码的游戏,七灯游戏是一款经典的益智类游戏。游戏中,有七盏灯排成一圈,如图a所示,初始时灯的开关状态随机生成,操作其中某一盏灯,则可以切换该灯的“开/关”状态,同时,这盏灯-组卷网...
  4. java图形界面的监听_非专业码农 JAVA学习笔记 用户图形界面设计与实现-所有控件的监听事件...
  5. Restful API 设计
  6. mysql 防注入 php_PHP+mysql防止SQL注入的方法小结
  7. 搜索二叉树之字典实现
  8. 万有引力的意思_万有引力和引力有什么不同?四种基本性质力中电磁力最多
  9. databinding包名不能大写?——不改包名解决databinding与包名大写的冲突Android
  10. 高德城市编码json_sql_excel 三级联动json合集
  11. Region和Available Zone的区别
  12. 苹果充电慢是什么原因_成都苹果维修点告诉你为什么iPhone手机不能在关机状态下充电?...
  13. 新版升级 DAEMON Tools v4.10 X86 32 Bits (with SPTD 1.50)
  14. 苹果手机html吊起拍照,一张好照片不是只按快门 iPhone手机拍照指南
  15. 应用软件安全编程代码实现安全
  16. 新技能get之公众号音频下载
  17. 从零开始编写一个上位机(串口助手)QT Creator + C++
  18. 【笔记】Seaborn和Pyecharts
  19. Eclipse Android LogCat有时候显示不出输出的原因
  20. CSDN如何发表自定义颜色的字体/号?(Markdown编辑器)

热门文章

  1. U3D连接MySQL数据库详细步骤解析
  2. Spring 定时任务源码解析
  3. 冯学荣:古代中国人为什么贫穷
  4. [华为机试练习题]28.报数
  5. svr公式推导_SVR美白公式——一个元素都不能少
  6. Aja检验用户名是否存在
  7. IDE解决算法4中环境配置问题algs.jar
  8. 全部1500左右 近期各品牌最新超值手机
  9. Lua元表(__index方法个人理解)
  10. File,字符集基础知识,IO流学习(一)