成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

記一次 android 線上 oom 問題

番茄西紅柿 / 3191人閱讀

摘要:問題分析隨著回滾版本的放量,主端崩潰逐漸回歸正常,進(jìn)一步坐實(shí)了新版本存在問題。內(nèi)容非常多但都是重復(fù)的,看起來進(jìn)程沒有啟動,導(dǎo)致連接端一直在進(jìn)行重連。

背景

公司的主打產(chǎn)品是一款跨平臺的 App,我的部門負(fù)責(zé)為它提供底層的 sdk 用于數(shù)據(jù)傳輸,我負(fù)責(zé)的是 Adnroid 端的 sdk 開發(fā)。

sdk 并不直接加載在 App 主進(jìn)程,而是隔離在一個多帶帶進(jìn)程中,然后兩個進(jìn)程通過 tcp 連接進(jìn)行通信的,這樣做的目的是減少因 sdk 的崩潰帶來的主進(jìn)程 crash,為用戶帶來更好的體驗(yàn)。

如上圖所示,sdk 主要實(shí)現(xiàn)于 service.so 中被 Work 進(jìn)程加載,kernel.so 通過 jni 嵌入在 App 主進(jìn)程,前者作為偵聽端,后者是連接端。

不過這樣做有一個問題,當(dāng)偵聽端口被占用時(shí),兩個進(jìn)程就無法建立通信了,導(dǎo)致數(shù)據(jù)無法傳輸。為了解決這個問題,打算用本地 socket (unix domain socket) 代替 tcp socket,因?yàn)榍罢卟灰蕾嚩丝谔?,只依賴文件路徑,?Android 中的私有存儲可以有效的防止文件沖突。

這個替換過程也不能一蹴而就,因?yàn)?App 進(jìn)程加載的 so 與 Work 進(jìn)程加載的可能并不是一個版本,考慮到向后兼容,新的 service 版本需要同時(shí)偵聽 tcp 與 local 兩個通道,新的 kernel 版本也需要同時(shí)連接兩個通道,哪個先連接上就用哪個。

開發(fā)完成的自測階段一切正常,驗(yàn)證了以下組合:

連接端偵聽端結(jié)果
tcplocal, tcptcp 成功
locallocal, tcplocal 成功
local, tcptcptcp 成功
local, tcplocal, tcplocal, tcp 均成功,一般 local 搶先

結(jié)果符合預(yù)期,提測階段也順利通過,于是通過版本灰度,逐漸替換線上的舊版本,各個灰度階段觀察正常,最后正式全量發(fā)布。

問題發(fā)生

全量兩天后,正式將特性分支合入 master,結(jié)果合入沒 30 分鐘,QA 反饋主端 oom (out of memory) 崩潰異常升高,需要回滾版本驗(yàn)證。

了解了一下情況,發(fā)現(xiàn)主端的全部版本崩潰率確實(shí)從 0.01% 升高到了 0.05%~0.07% 的水平,且大量新增的崩潰類型堆棧顯示 oom 信息,最關(guān)鍵的是崩潰升高的趨勢和 sdk 灰度的節(jié)奏完全吻合,而這期間主端也沒有發(fā)布新的版本,于是只能回滾 sdk 版本嘗試。

糟糕的是剛剛合入代碼,使用 revert 回滾提交的幾個 commit,又出現(xiàn)了一大堆沖突提示。正在解決沖突的過程中,QA 等不急了,建議從之前合入的位置直接拉分支打版本,一頓操作猛于虎,很快就打好了回滾版本,當(dāng)天就通過測試小流量了。

第二天來了一看,崩潰率果然應(yīng)聲下降,于是 QA 開啟全量修復(fù)。同時(shí)研究了一個短平快的 master 回滾方案:新建一個目錄,clone 并 checkout 到合入前的代碼,將 .git 目錄刪除后用這個目錄覆蓋舊的工作目錄,最后將所有 modified 的文件作為新版本直接提交。這樣做的好處是可以得到與合入前完全一樣的代碼,防止手工處理沖突引入新的變更。

問題分析

