有过嵌入式开发经验的都知道日志的存储是个比较麻烦的问题。以前ARM开源了一款项目:cmbacktrace,github地址就不贴了,能够将crash时的堆栈信息进行保存。可是对于手机app来讲,光保存程序崩溃时的日志信息时远远不够的,根本没法定位到具体缘由。在讲解淘系使用的日志系统以前,先看一下最通用的日志系统。
git
通用方案一github
主流的日志模块好比logback,是属于实时日志记录系统,也就是每产生一句日志就进行加密存入磁盘文件。这样有个缺点就是I/O过于密集占用大量CPU资源,影响程序性能,极易卡顿。通常使用与app开发调试阶段,正式发版确定要把log功能关闭,等用户产生程序崩溃等反应问题时,才从新打一个调试包进行复现。可是不少时候场景不彻底相同,很难复现问题。算法
前文有讲过cache数据与磁盘数据之间的关系:进程是如何使用内存的?缓存
程序写文件操做的时候,并非直接操做磁盘里的文件,而是先把数据写入系统缓存,也就是脏页中,而后操做系统的守护进程update进程会定时(通常为30s)将脏页更新到磁盘里。数据写入磁盘存在两次拷贝:从用户空间内存拷贝到内核空间内存,而后从内核空间flush写入到磁盘。加上具体写入磁盘的操做没法由程序控制,所以这里会形成CPU峰值太高致使性能下降。微信
通用方案二网络
直接使用Android的logsdk固然方便,可是会形成性能问题,那么咱们能够先将日志保存到内存缓存,达到必定大小后再加密写入文件。同时为了减小数据流,先对数据进行压缩再写入(借鉴HTTP网络传输的作法)。总体方案以下图:app
这种方案不须要实时保存日志,不会产生CPU峰值。可是丢日志的问题仍然没解决。好比程序crash异常退出时,不少场景并不会有系统事件通知,也就没法保存crash时的堆栈。鹅厂的嵌入式操做系统Tencenttiny OS宣称在stm32系列单板上解决了这个问题,可是使用起来仍然差强人意。更为重要的是,Android系统比嵌入式系统复杂的多,不少方案没法直接套用。
异步
手淘优化方案jsp
手淘app主要从两个方面来提升日志使用效率。性能
mmap
经过调用mmap存储映射I/O,具体方案就是将磁盘文件映射到用户空间缓冲区上,对内存缓冲区执行数据操做时,至关于操做磁盘中的文件。这样的好处就是不须要使用read和write。映射方案以下图所示。
使用mmap的好处是免去一次实时数据拷贝,同时能够经过调用msync、munmap将数据脏页写回磁盘文件,对于应用层可控(起一个后台线程异步执行就行)。优化后具体效果能够参看APUE图14-28。
这里要注意,intmadvise(caddr_t addr, size_t len, int advice);这个接口在Unix说明是能够经过使用madv_willneed参数来预加载磁盘文件到内存,可是在mac上实验并没什么效果,所以妥善的方案是对每一个页执行读取一个字节的操做,这样保证文件加载到内存中。
数据压缩
数据是先加密仍是先压缩?标准作法是先压缩再加密。明文通常都有冗余度,压缩以后内容会变少。相反若是先加密,会破坏文件的冗余度。通用的压缩方法主要有gzip、huffman等,手淘借鉴HTTP2的hpack头部压缩,在facebook开源的zstd压缩算法基础上进行了网络传输的优化。由于日志中一般会有大量相同特性的短语,所以会在服务端常驻一个字典,而且适时更新由客户端拉取,经过字典压缩提高压缩率。同时为了防止数据损坏影响整个压缩包没法解压,采用流式压缩(即日志达到32k时进行压缩)。分块压缩虽然整体效率略低,可是由于不会在短期内几种打包压缩,不会形成CPU峰值。
实际使用过程当中还对外置SD卡进行了适配,当SD卡被删除时会将日志存入临时缓存,每次进程被杀掉时清理缓存,防止长时间占用用户空间形成舆情。
参考:
https://github.com/facebook/zstd
APUE 第14章
本文分享自微信公众号 - 机械猿(on_ourway)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。