Android高性能日志写入方案的实现

前言

公司目前在做一款企业级智能客服系统,对于系统稳定性要求很高,不过难保用户在使用中不会出现问题,而 Android SDK 集成在客户的 APP 中,同时由于 Android 碎片化的问题,对于 SDK 的问题排查就显得尤为困难,因此记录下用户的操作日志就显得极为重要。

初始方案

一开始,SDK 记录日志的方式是直接通过写文件,当有一条日志要写入的时候,首先,打开文件,然后写入日志,最后关闭文件。这样做的问题就在于频繁的IO操作,影响程序的性能,而且 SDK 为了保证消息的及时性,还维护了一个后台进程,当其中一个进程进行日志写入时,另一个就会被锁在门外等着,问题就愈发严重。使用这种方案虽然当前看上去对程序的影响不大,但是随着日志量的增加,更多的IO操作,一定会造成性能瓶颈。

下面我们来分析下直接写入文件的流程:

  • 用户发起 write 操作
  • 操作系统查找页缓存
    a.若未命中,则产生缺页异常,然后创建页缓存,将用户传入的内容写入页缓存
    b.若命中,则直接将用户传入的内容写入页缓存
  • 用户 write 调用完成
  • 页被修改后成为脏页,操作系统有两种机制将脏页写回磁盘
    a.用户手动调用 fsync()
    b.由 pdflush 进程定时将脏页写回磁盘

可以看出,数据从程序写入到磁盘的过程中,其实牵涉到两次数据拷贝:一次是用户空间内存拷贝到内核空间的缓存,一次是回写时内核空间的缓存到硬盘的拷贝。当发生回写时也涉及到了内核空间和用户空间频繁切换。

而且相对于机械硬盘,SSD 存储还有一个“写入放大”的问题。这个问题主要和 SSD 存储的物理结构有关。当 SSD 被全部写过一遍之后,再写入的数据是不可以直接更新,只可以通过覆盖重写,在覆盖之前需要先擦除数据。但写入的最小单位是 Page,擦除的最小单位是 Block,而 Block 远大于 Page,所以在写入新数据时就需要先把 Block 上的数据读出来和要写入的数据合并在一起,再把 Block 擦除,最后把读出来的数据重新写入到存储上,这样导致实际写入的数据可能远远大于最开始需要写入的数据。

没想到简单的写文件竟然涉及了这么多操作,只是对于应用层透明而已。

既然每写一次文件会执行这么多次操作,那么我们能不能将日志缓存起来,当达到一定的数量后再一次性的写入磁盘中呢?
这样确实能够大量减少 IO 次数,但是却会引发另一个更严重的问题——丢日志

把日志缓存在内存中,当程序发生 Crash 或进程被杀后就无法保证日志的完整性,而且由于 SDK 存在多进程,也无法保证多进程下日志的顺序。

一个完善的日志方案,需要满足

  • 高效,不能影响系统性能,不能因为引入了日志模块而造成应用卡顿
  • 保证日志的完整性,如果不能保证日志完整,那么日志收集就没有意义了
  • 对于多进程应用,要保证最终看到的日志顺序的准确性

高性能方案

既然无法减少写入次数,那么我们能不能在写文件的过程中去优化呢?

答案是可以的,使用 mmap

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系,函数原型如下

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

mmap操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。在要求高性能的应用中比较常用。

时 mmap 能够保证日志的完整性,mmap 的回写时机:

  • 内存不足
  • 进程退出
  • 调用 msync 或者 munmap
  • 不设置 MAP_NOSYNC 情况下 30s-60s(仅限FreeBSD)

当映射一个文件后,程序就会在 native 内存中申请一块相同大小的空间,因此建议每次映射一小段内容,如 64k,写满后再重新映射文件后面的内容。

日志写入性能和完整性的问题解决了,那么如何保证多进程下日志的顺序呢?

由于 mmap 是采用共享内存的方式写入数据,如果两个进程同时映射一个文件,那么一定会造成日志覆盖的问题。

既然不能直接保证顺序,那我们只能退而求其次,两个进程分别映射不同的文件,每天合并一次,合并时对日志进行排序。

继续优化

根据上述方案,设计 jni 接口,打包 so,引入 SDK,看似没什么问题了,但是作为一款 SDK,总觉得包含 so 不太友好,在一定程度上会增加接入的难度。

那么能不能不用 so 呢?

其实 Java 中已经提供了内存映射的实现——MappedByteBuffer

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