隨著回滾版本的放量,主端 oom 崩潰逐漸回歸正常,進(jìn)一步坐實(shí)了新版本存在問題。oom 問題非常不好排查,原因是崩潰時(shí)的堆棧與引入 bug 的地方已經(jīng)相差了十萬八千里,不能直接定位問題點(diǎn)。

好在這個版本之前做過一次小流量,看當(dāng)時(shí)的崩潰率沒有明顯升高,在準(zhǔn)備全量前,合入了 master 上的最新修改、ios 平臺的一些代碼等,因此重點(diǎn)排查兩個版本的差異部分,應(yīng)該就可以定位引入問題的點(diǎn)了。

走查了一遍,沒有發(fā)現(xiàn)明顯的內(nèi)存泄漏代碼:

  • master 是穩(wěn)定版本,不存在內(nèi)存泄漏;
  • ios 平臺代碼通過宏定義作了隔離,對 android 沒有影響;

只有一個地方非??梢伞@是一個日志上報(bào)操作,只在特定場景下發(fā)生,日志上報(bào)時(shí)并不是直接上報(bào)到服務(wù)器,而是放入一個隊(duì)列,再由專門的線程負(fù)責(zé)上傳。一次上報(bào)并不會占用太多內(nèi)存,但關(guān)鍵是一旦進(jìn)入這個特定場景,日志就會一直產(chǎn)生,而主端會在傳輸數(shù)據(jù)的過程中頻繁調(diào)用這個接口,導(dǎo)致大量的日志進(jìn)入隊(duì)列,特別是當(dāng)用戶處于非 WIFI 環(huán)境下,日志上報(bào)會被關(guān)閉來節(jié)省流量,進(jìn)一步加劇了隊(duì)列積壓,最終導(dǎo)致隊(duì)列瘋狂增長耗盡內(nèi)存……

知道了原因,改起來就簡單了,加一個 bool 標(biāo)記,上報(bào)過后設(shè)置這個標(biāo)記下次就不再上報(bào)了,因?yàn)檫@類日志有一條用來排查問題就足夠了。

問題定位

修復(fù)版都打好準(zhǔn)備送測了,老大的一句話提醒了我——最好能在本地復(fù)現(xiàn)一下。于是基于有問題的版本,稍加修改讓它一啟動就不停上報(bào)日志,關(guān)閉 WIFI 打開 4G,用這個版本在測試機(jī)上跑了一整天,進(jìn)程居然沒崩潰!

于是不得不評估一下日志上報(bào)的泄漏規(guī)模,按一條日志最大 300 字節(jié)、主端 2 次/秒的調(diào)用頻率計(jì)算,一天占用內(nèi)存為 300 * 2 * 3600 * 24 = 51840000 B < 50 MB,雖然也不小了,但是對于動轍 4~8 GB 的智能手機(jī)而言,實(shí)在不算什么,要想泄漏 4 GB 得不關(guān)機(jī)運(yùn)行將近 82 天,不要說現(xiàn)在沒有這樣一直不關(guān)機(jī)的用戶,即使有,Android 的后臺進(jìn)程優(yōu)化功能也早就將 App 殺了上萬遍了,哪容你泄漏這么久~ 別說你不是微信,就是微信系統(tǒng)該殺也就殺了~

與同事一起研究這個問題后,自己又提出來一個疑點(diǎn):假如是因?yàn)槿罩拘孤?dǎo)致的 oom,那應(yīng)該是 Work 進(jìn)程崩潰,而不是出現(xiàn)大量的 App 進(jìn)程崩潰。如果是因?yàn)閮?nèi)存耗盡導(dǎo)致系統(tǒng)上所有進(jìn)程崩潰,那也至少是崩潰率一起升高,而不像現(xiàn)在只有 App 進(jìn)程崩潰率升高,所以越看越不像是這個原因?qū)е碌摹?/p>

問題根因

正當(dāng)排查方向一片迷茫的時(shí)候,同事的一句話提醒了我——如果能抓到崩潰現(xiàn)場的日志就好辦了??墒窃趺醋ツ??崩潰平臺記錄的是崩潰時(shí)間和 CUID,后者用于標(biāo)識一次唯一的崩潰事件;日志抓取需要時(shí)間范圍和用戶 UID,而崩潰平臺并不提供 UID。

