Android高性能日志写入方案-mmap

前言

最近在做一个新零售的收银app,对于app稳定性要求比较高,但是难免会出现一些难以复现的问题,针对这些问题,分析日志有时候是解决问题的必要手段。下面我们主要分析下日志写入方案的实现。详细代码可参考AwesomeLog,如果能够帮到你,希望给个star,感谢。

常规方案的缺陷

  • 性能问题:一开始日志的写入就是通过标准I/O直接写文件,当有一条日志要写入的时候,首先,打开文件,然后写入日志,最后关闭文件。但是写文件是 IO 操作,随着日志量的增加,更多的IO操作,一定会造成性能瓶颈。为什么这么说呢?因为数据从程序写入到磁盘的过程中,其实牵涉到两次数据拷贝:一次是用户空间内存拷贝到内核空间的缓存,一次是回写时内核空间的缓存到硬盘的拷贝。当发生回写时也涉及到了内核空间和用户空间频繁切换
  • 丢日志:为了解决性能问题,直接想到就是减少I/O操作,我们可以先把日志缓存到内存中,当达到一定数量或者在合适的时机将内存里的日志写入磁盘中。这样似乎可以减少I/O操作,但是在将内存里的日志写入磁盘的过程中,app被强杀了或者Crash了的话,这样会造成更严重的问题,日志丢失。

看到这里,难道真的就没有高性能又能保证日志完整性的方案了吗?答案是mmap。mmap是个什么鬼?我们接着往下看。

什么是mmap

mmap是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系;实现这样的映射关系后,进程就可以采用指针的方式读写操作这一块内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必调用read,write等系统调用函数,相反,内核空间堆这段区域的修改也直接反应到用户空间,从而可以实现不同进程间的文件共享。

网上很多文章都说 mmap 完全绕开了页缓存机制,其实这并不正确。我们最终映射的物理内存依然在页缓存中,它可以带来的好处有:

  • 减少系统调用。我们只需要一次 mmap() 系统调用,后续所有的调用像操作内存一样,而不会出现大量的 read/write 系统调用。
  • 减少数据拷贝。普通的 read() 调用,数据需要经过两次拷贝;而 mmap 只需要从磁盘拷贝一次就可以了,并且由于做过内存映射,也不需要再拷贝回用户空间。
  • 可靠性高。mmap 把数据写入页缓存后,跟缓存 I/O 的延迟写机制一样,可以依靠内核线程定期写回磁盘。
mmap.jpg

从上面的图看来,我们使用 mmap 仅仅只需要一次数据拷贝。

mmap使用场景

mmap 比较适合于对同一块区域频繁读写的情况,推荐也使用线程来操作。

  • 用户日志、数据上报都满足这种场景,微信开源的 mars 框架中的 xlog模块也是基于 mmap 特性实现的。
  • 需要跨进程同步的时候,mmap 也是一个不错的选择,Android 跨进程通信有自己独有的 Binder 机制,它内部也是使用 mmap 实现。

具体实现

在Android中可以将文件通过Java提供的MappedByteBuffer映射到内存,然后进行读写。(微信的xlog模块mmap实现是基于C++代码实现)

MappedByteBuffer 位于 Java NIO 包下,用于将文件内容映射到缓冲区,使用的即是 mmap 技术。通过 FileChannel 的 map 方法可以创建缓冲区。

RandomAccessFile raf = new RandomAccessFile(file, "rw");
//position映射文件的起始位置,size映射文件的大小
MappedByteBuffer buffer = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, position, size);
//往缓冲区里写入字节数据
buffer.put(log);

有一点比较坑,Java 虽然提供了 map 方法,但是并没有提供 unmap 方法,通过 Google 得知 unmap 方法是有的,不过是私有的,我们可以通过反射调用获取unmap方法(Android 9.0以上对反射做了限制,可以参考这篇博文绕过限制)

    /**
     * 解除内存与文件的映射
     * */
    private void unmap(MappedByteBuffer mbbi) {
        if (mbbi == null) {
            return;
        }
        try {
            Class<?> clazz = Class.forName("sun.nio.ch.FileChannelImpl");
            Method m = clazz.getDeclaredMethod("unmap", MappedByteBuffer.class);
            m.setAccessible(true);
            m.invoke(null, mbbi);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

在这里记录一个点,刚开始在写入文件的时候,我是使用mmap将日志直接写入文件,这样的话需要通过代码去实现动态扩容,挺麻烦的,后来发现xlog是将日志写入高速缓冲区,这块高速缓冲区是使用mmap映射出来的内存区,被映射的磁盘文件是它新建的一个缓存文件,当高速缓冲区内容写到一定阈值时,通知后台线程将缓冲区的内容写入文件。借鉴xlog的方案,后面我们也改为先写入缓存中,当写满了之后,再flush到目标文件,也可以手动调用flush,将缓存刷新到目标文件

至于微信为什么这么做,肯定也是出于性能的原因啦,具体的可参考微信跨平台组件mars-xlog架构分析及迁移思路

总结

这篇文章主要讲了日志写入常规方案存在的一些缺陷以及原因,进而引出mmap的定义,优势和使用场景。最后主要讲了mmap的具体实现以及如何应用到日志写入当中。详细代码可参考AwesomeLog,如果能够帮到你,希望给个star,感谢

参考文档:《Android开发高手课》、微信xlog

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 前言 公司目前在做一款企业级智能客服系统,对于系统稳定性要求很高,不过难保用户在使用中不会出现问题,而 Andro...
    kkgo阅读 498评论 0 0
  • 前言 公司目前在做一款企业级智能客服系统,对于系统稳定性要求很高,不过难保用户在使用中不会出现问题,而 Andro...
    王晨彦阅读 9,521评论 9 24
  • 金色世纪他家的配套服务非常齐全,都很不错,而且不定期举办读书沙龙、新书分享会、艺术展等。我觉得对于像我这样经常出差...
    ii来日可期阅读 165评论 2 0
  • 赵鹏,以沃科技 【日精进打卡第4天】 【知~学习】 《六项精进》1遍 共1遍 《大学》1遍 共1遍 •••••• ...
    成墨画弦阅读 133评论 0 0
  • 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独...
    中v中阅读 2,518评论 0 2