3f1f1353a01e42ca0744ac9467b9270f
Android 架构之高性能移动端日志系统

《亿级Android架构》小专栏文章列表:

《亿级 Android 架构》专栏随谈》

《Android 架构之网络连接与加速》

《Android 架构之长连接技术》

《Android 架构之高可用移动网络连接》

《Android 架构之网络安全演进》

《Android 架构之高性能移动端日志系统》

正文

小专栏的读者好,在《亿级Android架构》的前几篇文章里,我们围绕网络的连接优化长连接安全演进等多个方面讲解了国内大厂的网络架构体系,其中涉及了微信Mars美团的Shark阿里、蚂蚁的高可用网络体系等。

这一次,我们再来谈一谈在Android架构里平时默默无闻但关键时刻却至关重要的日志系统。

日志系统

日志系统对移动应用而言非常重要,它可以帮助我们定位崩溃、统计用户行为、发现代码运行问题等。这次我们结合美团的Logan、微信的xLog等著名开源项目,谈一谈如何搭建一套高性能的日志系统和其中核心的技术原理。

在文章开始前,读者可以先考虑一下几个问题:

  1. 很多开发者应该清楚我们只会在Debug环境下让Logcat去打印日志,一旦上线则会关闭这个开关,以防止过多IO操作影响到App正常运行。但这些日志在定位问题时却很重要,如何做到平衡呢?
  2. 日志在我们定位某些Crash时非常有用,但常常遇到的问题是在Crash发生时,我们的日志还没来得及写入文件,导致不完整,如何解决呢?
  3. 日志如何上报给后端呢?比如老板或者某位用户要你定位某个问题,而这个问题你却无法复现,如何快速获取这个用户手机上的日志呢?

I. 高性能背后的核心技术

对于xLogLogan有一定了解的同学应该知道,这两个库的重点在于解决了移动端日志系统的流畅性、完整性、安全性、容错性。下面我们针对这些特性,阐述下xLogLogan是如何解决的。

日志系统的基本流程就是采集代码里产生的各种日志数据,通过IO写入到文件。

首先我们假设,每次产生一条日志,我们就立即写入文件。这样子可以保证日志所有日志都能安全落地,不会发生丢失。但是,这里面存在频繁的IO操作,这会导致App自身业务逻辑运行变慢,出现卡顿等现象。

那么,我们考虑先将日志写入内存缓存,等达到一定量的时候,再一起压缩并写入文件。这样的话对于App流畅性就没有多大影响,但带来了新的问题:

  1. 如果在写入文件前App出现Crash,那么这部分日志就丢失了;
  2. 每次写入文件时,都需要做一次集中压缩和加密,这些是CPU密集型操作,容易出现CPU波峰和卡顿;

那么,如何既能保证App的流畅性,又能保证日志的完整性呢?可以想象下,我们需要的方案要符合下面几点特征:

  1. 不实时写入文件,防止频繁IO,但是又不存在丢失风险;
  2. 将压缩分摊成多次,而非一次集中压缩;

1. MMAP机制

针对第一点:不实时写入文件,防止频繁IO,但是又不存在丢失风险xLogLogan都采用了相同的系统机制:MMAP。MMAP是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系。

简单来说,MMAP实际是一段内存空间,我们可以实时将日志写入MMAP,因为它是内存,所以速度非常快;而另外,它也不存在丢失风险,因为在如内存不足,进程退出的时候操作系统会自动回写文件,因此也极大降低了丢失的风险。

如果对于MMAP的具体代码感兴趣,可以参考Logan里的mmap_util.c,底层使用了NDK里提供的mman.h内部的void* mmap(void* __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset);方法。

2. xLog的短语式压缩

在压缩方面,xLog采用的方案是每产生一条日志,都进行压缩且写入MMAP。这里采用的压缩算法是LZ77压缩。压缩率可以达到83.7%。

LZ77是一种基于字典的算法,它将长字符串(也称为短语)编码成短小的标记,用小标记代替字典中的短语,从而达到压缩的目的。感兴趣的同学可以在文末找到相关资料。

这种压缩算法的核心就是从以前压缩的内容里找到相同的短语,然后把当前的文本短语换成数字,参考下图:

3. Logan的流式Gzip压缩

top Created with Sketch.