高性能c++日志库
高性能c++日志库
背景
大规模分布式系统中, 系统的可观测性是一个待解决的问题,如何以低代码的方式保证并发安全,低性能成本的方式实现高可靠的日志库是实现可观测性的重要手段。同时在学习高性能日志库的实现过程中,我们能够体会到高性能编程的设计原则,以及其中所涉及到的Linux内核原理。
日志库作为一个最基础的组件,既要保证性能,又要保证实用。如何玩转高性能日志,可以说是基础架构研发的HelloWorld,在日志库的研发过程中程序员将领会一些软件设计中的基本问题。现有的日志库如boost.log/glog/log4j等等代码都过于庞大,并不适合提炼出日志库的精髓,因此本文将会手把手带领大家实现一个支持同步/异步等方式的日志库。
需求列表
  1. 动态修改日志库的配置参数
  1. 同步,异步的方式写入日志(字节流)到对端(文件,网络,数据库)
  1. 通过回调函数,实现用户自定义逻辑可插拔式注入
  1. 高性能高可靠的同时将日志写入多种对端(文件,网络,数据库)
关于日志库的核心问题
  1. 如何自定义日志的格式?
  • 不同场景下 需要的定制化信息是不同的,例如分布式场景下每一个请求都需要一个reqid进行标识,所以如何把对输入的日志消息进行统一的格式化是很必要的事情。
  1. 如何动态的修改日志级别?
  • 由于使用枚举来表示日志级别,而8位整型取值和赋值本身就是原子的,无需担心;
  1. 如何平衡磁盘与内存的IO效率?
  • 我们知道计算机组成中,磁盘最慢,因此在系统中我们一般不会同步刷盘,而是单独的后台线程来负责日志的持久化(生产者消费者模式)
  1. 多线程的写入日志时如何保证并发安全?
  • 多线程写日志存在安全问题,本身需要加锁,正是由于需要加锁,反而会降低写文件的效率。所以常见的日志库大多采用多生产单消费者方式来写文件,一个可行的优化,就是使用双buffer机制,来避免每次生产者向队列中push一条消息时,都要唤醒消费者。使用双buffer本质其实是批操作。 如何设计一个并发安全的高性能buffer是日志库设计的关键所在。
  1. 如何刷盘?何时滚动日志?
  1. 滚动要在时间与空间上进行控制同时也要避免空转
  1. 通过写入次数大于一个阈值的方法来避免写入次数过少的情况下 滚动与刷新日志
  1. 如何减少刷盘的次数从而提升性能?