欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android高性能日志寫入方案的實(shí)現(xiàn)

 更新時間:2019年01月13日 10:54:01   作者:chay  
這篇文章主要給大家介紹了關(guān)于Android高性能日志寫入方案的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

公司目前在做一款企業(yè)級智能客服系統(tǒng),對于系統(tǒng)穩(wěn)定性要求很高,不過難保用戶在使用中不會出現(xiàn)問題,而 Android SDK 集成在客戶的 APP 中,同時由于 Android 碎片化的問題,對于 SDK 的問題排查就顯得尤為困難,因此記錄下用戶的操作日志就顯得極為重要。

初始方案

一開始,SDK 記錄日志的方式是直接通過寫文件,當(dāng)有一條日志要寫入的時候,首先,打開文件,然后寫入日志,最后關(guān)閉文件。這樣做的問題就在于頻繁的IO操作,影響程序的性能,而且 SDK 為了保證消息的及時性,還維護(hù)了一個后臺進(jìn)程,當(dāng)其中一個進(jìn)程進(jìn)行日志寫入時,另一個就會被鎖在門外等著,問題就愈發(fā)嚴(yán)重。使用這種方案雖然當(dāng)前看上去對程序的影響不大,但是隨著日志量的增加,更多的IO操作,一定會造成性能瓶頸。

下面我們來分析下直接寫入文件的流程:

  • 用戶發(fā)起 write 操作
  • 操作系統(tǒng)查找頁緩存
    a.若未命中,則產(chǎn)生缺頁異常,然后創(chuàng)建頁緩存,將用戶傳入的內(nèi)容寫入頁緩存
    b.若命中,則直接將用戶傳入的內(nèi)容寫入頁緩存
  • 用戶 write 調(diào)用完成
  • 頁被修改后成為臟頁,操作系統(tǒng)有兩種機(jī)制將臟頁寫回磁盤
    a.用戶手動調(diào)用 fsync()
    b.由 pdflush 進(jìn)程定時將臟頁寫回磁盤

可以看出,數(shù)據(jù)從程序?qū)懭氲酱疟P的過程中,其實(shí)牽涉到兩次數(shù)據(jù)拷貝:一次是用戶空間內(nèi)存拷貝到內(nèi)核空間的緩存,一次是回寫時內(nèi)核空間的緩存到硬盤的拷貝。當(dāng)發(fā)生回寫時也涉及到了內(nèi)核空間和用戶空間頻繁切換。

而且相對于機(jī)械硬盤,SSD 存儲還有一個“寫入放大”的問題。這個問題主要和 SSD 存儲的物理結(jié)構(gòu)有關(guān)。當(dāng) SSD 被全部寫過一遍之后,再寫入的數(shù)據(jù)是不可以直接更新,只可以通過覆蓋重寫,在覆蓋之前需要先擦除數(shù)據(jù)。但寫入的最小單位是 Page,擦除的最小單位是 Block,而 Block 遠(yuǎn)大于 Page,所以在寫入新數(shù)據(jù)時就需要先把 Block 上的數(shù)據(jù)讀出來和要寫入的數(shù)據(jù)合并在一起,再把 Block 擦除,最后把讀出來的數(shù)據(jù)重新寫入到存儲上,這樣導(dǎo)致實(shí)際寫入的數(shù)據(jù)可能遠(yuǎn)遠(yuǎn)大于最開始需要寫入的數(shù)據(jù)。

沒想到簡單的寫文件竟然涉及了這么多操作,只是對于應(yīng)用層透明而已。

既然每寫一次文件會執(zhí)行這么多次操作,那么我們能不能將日志緩存起來,當(dāng)達(dá)到一定的數(shù)量后再一次性的寫入磁盤中呢?
這樣確實(shí)能夠大量減少 IO 次數(shù),但是卻會引發(fā)另一個更嚴(yán)重的問題——丟日志

把日志緩存在內(nèi)存中,當(dāng)程序發(fā)生 Crash 或進(jìn)程被殺后就無法保證日志的完整性,而且由于 SDK 存在多進(jìn)程,也無法保證多進(jìn)程下日志的順序。

一個完善的日志方案,需要滿足

  • 高效,不能影響系統(tǒng)性能,不能因?yàn)橐肓巳罩灸K而造成應(yīng)用卡頓
  • 保證日志的完整性,如果不能保證日志完整,那么日志收集就沒有意義了
  • 對于多進(jìn)程應(yīng)用,要保證最終看到的日志順序的準(zhǔn)確性

高性能方案

既然無法減少寫入次數(shù),那么我們能不能在寫文件的過程中去優(yōu)化呢?

答案是可以的,使用 mmap

mmap是一種內(nèi)存映射文件的方法,即將一個文件或者其它對象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對映關(guān)系,函數(shù)原型如下

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

 

mmap操作提供了一種機(jī)制,讓用戶程序直接訪問設(shè)備內(nèi)存,這種機(jī)制,相比較在用戶空間和內(nèi)核空間互相拷貝數(shù)據(jù),效率更高。在要求高性能的應(yīng)用中比較常用。

時 mmap 能夠保證日志的完整性,mmap 的回寫時機(jī):

  • 內(nèi)存不足
  • 進(jìn)程退出
  • 調(diào)用 msync 或者 munmap
  • 不設(shè)置 MAP_NOSYNC 情況下 30s-60s(僅限FreeBSD)

當(dāng)映射一個文件后,程序就會在 native 內(nèi)存中申請一塊相同大小的空間,因此建議每次映射一小段內(nèi)容,如 64k,寫滿后再重新映射文件后面的內(nèi)容。

日志寫入性能和完整性的問題解決了,那么如何保證多進(jìn)程下日志的順序呢?

由于 mmap 是采用共享內(nèi)存的方式寫入數(shù)據(jù),如果兩個進(jìn)程同時映射一個文件,那么一定會造成日志覆蓋的問題。

既然不能直接保證順序,那我們只能退而求其次,兩個進(jìn)程分別映射不同的文件,每天合并一次,合并時對日志進(jìn)行排序。

繼續(xù)優(yōu)化

根據(jù)上述方案,設(shè)計(jì) jni 接口,打包 so,引入 SDK,看似沒什么問題了,但是作為一款 SDK,總覺得包含 so 不太友好,在一定程度上會增加接入的難度。

那么能不能不用 so 呢?

其實(shí) Java 中已經(jīng)提供了內(nèi)存映射的實(shí)現(xiàn)——MappedByteBuffer

MappedByteBuffer 位于 Java NIO 包下,用于將文件內(nèi)容映射到緩沖區(qū),使用的即是 mmap 技術(shù)。通過 FileChannel 的 map 方法可以創(chuàng)建緩沖區(qū)

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

為了測試 MappedByteBuffer 的效率,我們把 64byte 的數(shù)據(jù)分別寫入內(nèi)存、MappedByteBuffer 和磁盤文件 50 萬次,并統(tǒng)計(jì)耗時

方法 耗時
內(nèi)存 384ms
MappedByteBuffer 700ms
磁盤文件 16805ms

可以看出 MappedByteBuffer 雖然不及寫入內(nèi)存的性能,但是相比較寫入磁盤文件,已經(jīng)有了質(zhì)的提升。

總結(jié)

本文主要分析了直接寫文件記錄日志方式存在的問題,并引申出高性能文件寫入方案 mmap,兼顧了寫入性能和完整性,并通過補(bǔ)償方案確保多進(jìn)程下日志的順序。最后發(fā)現(xiàn)了內(nèi)存映射在 Java 層的實(shí)現(xiàn),避免了引入 so。

好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

最新評論