RandomAccessFileraf = new RandomAccessFile(file, "rw");
MappedByteBuffer buffer = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, position, size);

为了测试 MappedByteBuffer 的效率,我们把 64byte 的数据分别写入内存、MappedByteBuffer 和磁盘文件 50 万次,并统计耗时

方法 耗时
内存 384ms
MappedByteBuffer 700ms
磁盘文件 16805ms

可以看出 MappedByteBuffer 虽然不及写入内存的性能,但是相比较写入磁盘文件,已经有了质的提升。

总结

本文主要分析了直接写文件记录日志方式存在的问题,并引申出高性能文件写入方案 mmap,兼顾了写入性能和完整性,并通过补偿方案确保多进程下日志的顺序。最后发现了内存映射在 Java 层的实现,避免了引入 so。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android APP性能优化分析

    本文通过Android APP性能优化的四个方面做了详细分析,并对原理和重点做了详细解释,以下是全部内容: 说到 Android 系统手机,大部分人的印象是用了一段时间就变得有点卡顿,有些程序在运行期间莫名其妙的出现崩溃,打开系统文件夹一看,发现多了很多文件,然后用手机管家 APP 不断地进行清理优化 ,才感觉运行速度稍微提高了点,就算手机在各种性能跑分软件面前分数遥遥领先,还是感觉无论有多大的内存空间都远远不够用.相信每个使用 Android 系统的用户都有过以上类似经历,确实,Android

  • Android图片性能优化详解

    1. 图片的格式 目前移动端Android平台原生支持的图片格式主要有:JPEG.PNG.GIF.BMP.和WebP(自从Android 4.0开始支持),但是在Android应用开发中能够使用的编解码格式只有三种:JPEG.PNG.WebP,图片格式可以通过查看Bitmap类的CompressFormat枚举值来确定. public static enum CompressFormat { JPEG. PNG. WebP; private CompressFormat() { } } 如果要在

  • Android性能调优利器StrictMode应用分析

    作为Android开发,日常的开发工作中或多或少要接触到性能问题,比如我的Android程序运行缓慢卡顿,并且常常出现ANR对话框等等问题.既然有性能问题,就需要进行性能优化.正所谓工欲善其事,必先利其器.一个好的工具,可以帮助我们发现并定位问题,进而有的放矢进行解决.本文主要介绍StrictMode 在Android 应用开发中的应用和一些问题. 什么是StrictMode StrictMode意思为严格模式,是用来检测程序中违例情况的开发者工具.最常用的场景就是检测主线程中本地磁盘和网络读写

  • Android性能测试关注的指标整理

    性能测试过程中,出现的一些问题可直接导致了用户对当前app的使用率和卸载率,如果app使用时卡顿严重或者加载页面慢,cpu占用率高,导致app闪退等问题,在测试过程中,则需特别关注性能方面的体验,app性能好.ui设计美观.功能层级明确,路径层级较少,均可提升用户对app的使用率,性能测试中可关注的问题如下: 1.连接超时:app关注的首要问题,在移动应用中网络错误数据比例报错中最高的就是连接错误超时 2.闪退:点击某一个功能点出现闪退,客户的内心都崩溃了 3.卡顿.黑白屏: 4.崩溃:(优秀:

  • Android端TCP长连接的性能优化教程分享

    前言 大家应该都知道,在Android端实现TCP长连接场景其实不多,我们最熟悉的不过推送和HTTP协议的实现(OkHttp),本文讨论的是在实现推送长连接的情况下怎么来做性能优化,下文只是我的一点拙见,有不妥之处还望指出,下面话不多说了,来一起看看详细的介绍吧. 推送长连接 可以说大部分APP是离不开推送(push)这个功能的,不过平常我们都是接入第三方SDK(极光.个推等)居多,因为要做一个推送服务,不光客户端要编写相应的Socket通信代码,服务器端更是麻烦,要处理大规模的长连接服务,消息

  • Android高性能日志写入方案的实现

    前言 公司目前在做一款企业级智能客服系统,对于系统稳定性要求很高,不过难保用户在使用中不会出现问题,而 Android SDK 集成在客户的 APP 中,同时由于 Android 碎片化的问题,对于 SDK 的问题排查就显得尤为困难,因此记录下用户的操作日志就显得极为重要. 初始方案 一开始,SDK 记录日志的方式是直接通过写文件,当有一条日志要写入的时候,首先,打开文件,然后写入日志,最后关闭文件.这样做的问题就在于频繁的IO操作,影响程序的性能,而且 SDK 为了保证消息的及时性,还维护了一

  • 完整的Android表情功能处理方案

    Android表情功能处理方案概述 1.原理和实现思路 2.表情图片显示 3.表情面板 4.表情的输入框插入和删除 5.表情添加脚本 Android中表情功能,一般都不是用ImageView去设置图片实现的,表情一般会嵌套在文本之中,那么如何实现呢,这里就介绍一下其中的原理,此外还有相关功能的实现思路和具体代码. 先看下良心动态图 1.原理和思路 a.表情内容的数据格式 表情看上去是图片,但是在数据传输的时候本质上是一个特殊文本: 比如QQ表情就是一个 "/表情字母"的结构,比如害羞的

  • Python中更优雅的日志记录方案详解

    目录 常见使用 loguru 安装 基本使用 详细使用 在 Python 中,一般情况下我们可能直接用自带的 logging 模块来记录日志,包括我之前的时候也是一样.在使用时我们需要配置一些 Handler.Formatter 来进行一些处理,比如把日志输出到不同的位置,或者设置一个不同的输出格式,或者设置日志分块和备份.但其实个人感觉 logging 用起来其实并不是那么好用,其实主要还是配置较为繁琐. 常见使用 首先看看 logging 常见的解决方案吧,我一般会配置输出到文件.控制台和

  • php实现的简单日志写入函数

    本文实例讲述了php实现的简单日志写入函数.分享给大家供大家参考.具体实现方法如下: function log( $logthis ){ file_put_contents('logfile.log', date("Y-m-d H:i:s"). " " . $logthis. "\r\n", FILE_APPEND | LOCK_EX); } // use \r\n for new line on windows, just \n on linu

  • Android 数据库SQLite 写入SD卡的方法

    如果手机没有root,数据库文件是无法查看到的,不方便调试. 最好的办法是把数据库写进SD卡. 修改的地方有两处: 1.在你的helper类中把数据库文件名称 DATABASE_NAME 由原来的一个文件名,修改成路径的形式. 修改前:DATABASE_NAME = "demo.db" public class MyDBHelper extends SQLiteOpenHelper { public static final int VERSION = 1; //数据库版本号 publ

  • C#实现将日志写入文本文件的方法

    本文实例讲述了C#实现将日志写入文本文件的方法.分享给大家供大家参考.具体如下: 这里传入的参数是 要写的内容 using System.IO; public static void WriteLog(string strLog) { string sFilePath="d:\\"+DateTime.Now.ToString("yyyyMM"); string sFileName = "rizhi" + DateTime.Now.ToString

  • Android中图片压缩方案详解及源码下载

    Android中图片压缩方案详解及源码下载 图片的展示可以说在我们任何一个应用中都避免不了,可是大量的图片就会出现很多的问题,比如加载大图片或者多图时的OOM问题,可以移步到Android高效加载大图及多图避免程序OOM.还有一个问题就是图片的上传下载问题,往往我们都喜欢图片既清楚又占的内存小,也就是尽可能少的耗费我们的流量,这就是我今天所要讲述的问题:图片的压缩方案的详解. 1.质量压缩法 设置bitmap options属性,降低图片的质量,像素不会减少 第一个参数为需要压缩的bitmap图

  • Android向Excel写入数据导出U盘并发送邮件

    本文实例为大家分享了Android向Excel写入数据导出并发送邮件的具体代码,供大家参考,具体内容如下 创建Execl.写入Excel格式 public WriteExcel(Context mContext){ this.mContext = mContext; } // 创建excel表 public void createExcel(File file) { deleteExcel(file); WritableSheet ws = null; try { if (!file.exist

  • Android的日志系统分层与logcat使用

    android的日志系统有典型的android层次结构.本文指出路径,分析层次但不分析代码,这里还介绍logcat的使用和log_bg服务. 日志系统分层 1.先从驱动开始 linux-3.10/drivers/staging/android/logger.c linux-3.10/drivers/staging/android/logger.h logger_init创建4个日志设备文件/dev/main./dev/events./dev/radio./dev/system. 分析代码的话跟踪

  • android 6.0 写入SD卡的权限申请实例讲解

    6.0的手机对于写入手机需要申请权限的 我做了如下处理 下面我贴出代码 package com.example.admin.sdapplication; import android.Manifest; import android.app.Dialog; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Build; import android

随机推荐