這時(shí)同事神秘兮兮的祭出了一條鏈接,點(diǎn)開一看:ID-Mapping,可以將各種系統(tǒng)的 ID 進(jìn)行批量轉(zhuǎn)換,其中就包括 CUID 向 UID 的轉(zhuǎn)換,好家伙,這不就是我想要的?老同事真的渾身都是寶,摸著他們過河錯不了~

大部分 UID 沒有撈取到日志,只有兩個用戶有日志。內(nèi)容非常多但都是重復(fù)的,看起來 Work 進(jìn)程沒有啟動,導(dǎo)致連接端一直在進(jìn)行重連。在連接后期都發(fā)現(xiàn)了這樣的日志:

2021-10-30T20:55:19.84255454 [b61e7920] {netio} LocalHandler::post_connect: local endpoint failed with system:24, fatal error2021-10-30T20:55:19.84408116 [b61e7920] {netio} kernel_message_transmit:handle_io: pipeerror|system:24 type=1|channel=12021-10-30T20:55:19.84480116 [b61e7920] {netio} kernel_message_transmit:handle_io: pipeerror|system:24 type=1|channel=22021-10-30T20:55:31.05991064 [b61e7920] {netio} kernel_service_interface:on_ready_timeout: restart! running=1, channel=0

查了下系統(tǒng)錯誤碼:

#define EMFILE      24  /* Too many open files */

這種錯誤一般是打開的句柄超過 linux 進(jìn)程的最大打開文件句柄數(shù) (一般是 1024),這個值對于服務(wù)器程序來說一般是不夠用的,需要通過系統(tǒng)設(shè)置來拉高上限。但對于 App 進(jìn)程是足夠了,怎么會超限呢?難道是出現(xiàn)了句柄泄漏。于是馬上去走查了連接關(guān)閉的代碼:

if channel=local then   close local_channel else if channel=tcp then   close tcp_channelelse  nothing        channel = none

這里使用了偽代碼來說明大意,其中 channel 標(biāo)記當(dāng)前使用的連接方式,初始時(shí)設(shè)置為 none,連接時(shí)兩種方式同時(shí)發(fā)送異步連接請求,先收到應(yīng)答的連接將設(shè)置對應(yīng)的 channel 值并關(guān)閉另一種連接通道,連接建立成功后 channel 必為兩種方式之一 (local | tcp)。

上面推演的是正常的場景,當(dāng) Work 進(jìn)程沒有啟動而導(dǎo)致兩個通道都無法完成連接時(shí),channel 將一直保持 none 值直到超時(shí),在連接重啟前,會嘗試使用上面這段代碼清理資源,此時(shí)就會命中最后的 else 邏輯——什么也不做——從而導(dǎo)致連接句柄被泄漏。以 10 秒重連、6 秒超時(shí)一次計(jì)算,每 16 秒就泄漏 2 個句柄,1024 個句柄泄漏光只需要不到 2 小時(shí)!

為了驗(yàn)證,專門修改了一版代碼,人為制造 Work 進(jìn)程不啟動的場景,果然跑了沒多久 App 進(jìn)程就崩潰重啟了。確定了問題根因,再回顧一下現(xiàn)象,之前那幾個疑問就能得到解釋了:

  • 問題表現(xiàn)為打開文件、創(chuàng)建線程均失敗的 oom 問題,實(shí)際是 oof (out of fd),句柄泄漏的表現(xiàn)和內(nèi)存泄漏有相似的地方
  • 問題存在于 kernel,當(dāng) kernel 耗光句柄后對應(yīng)的 App 進(jìn)程會因 EMFILE 錯誤崩潰,Work 進(jìn)程反而是沒什么事,所以表現(xiàn)為 App 進(jìn)程崩潰率多帶帶升高
  • 只影響一部分 Work 進(jìn)程長時(shí)間不啟動的用戶,這部分用戶占比較少,所以崩潰率升高有限
  • 之前小流量的那版也有問題,只是放量較少所以崩潰率升高不明顯而已

