前言
在黃山區(qū)等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都網(wǎng)站制作、網(wǎng)站建設(shè) 網(wǎng)站設(shè)計(jì)制作定制網(wǎng)站制作,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),成都品牌網(wǎng)站建設(shè),成都全網(wǎng)營(yíng)銷,外貿(mào)營(yíng)銷網(wǎng)站建設(shè),黃山區(qū)網(wǎng)站建設(shè)費(fèi)用合理。
公司目前在做一款企業(yè)級(jí)智能客服系統(tǒng),對(duì)于系統(tǒng)穩(wěn)定性要求很高,不過(guò)難保用戶在使用中不會(huì)出現(xiàn)問題,而 Android SDK 集成在客戶的 APP 中,同時(shí)由于 Android 碎片化的問題,對(duì)于 SDK 的問題排查就顯得尤為困難,因此記錄下用戶的操作日志就顯得極為重要。
初始方案
一開始,SDK 記錄日志的方式是直接通過(guò)寫文件,當(dāng)有一條日志要寫入的時(shí)候,首先,打開文件,然后寫入日志,最后關(guān)閉文件。這樣做的問題就在于頻繁的IO操作,影響程序的性能,而且 SDK 為了保證消息的及時(shí)性,還維護(hù)了一個(gè)后臺(tái)進(jìn)程,當(dāng)其中一個(gè)進(jìn)程進(jìn)行日志寫入時(shí),另一個(gè)就會(huì)被鎖在門外等著,問題就愈發(fā)嚴(yán)重。使用這種方案雖然當(dāng)前看上去對(duì)程序的影響不大,但是隨著日志量的增加,更多的IO操作,一定會(huì)造成性能瓶頸。
下面我們來(lái)分析下直接寫入文件的流程:
可以看出,數(shù)據(jù)從程序?qū)懭氲酱疟P的過(guò)程中,其實(shí)牽涉到兩次數(shù)據(jù)拷貝:一次是用戶空間內(nèi)存拷貝到內(nèi)核空間的緩存,一次是回寫時(shí)內(nèi)核空間的緩存到硬盤的拷貝。當(dāng)發(fā)生回寫時(shí)也涉及到了內(nèi)核空間和用戶空間頻繁切換。
而且相對(duì)于機(jī)械硬盤,SSD 存儲(chǔ)還有一個(gè)“寫入放大”的問題。這個(gè)問題主要和 SSD 存儲(chǔ)的物理結(jié)構(gòu)有關(guān)。當(dāng) SSD 被全部寫過(guò)一遍之后,再寫入的數(shù)據(jù)是不可以直接更新,只可以通過(guò)覆蓋重寫,在覆蓋之前需要先擦除數(shù)據(jù)。但寫入的最小單位是 Page,擦除的最小單位是 Block,而 Block 遠(yuǎn)大于 Page,所以在寫入新數(shù)據(jù)時(shí)就需要先把 Block 上的數(shù)據(jù)讀出來(lái)和要寫入的數(shù)據(jù)合并在一起,再把 Block 擦除,最后把讀出來(lái)的數(shù)據(jù)重新寫入到存儲(chǔ)上,這樣導(dǎo)致實(shí)際寫入的數(shù)據(jù)可能遠(yuǎn)遠(yuǎn)大于最開始需要寫入的數(shù)據(jù)。
沒想到簡(jiǎn)單的寫文件竟然涉及了這么多操作,只是對(duì)于應(yīng)用層透明而已。
既然每寫一次文件會(huì)執(zhí)行這么多次操作,那么我們能不能將日志緩存起來(lái),當(dāng)達(dá)到一定的數(shù)量后再一次性的寫入磁盤中呢?
這樣確實(shí)能夠大量減少 IO 次數(shù),但是卻會(huì)引發(fā)另一個(gè)更嚴(yán)重的問題——丟日志
把日志緩存在內(nèi)存中,當(dāng)程序發(fā)生 Crash 或進(jìn)程被殺后就無(wú)法保證日志的完整性,而且由于 SDK 存在多進(jìn)程,也無(wú)法保證多進(jìn)程下日志的順序。
一個(gè)完善的日志方案,需要滿足
高性能方案
既然無(wú)法減少寫入次數(shù),那么我們能不能在寫文件的過(guò)程中去優(yōu)化呢?
答案是可以的,使用 mmap
mmap是一種內(nèi)存映射文件的方法,即將一個(gè)文件或者其它對(duì)象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對(duì)映關(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)用中比較常用。
時(shí) mmap 能夠保證日志的完整性,mmap 的回寫時(shí)機(jī):
當(dāng)映射一個(gè)文件后,程序就會(huì)在 native 內(nèi)存中申請(qǐng)一塊相同大小的空間,因此建議每次映射一小段內(nèi)容,如 64k,寫滿后再重新映射文件后面的內(nèi)容。
日志寫入性能和完整性的問題解決了,那么如何保證多進(jìn)程下日志的順序呢?
由于 mmap 是采用共享內(nèi)存的方式寫入數(shù)據(jù),如果兩個(gè)進(jìn)程同時(shí)映射一個(gè)文件,那么一定會(huì)造成日志覆蓋的問題。
既然不能直接保證順序,那我們只能退而求其次,兩個(gè)進(jìn)程分別映射不同的文件,每天合并一次,合并時(shí)對(duì)日志進(jìn)行排序。
繼續(xù)優(yōu)化
根據(jù)上述方案,設(shè)計(jì) jni 接口,打包 so,引入 SDK,看似沒什么問題了,但是作為一款 SDK,總覺得包含 so 不太友好,在一定程度上會(huì)增加接入的難度。
那么能不能不用 so 呢?
其實(shí) Java 中已經(jīng)提供了內(nèi)存映射的實(shí)現(xiàn)——MappedByteBuffer
MappedByteBuffer 位于 Java NIO 包下,用于將文件內(nèi)容映射到緩沖區(qū),使用的即是 mmap 技術(shù)。通過(guò) FileChannel 的 map 方法可以創(chuàng)建緩沖區(qū)
RandomAccessFileraf = new RandomAccessFile(file, "rw"); MappedByteBuffer buffer = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, position, size);
為了測(cè)試 MappedByteBuffer 的效率,我們把 64byte 的數(shù)據(jù)分別寫入內(nèi)存、MappedByteBuffer 和磁盤文件 50 萬(wàn)次,并統(tǒng)計(jì)耗時(shí)
方法 | 耗時(shí) |
---|---|
內(nèi)存 | 384ms |
MappedByteBuffer | 700ms |
磁盤文件 | 16805ms |
可以看出 MappedByteBuffer 雖然不及寫入內(nèi)存的性能,但是相比較寫入磁盤文件,已經(jīng)有了質(zhì)的提升。
總結(jié)
本文主要分析了直接寫文件記錄日志方式存在的問題,并引申出高性能文件寫入方案 mmap,兼顧了寫入性能和完整性,并通過(guò)補(bǔ)償方案確保多進(jìn)程下日志的順序。最后發(fā)現(xiàn)了內(nèi)存映射在 Java 層的實(shí)現(xiàn),避免了引入 so。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)創(chuàng)新互聯(lián)的支持。
標(biāo)題名稱:Android高性能日志寫入方案的實(shí)現(xiàn)
標(biāo)題來(lái)源:http://jinyejixie.com/article22/ijjicc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、微信小程序、網(wǎng)站導(dǎo)航、App設(shè)計(jì)、服務(wù)器托管、關(guān)鍵詞優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)