作者|潘文超(開風)
出品|阿里巴巴新零售淘系技術部
導讀:本文是作者在 「Top100全球軟體案例研究峰會」上分享的——手淘Native治理,結合分享後的反饋焦點,從 native 問題線下發現和快速定位、新so集成標準、線上歷史native問題治理等幾個方面為大家介紹,特產此文。
掃描下方二維碼,關注「淘系技術」微信公眾號,回復「native」即可獲取本次分享完整版 PPT 資料!
無線客戶端 crash 一般分為 java crash 和 native crash ,無線應用往往為了追求更好的性能,把一些複雜的計算放到 native 層去實現,java 層通過 jni 調用 native 層實現,滿足功能場景;在 native 層產生的崩潰就產生了 native crash ;無線客戶端衡量穩定性的最重要的指標就是 crash 率,穩定性一直是各系統治理的重中之重,也是一直繞不開的話題,而 native 是目前業界認為治理最有難度,也是要重點突破的方向。接下來分析一下為什麼 native 治理值得去做以及如何做好。
從圖中我們可以看出,java crash 正常可以維持在較好的水平,手淘 native crash 一般比 java crash 要高,大促期間,由於手淘內存瓶頸, native crash 率會漲到日常水準的2-3倍。
從數據可見,native crash 是 java crash 的6倍之多,如果要想進一步突破,native 是有很大空間的,但 native 問題一般都很難定位, 堆棧不全,或者堆棧都集中在系統 so 上,無法直接定位問題,所以想要突破難度很大。手淘穩定性再上升一個臺階,native crash是瓶頸,需要突破。
(1)難點一:crash堆棧目前絕大部分只有系統so,缺乏問題關聯的業務模塊so,定位問題難度大,可以看一下如下的一個native crash堆棧,堆棧中的so都是系統的,無法直接定位業務問題。
(2)難點二:線上so都是去符號化的,即使堆棧中有業務so,也需要記錄該APP版本對應的符號化so做反解才能拿到能看得懂的堆棧。這點已經通過第一階段的native工程標準化解決,打出來的SDK包裡面必須要有對應的符號化so才可以集成。
(3)難點三:目前線下有效提前發現native問題的手段缺乏,想要提前發現,需要平臺工具和有效手段。
要治理、要解決問題,首先得理清目前導致 native crash 的問題,在做之前,做了一下 crash 數據分析,於是手動撈取了當時近5個版本的 top native crash 數據,佔比最多的就是sig 6和sig 11。
那能說明什麼問題呢?signal 6這種崩潰信號要看具體場景,但根據具體數據分析,手淘裡面一般都是堆棧溢出、OOM 等導致的。signal 11 這種崩潰基本就鎖定為內存問題了。
根據實際數據,大部分 crash 原因是因為內存,可以初步下的結論是,目前手淘 native crash 治理的關鍵是內存,解決手淘native內存相關的問題即可解決掉不部分問題。
對集團內及業界的一些產品做了一些調研,詳細如下:
分別從使用成本、功能支撐、是否有堆棧能力,性能如何等維度進行了比較,其實我們的訴求是希望不需要 root 就能 run 起來,因為我們要持續集成、線上能灰度驗證,線下可以大規模任務執行、並且可以做手淘 native 問題沉澱,能把問題沉澱做成檢查項,可以涵蓋解決主要的內存問題,不止是內存洩漏。因此開發了Native Finder,希望能徹底治理好手淘 Native 問題。
Native Finder是利用elf hook的原理,對malloc、free、realloc、alloc、mmap、munmap 等操作進行 hook,在自己的 hook 函數中,做一些智能分析和統計,並最後會調用系統內存操作函數,不破壞 so 原本發起的 malloc 等操作。
這個方案的缺點是會有一定的性能損耗,畢竟在 malloc 等操作中增加了一些分析和統計操作,但性能影響還好,在開了堆棧能力之後,APP性能會受影響,這個也是後面要優化的地方。整體技術方案如下:
首先在前期,花了比較多時間去研究歷史數據及問題,認真分析 crash 的關鍵問題和痛點是什麼,才找準了方向,分析出來內存問題是最痛的點。治理過程總結如上圖,分為5個階段。接一下講詳細介紹結合Native Finder工具平臺的治理過程和心得。
治理總結如下:
總結起來這個階段我們做了3件事情:
此階段發現了幾個堆破壞的問題,但是經過好幾天反覆線下執行 monkey,並未有任何進展。
後續調整思路,開始逐個分析線上存在的 native crash,並根據這些 crash 特徵和根因開始沉澱經典問題,並把這些問題做成檢查項,跟同學交流和對焦後。
通過進一步的數據,發現內存 OOM 是目前優先級較高,且比較嚴重的問題,所以開始做這方面的技術建設,跟 crashSDK 打通,Native Finder 中統計和分析 so 維度佔用內存未釋放的數據,在 crash 的時候做內存信息 dump,並輸出輔助信息,例如 malloc、mmap 次數、大內存(大於2M,屬於大內存申請,可配置,可動態調整)的申請等信息。接下來看一下線下monkey 驅動階段
線下 monkey 並不能都能復現問題,藉助工具平臺拿到關鍵信息去做問題解決和定位,我們希望場景更加豐富和多樣,所以我們把 Native Finder 放到線上,做了線上灰度。
隨即把 Native Finder 放到線上做外灰,在用戶真實操作場景下的 crash,拿到 crash 時的內存 dump,但是經過一段時間的線上 crash 內存信息採集,然後分析之後,沒發現明顯問題,從現在回頭看這個階段,其實當時的數據是能夠體現出問題的,只是當時的想法不對。
我們對線上crash做了分析,同時也在線下做了可疑場景的嘗試復現,復現過程中,我們也做了大量數據的對比分析;
分析發現:正常 crash 跟 native oom crash,在內存詳細數據上做對比,發現OOM crash 在 native vmsize 上有較大差異,又看了很多手淘 OOM crash,發現都是這個原因,vmsize 暴漲。
大家都知道,32 位系統下,系統 vmszie 只有4G,還要拋去一些系統內核佔用、以及共享內存佔用,vmsize比較有限,手淘又是一個體量很大的航空母艦,各個業務都想有最佳的業務體驗,都想用空間換時間,每個業務洩漏那麼一點,那手淘就被撐爆了,是累加洩漏的結果。
所以手淘 Android OOM 要一個一個解決,逐個挖出來,才能根治。我們整個過程沉澱了如下檢查項:一共沉澱了8項內存檢查,內存檢查項已經能覆蓋 80% 以上的內存問題了;fd 文件句柄檢查項一共沉澱了6項,fd 的檢查項已經能覆蓋幾乎95%以上的 fd 問題了。
我們同時也研發了本地調試模式,方便開發和測試同學,能快速在本地復現和定位問題,具體的技術方案如下:
朝著這個方向,對 Native Finder 做了逐步優化,開始陸續發現各種問題,治理初步階段,我們通過 Native Finder 工具平臺一共治理發現 20+ 問題,其中包括了多種問題類型,例如內存堆破壞、內存洩漏、OOM、內存操作越界、多次free、內存操作錯誤等。
同時手淘日常的 native crash 率也有明顯降低。到這裡,我們 native crash 已經初見成效。雖然通過治理,陸續發現了不少問題,但是還遠遠不夠,手淘內存問題依然嚴峻,特別是雙十一場景下,互動、活動以及各種場景鏈路互拉的場景,內存問題還是很嚴峻,後續還需更加努力。
接下來我們希望還能做的更多,現在才剛剛開始;手淘內存問題依然嚴峻,要徹底治理,需要建立卡口,發現問題自動加入必改問題池,形成良性循環,舊問題不斷發現解決的同時,還需要杜絕新問題的引入。
除了 native so 導致的內存問題,當前 H5 場景的內存問題也比較嚴峻,去手淘隨便拿幾個 H5 頁面,看一下內存增量,都超過內存標準,H5缺乏管控,下半年需要對H5內存做卡口嚴控;目前針對前端的內存洩漏,還沒有有效的手段去檢測發現,從native層看,都體現在內核上,內核應該是沒有問題的,如果有效發現前端代碼導致的內存洩漏,也是一個值得研究的點,不過先做卡口再做進一步突破。
今日吐槽:
大家都在 native 治理上遇到哪些坑?
歡迎評論區留言,和小橙子分享哦~