您好,登錄后才能下訂單哦!
前言
公司目前在做一款企業級智能客服系統,對于系統穩定性要求很高,不過難保用戶在使用中不會出現問題,而 Android SDK 集成在客戶的 APP 中,同時由于 Android 碎片化的問題,對于 SDK 的問題排查就顯得尤為困難,因此記錄下用戶的操作日志就顯得極為重要。
初始方案
一開始,SDK 記錄日志的方式是直接通過寫文件,當有一條日志要寫入的時候,首先,打開文件,然后寫入日志,最后關閉文件。這樣做的問題就在于頻繁的IO操作,影響程序的性能,而且 SDK 為了保證消息的及時性,還維護了一個后臺進程,當其中一個進程進行日志寫入時,另一個就會被鎖在門外等著,問題就愈發嚴重。使用這種方案雖然當前看上去對程序的影響不大,但是隨著日志量的增加,更多的IO操作,一定會造成性能瓶頸。
下面我們來分析下直接寫入文件的流程:
可以看出,數據從程序寫入到磁盤的過程中,其實牽涉到兩次數據拷貝:一次是用戶空間內存拷貝到內核空間的緩存,一次是回寫時內核空間的緩存到硬盤的拷貝。當發生回寫時也涉及到了內核空間和用戶空間頻繁切換。
而且相對于機械硬盤,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 的回寫時機:
當映射一個文件后,程序就會在 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。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。