Page 488 - 《软件学报》2024年第4期
P. 488

2066                                                       软件学报  2024  年第  35  卷第  4  期


                 hilog_buffer 中最新的日志节点; 公用读指针       r, 指向当前  hilog_buffer 中最旧的日志节点; 读者指针      r i  (i∈N+), 会
                 指向介于最新和最旧之间的日志节点.
                    (3) 单生产者: 在  hilogd  中只运行一个生产者线程, hilog_buffer 中只有一个写指针         w. 当生产者线程收集到新
                 的日志数据时会操作写指针          w  将新数据插入到链表中, 这一过程包括以下            3  种情况: ① 插入数据比     w  所指数据更
                 新, 则在链表最新节点后插入数据, 移动            w  指针到最新节点. ② 插入数据比        w  所指数据更旧, 但比     r 所指数据更
                 新, 则操作   w  指针向前遍历, 找到合适的位置插入数据, 移动             w  指针回到最新节点. ③ 插入数据比         r 所指数据更
                 旧, 则在②的基础上, 还需将       r 指向新插入的节点.
                    (4) 多消费者: 消费者线程是日志打印线程或日志持久化线程, 在                  hilogd  中可能同时运行多个消费者线程. 每
                 个消费者拥有自己的读指针, 通过读指针按顺序读取                 log_buffer 中的数据. 第  i 个消费者提供服务时, 起始时将读
                 指针  r i 指向公共读指针   r 所指节点, 接下来操作      r i 依次读取后续节点.
                    (5) 同步问题: 单生产者和多个消费者访问           hilog_buffer 过程中, 可能会出现并发同步问题, 即消费者进行读取
                 的时候, 生产者可能同时对链表进行结构调整. 需要注意的是, 仅需处理生产者和消费者之间的同步问题即可, 消
                 费者之间不存在同步问题. 因此只需对生产者调整链表结构、消费者进行链表节点跳转这两类过程使用同一个线
                 程互斥锁   (pthread_mutex), 即可保证同步. 在锁的选择上, 考虑了线程互斥锁和           CAS  锁两种方案. CAS   锁的优势在
                 于不需要上下文切换, 速度快; 缺点在于需要轮询锁状态, CPU                消耗大, 并且存在     ABA  风险. 线程互斥锁的优势在
                 于  CPU  占用低、不存在    ABA  问题; 缺点在于需要额外消耗上下文切换的时间. 基于两者的特性分析, hilog_buffer
                 使用线程互斥锁更为合适, 原因如下: ① 资源消耗问题. hilog_buffer 的写日志线程与多个读日志线程需要互斥. 在
                 使用  CAS  锁的情况下, 若写日志线程取得锁, 则会同时存在多个线程轮询锁状态, 消耗                      CPU  资源, 对于计算能力
                 不丰富的移动终端设备不是好的选择. ② 临界区问题. hilog_buffer 的写日志操作较为复杂, 在加锁后需要判断
                 hilog_buffer 是否已满, 如果已满需要执行删除操作, 然后执行排序插入操作. 临界区执行时间较长, 因此整体延时
                 对线程互斥锁额外引入的上下文切换时间不敏感.
                    (6) 缓冲区容量问题: 缓冲区容量上限可配置, 当未达到上限时, 插入数据会导致                      hilog_buffer 长度增长, 删除
                 数据会导致    hilog_buffer 长度减小. 当  hilog_buffer 容量已达上限时, 插入数据会导致时间戳最早的一部分数据被
                 删除, 以存储最新的数据. 删除的过程为: 首先将公用读指针                 r 指向剩余数据中最旧的节点, 然后检查读者列表中
                 每个读者的读指针是否指向将被删除的节点, 如果是, 则将该读者的读指针指向公用读指针                             r 所指结点, 最后将需
                 要删除的节点内存释放.
                  3.3.6    HiLog  日志系统日志持久化与压缩
                    日志的持久化能够有效地防止由于系统崩溃或意外断电导致的日志丢失问题, 且崩溃或断电前持久化的日志
                 往往能够成为追溯操作系统问题的重要依据, 因此日志的持久化功能是日志系统的重要功能之一. HiLog                                提供日
                 志持久化机制, 即读取      hilog_buffer 内容, 写入到文件系统中.
                    为了减小持久化阶段的内存开销, 同时又保证日志数据量, HiLog                  采用了多文件轮转的持久化机制, 即规定日
                 志文件的总数和每个日志文件的大小, 这样仅需维持一个日志文件大小的内存空间, 即可实现数倍于日志文件大
                 小的持久化操作. 将内存中的日志数据写入文件之前, 检查当前目录下对应的文件数量和将要写入的文件的大小.
                 如果即将写入的文件大小超过规定阈值, 切换要写入的文件编号. 如果当前目录下文件数量超过规定阈值, 先删除
                 序号最小的文件, 然后对所有文件进行名称修改, 序号依次减                  1. 新建序号最大的文件, 作为写入目标, 如图           9  所示.
                    HiLog  日志系统提供两种不同的日志压缩方法, 一种是日志流压缩, 另一种是日志文件压缩.
                    日志流压缩方法首先将从日志缓冲区读取日志, 接下来将日志作为比特流输入压缩算法接口, 最后当将压缩
                 算法的输出数据量达到单文件大小阈值时写入文件. 日志作为高度格式化的数据, 可以以极低的压缩率执行压缩,
                 节省存储空间. 综合考虑了压缩时间和压缩率等压缩重要指标                    [31] , HiLog  采用  zlib [32] 作为流压缩算法库.
                    日志文件压缩方法是对日志文件进行压缩的手段, 是日志流压缩方法的补充. 日志文件压缩方法主要面向小
                 流量的写日志场景. 当日志流量较小时, 如果采用流压缩方法, 压缩算法的输出数据量达到单文件大小阈值需要很
                 长的时间, 期间一旦出现系统崩溃或断电问题, 就会导致内存中的日志数据丢失. 因此, 对于这种场景, 首先使用多
   483   484   485   486   487   488   489   490   491   492   493