問題的修復(fù)非常簡單,就是在關(guān)閉清理資源時(shí),不再根據(jù) channel 判斷,直接 close 所有句柄。打好的修復(fù)版本在 Work 進(jìn)程不啟動的場景下運(yùn)行了一天也沒有出現(xiàn)崩潰,對外灰度后,觀察 App 崩潰率正常,逐步全量覆蓋線上版本,最后合入 master。

結(jié)語

復(fù)盤整個 oom 問題產(chǎn)生的過程,為何在灰度階段沒有發(fā)現(xiàn) App 進(jìn)程崩潰率異常升高呢?原來在看崩潰數(shù)據(jù)時(shí)是過濾了 sdk 版本號的,而實(shí)際發(fā)生異常升高的版本號卻是奇特的 0.0.0.1 版本,因而沒有觀察到。

為何 oom 問題會集中在 0.0.0.1 版本中?進(jìn)一步排查發(fā)現(xiàn)并非只有 oom 崩潰是這樣,90% 的崩潰都?xì)w類在了這個版本下面,原因竟然是 App 在初始化時(shí)沒有處理好先后關(guān)系,從 sdk 拿版本號時(shí) sdk 還未初始化,所以得到了一個無效的版本值。更嚴(yán)重的是,該問題幾乎一直存在,而我們之前過濾版本號的做法幾乎可以肯定是不正確的,想到這里不由得背上直冒冷汗!幸好有這次問題的復(fù)盤,不然這個問題要繼續(xù)存在多久還是個未知數(shù)~

最后總結(jié)一下 oom 問題的處理方法,首先不要心慌,特別是在不經(jīng)求證的情況下靠猜測來定位問題、靠不斷發(fā)小版本在線上驗(yàn)證問題,這樣做一來不嚴(yán)謹(jǐn),二來效率比較低,最終很可能還會定位不到問題。最好的辦法是通過現(xiàn)場日志來定位出錯的場景,可以極大的縮小排查范圍;另外 oom 與 oof 在 Java 崩潰堆棧中有相似的表現(xiàn),因此遇到這類問題可以多考慮下句柄泄漏的可能性,而不是一味觀察內(nèi)存的分配與釋放。

另外可能還有人對 Work 進(jìn)程為何沒有啟動感興趣,但這就屬于另外一個問題了,可以多帶帶寫篇文章了。目前仍在排查中,真的是應(yīng)了那句:生命不息,debug 不止~~

參考

[1]. Git 如何優(yōu)雅地回退代碼,用 reset 還是 revert?

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/125687.html

相關(guān)文章

  • 一次 Booking 線上面試中遇到的小問題

    從事 Android 開發(fā)工作要滿 5 年了,雖然明白自己技術(shù)很一般,但是也總是期望能夠有機(jī)會進(jìn)入更好的平臺發(fā)展。這不,因?yàn)闄C(jī)緣巧合有了一次 Booking 的面試邀請(是在 hackerrank 上),然后開始臨時(shí)抱佛腳 (leetcode 走起),最終選擇了一個周末去完成線上測試,結(jié)果我完全沒預(yù)料到。本以為會被某道題的邏輯繞昏,結(jié)果哪知道被標(biāo)準(zhǔn)輸入這個東西卡得死死的,現(xiàn)在就記錄一下這次非常糟...

    lykops 評論0 收藏0
  • 一次線上頻繁FGC的事件和解決方式

    摘要:直接顯示了一個疑似內(nèi)存泄漏的問題。然后分析文件給出的信息,發(fā)現(xiàn)一個叫的類。文件里面說的內(nèi)存泄漏的大概的意思就是說,這個類里面的存放的東西太多了,爆掉了。修改了代碼將調(diào)用的地方改成了單例。修改完線上跑了一段日子,后來也沒有出現(xiàn)過這樣的問題。 問題描述: ????早上去公司上班,突然就郵件一直報(bào)警,接口報(bào)異常,然后去查服務(wù)器的運(yùn)行情況,發(fā)現(xiàn)java的cpu爆了.接著就開始排查問題 問題解決...

    Alliot 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<