posted @ 2020-03-03 15:53 Jack Jiang 閱讀(681) | 評(píng)論 (0) | 編輯 收藏
posted @ 2020-02-26 22:44 Jack Jiang 閱讀(481) | 評(píng)論 (0) | 編輯 收藏
本文由馬蜂窩技術(shù)團(tuán)隊(duì)電商交易基礎(chǔ)平臺(tái)研發(fā)工程師"Anti Walker"原創(chuàng)分享。
一、引言
即時(shí)通訊(IM)功能對(duì)于電商平臺(tái)來(lái)說(shuō)非常重要,特別是旅游電商。
從商品復(fù)雜性來(lái)看,一個(gè)旅游商品可能會(huì)包括用戶(hù)在未來(lái)一段時(shí)間的衣、食、住、行等方方面面。從消費(fèi)金額來(lái)看,往往單次消費(fèi)額度較大。對(duì)目的地的陌生、在行程中可能的問(wèn)題,這些因素使用戶(hù)在購(gòu)買(mǎi)前、中、后都存在和商家溝通的強(qiáng)烈需求。可以說(shuō),一個(gè)好用的 IM 可以在一定程度上對(duì)企業(yè)電商業(yè)務(wù)的 GMV 起到促進(jìn)作用。
本文我們將結(jié)合馬蜂窩旅游電商IM系統(tǒng)的發(fā)展歷程,單獨(dú)介紹基于Go重構(gòu)分布式IM系統(tǒng)過(guò)程中的實(shí)踐和總結(jié)(本文相當(dāng)于《從游擊隊(duì)到正規(guī)軍(一):馬蜂窩旅游網(wǎng)的IM系統(tǒng)架構(gòu)演進(jìn)之路》一文的進(jìn)階篇),希望可以給有相似問(wèn)題的朋友一些借鑒。
另外:如果你對(duì)Go在高并發(fā)系統(tǒng)中的應(yīng)用感興趣,即時(shí)通訊網(wǎng)的以下兩篇也值得一讀:
《Go語(yǔ)言構(gòu)建千萬(wàn)級(jí)在線的高并發(fā)消息推送系統(tǒng)實(shí)踐(來(lái)自360公司)》
《12306搶票帶來(lái)的啟示:看我如何用Go實(shí)現(xiàn)百萬(wàn)QPS的秒殺系統(tǒng)(含源碼)》
系列文章:
《從游擊隊(duì)到正規(guī)軍(一):馬蜂窩旅游網(wǎng)的IM系統(tǒng)架構(gòu)演進(jìn)之路》
《從游擊隊(duì)到正規(guī)軍(二):馬蜂窩旅游網(wǎng)的IM客戶(hù)端架構(gòu)演進(jìn)和實(shí)踐總結(jié)》
《從游擊隊(duì)到正規(guī)軍(三):基于Go的馬蜂窩旅游網(wǎng)分布式IM系統(tǒng)技術(shù)實(shí)踐》(* 本文)
關(guān)于馬蜂窩旅游網(wǎng):

馬蜂窩旅游網(wǎng)是中國(guó)領(lǐng)先的自由行服務(wù)平臺(tái),由陳罡和呂剛創(chuàng)立于2006年,從2010年正式開(kāi)始公司化運(yùn)營(yíng)。馬蜂窩的景點(diǎn)、餐飲、酒店等點(diǎn)評(píng)信息均來(lái)自上億用戶(hù)的真實(shí)分享,每年幫助過(guò)億的旅行者制定自由行方案。
學(xué)習(xí)交流:
- 即時(shí)通訊/推送技術(shù)開(kāi)發(fā)交流5群:215477170 [推薦]
- 移動(dòng)端IM開(kāi)發(fā)入門(mén)文章:《新手入門(mén)一篇就夠:從零開(kāi)發(fā)移動(dòng)端IM》
(本文同步發(fā)布于:http://www.52im.net/thread-2909-1-1.html)
二、相關(guān)文章
《一套海量在線用戶(hù)的移動(dòng)端IM架構(gòu)設(shè)計(jì)實(shí)踐分享(含詳細(xì)圖文)》
《一套原創(chuàng)分布式即時(shí)通訊(IM)系統(tǒng)理論架構(gòu)方案》
《從零到卓越:京東客服即時(shí)通訊系統(tǒng)的技術(shù)架構(gòu)演進(jìn)歷程》
《蘑菇街即時(shí)通訊/IM服務(wù)器開(kāi)發(fā)之架構(gòu)選擇》
《以微博類(lèi)應(yīng)用場(chǎng)景為例,總結(jié)海量社交系統(tǒng)的架構(gòu)設(shè)計(jì)步驟》
《一套高可用、易伸縮、高并發(fā)的IM群聊、單聊架構(gòu)方案設(shè)計(jì)實(shí)踐》
《騰訊QQ1.4億在線用戶(hù)的技術(shù)挑戰(zhàn)和架構(gòu)演進(jìn)之路PPT》
《微信技術(shù)總監(jiān)談架構(gòu):微信之道——大道至簡(jiǎn)(演講全文)》
《如何解讀《微信技術(shù)總監(jiān)談架構(gòu):微信之道——大道至簡(jiǎn)》》
《快速裂變:見(jiàn)證微信強(qiáng)大后臺(tái)架構(gòu)從0到1的演進(jìn)歷程(一)》
《瓜子IM智能客服系統(tǒng)的數(shù)據(jù)架構(gòu)設(shè)計(jì)(整理自現(xiàn)場(chǎng)演講,有配套PPT)》
三、技術(shù)背景和問(wèn)題
與廣義上的即時(shí)通訊不同,電商各業(yè)務(wù)線有其特有業(yè)務(wù)邏輯,如客服聊天系統(tǒng)的客人分配邏輯、敏感詞檢測(cè)邏輯等,這些往往要耦合進(jìn)通信流程中。隨著接入業(yè)務(wù)線越來(lái)越多,即時(shí)通訊服務(wù)冗余度會(huì)越來(lái)越高。同時(shí)整個(gè)消息鏈路追溯復(fù)雜,服務(wù)穩(wěn)定性很受業(yè)務(wù)邏輯的影響。
之前我們 IM 應(yīng)用中的消息推送主要基于輪詢(xún)技術(shù),消息輪詢(xún)模塊的長(zhǎng)連接請(qǐng)求是通過(guò) php-fpm 掛載在阻塞隊(duì)列上實(shí)現(xiàn)。當(dāng)請(qǐng)求量較大時(shí),如果不能及時(shí)釋放 php-fpm 進(jìn)程,對(duì)服務(wù)器的性能消耗很大。
為了解決這個(gè)問(wèn)題,我們?cè)?OpenResty+Lua 的方式進(jìn)行改造,利用 Lua 協(xié)程的方式將整體的 polling 的能力從 PHP 轉(zhuǎn)交到 Lua 處理,釋放一部 PHP 的壓力。這種方式雖然能提升一部分性能,但 PHP-Lua 的混合異構(gòu)模式,使系統(tǒng)在使用、升級(jí)、調(diào)試和維護(hù)上都很麻煩,通用性也較差,很多業(yè)務(wù)場(chǎng)景下還是要依賴(lài) PHP 接口,優(yōu)化效果并不明顯。
為了解決以上問(wèn)題,我們決定結(jié)合電商 IM 的特定背景對(duì) IM 服務(wù)進(jìn)行重構(gòu),核心是實(shí)現(xiàn)業(yè)務(wù)邏輯和即時(shí)通訊服務(wù)的分離。
更多有關(guān)馬蜂窩旅游網(wǎng)的IM系統(tǒng)架構(gòu)的演進(jìn)過(guò)程,請(qǐng)?jiān)斪x:《從游擊隊(duì)到正規(guī)軍(一):馬蜂窩旅游網(wǎng)的IM系統(tǒng)架構(gòu)演進(jìn)之路》一文,在此不再贅述。
四、基于Go的雙層分布式IM架構(gòu)
4.1、實(shí)現(xiàn)目標(biāo)
1)業(yè)務(wù)解耦:
將業(yè)務(wù)邏輯與通信流程剝離,使 IM 服務(wù)架構(gòu)更加清晰,實(shí)現(xiàn)與電商 IM 業(yè)務(wù)邏輯的完全分離,保證服務(wù)穩(wěn)定性。
2)接入方式靈活:
之前新業(yè)務(wù)接入時(shí),需要在業(yè)務(wù)服務(wù)器上配置 OpenResty 環(huán)境及 Lua 協(xié)程代碼,非常不便,IM 服務(wù)的通用性也很差。考慮到現(xiàn)有業(yè)務(wù)的實(shí)際情況,我們希望 IM 系統(tǒng)可以提供 HTTP 和 WebSocket 兩種接入方式,供業(yè)務(wù)方根據(jù)不同的場(chǎng)景來(lái)靈活使用。
比如已經(jīng)接入且運(yùn)行良好的電商定制化團(tuán)隊(duì)的待辦系統(tǒng)、定制游搶單系統(tǒng)、投訴系統(tǒng)等下行相關(guān)的系統(tǒng)等,這些業(yè)務(wù)沒(méi)有明顯的高并發(fā)需求,可以通過(guò) HTTP 方式迅速接入,不需要熟悉稍顯復(fù)雜的 WebSocket 協(xié)議,進(jìn)而降低不必要的研發(fā)成本。
3)架構(gòu)可擴(kuò)展:
為了應(yīng)對(duì)業(yè)務(wù)的持續(xù)增長(zhǎng)給系統(tǒng)性能帶來(lái)的挑戰(zhàn),我們考慮用分布式架構(gòu)來(lái)設(shè)計(jì)即時(shí)通訊服務(wù),使系統(tǒng)具有持續(xù)擴(kuò)展及提升的能力。
4.2、語(yǔ)言選擇
目前,馬蜂窩技術(shù)體系主要包括 PHP,Java,Golang,技術(shù)棧比較豐富,使業(yè)務(wù)做選型時(shí)可以根據(jù)問(wèn)題場(chǎng)景選擇更合適的工具和語(yǔ)言。
結(jié)合 IM 具體應(yīng)用場(chǎng)景,我們選擇 Go 的原因包括:
- 1)運(yùn)行性能:在性能上,尤其是針對(duì)網(wǎng)絡(luò)通信等 IO 密集型應(yīng)用場(chǎng)景。Go 系統(tǒng)的性能更接近 C/C++;
- 2)開(kāi)發(fā)效率:Go 使用起來(lái)簡(jiǎn)單,代碼編寫(xiě)效率高,上手也很快,尤其是對(duì)于有一定 C++ 基礎(chǔ)的開(kāi)發(fā)者,一周就能上手寫(xiě)代碼了。
4.3、架構(gòu)設(shè)計(jì)
整體架構(gòu)圖如下:

名詞解釋?zhuān)?/strong>
- 1)客戶(hù):一般指購(gòu)買(mǎi)商品的用戶(hù);
- 2)商家:提供服務(wù)的供應(yīng)商,商家會(huì)有客服人員,提供給客戶(hù)一個(gè)在線咨詢(xún)的作用;
- 3)分發(fā)模塊:即 Dispatcher,提供消息分發(fā)的給指定的工作模塊的橋接作用;
- 4)工作模塊:即 Worker 服務(wù)器,用來(lái)提供 WebSocket 服務(wù),是真正工作的一個(gè)模塊。
架構(gòu)分層:
- 1)展示層:提供 HTTP 和 WebSocket 兩種接入方式;
- 2)業(yè)務(wù)層:負(fù)責(zé)初始化消息線和業(yè)務(wù)邏輯處理。如果客戶(hù)端以 HTTP 方式接入,會(huì)以 JSON 格式把消息發(fā)送給業(yè)務(wù)服務(wù)器進(jìn)行消息解碼、客服分配、敏感詞過(guò)濾,然后下發(fā)到消息分發(fā)模塊準(zhǔn)備下一步的轉(zhuǎn)換;通過(guò) WebSocket 接入的業(yè)務(wù)則不需要消息分發(fā),直接以 WebSocket 方式發(fā)送至消息處理模塊中;
- 3)服務(wù)層:由消息分發(fā)和消息處理這兩層組成,分別以分布式的方式部署多個(gè) Dispatcher 和 Worker 節(jié)點(diǎn)。Dispatcher 負(fù)責(zé)檢索出接收者所在的服務(wù)器位置,將消息以 RPC 的方式發(fā)送到合適的 Worker 上,再由消息處理模塊通過(guò) WebSocket 把消息推送給客戶(hù)端;
- 4)數(shù)據(jù)層:Redis 集群,記錄用戶(hù)身份、連接信息、客戶(hù)端平臺(tái)(移動(dòng)端、網(wǎng)頁(yè)端、桌面端)等組成的唯一 Key。
4.4、服務(wù)流程
步驟一:
如上圖右側(cè)所示:
用戶(hù)客戶(hù)端與消息處理模塊建立 WebSocket 長(zhǎng)連接;
通過(guò)負(fù)載均衡算法,使客戶(hù)端連接到合適的服務(wù)器(消息處理模塊的某個(gè) Worker);
連接成功后,記錄用戶(hù)連接信息,包括用戶(hù)角色(客人或商家)、客戶(hù)端平臺(tái)(移動(dòng)端、網(wǎng)頁(yè)端、桌面端)等組成唯一 Key,記錄到 Redis 集群。
步驟二:
如圖左側(cè)所示,當(dāng)購(gòu)買(mǎi)商品的用戶(hù)要給管家發(fā)消息的時(shí)候,先通過(guò) HTTP 請(qǐng)求把消息發(fā)給業(yè)務(wù)服務(wù)器,業(yè)務(wù)服務(wù)端對(duì)消息進(jìn)行業(yè)務(wù)邏輯處理。
1)該步驟本身是一個(gè) HTTP 請(qǐng)求,所以可以接入各種不同開(kāi)發(fā)語(yǔ)言的客戶(hù)端。通過(guò) JSON 格式把消息發(fā)送給業(yè)務(wù)服務(wù)器,業(yè)務(wù)服務(wù)器先把消息解碼,然后拿到這個(gè)用戶(hù)要發(fā)送給哪個(gè)商家的客服的。
2)如果這個(gè)購(gòu)買(mǎi)者之前沒(méi)有聊過(guò)天,則在業(yè)務(wù)服務(wù)器邏輯里需要有一個(gè)分配客服的過(guò)程,即建立購(gòu)買(mǎi)者和商家的客服之間的連接關(guān)系。拿到這個(gè)客服的 ID,用來(lái)做業(yè)務(wù)消息下發(fā);如果之前已經(jīng)聊過(guò)天,則略過(guò)此環(huán)節(jié)。
3)在業(yè)務(wù)服務(wù)器,消息會(huì)異步入數(shù)據(jù)庫(kù)。保證消息不會(huì)丟失。
步驟三:
業(yè)務(wù)服務(wù)端以 HTTP 請(qǐng)求把消息發(fā)送到消息分發(fā)模塊。這里分發(fā)模塊的作用是進(jìn)行中轉(zhuǎn),最終使服務(wù)端的消息下發(fā)給指定的商家。
步驟四:
基于 Redis 集群中的用戶(hù)連接信息,消息分發(fā)模塊將消息轉(zhuǎn)發(fā)到目標(biāo)用戶(hù)連接的 WebSocket 服務(wù)器(消息處理模塊中的某一個(gè) Worker)
1)分發(fā)模塊通過(guò) RPC 方式把消息轉(zhuǎn)發(fā)到目標(biāo)用戶(hù)連接的 Worker,RPC 的方式性能更快,而且傳輸?shù)臄?shù)據(jù)也少,從而節(jié)約了服務(wù)器的成本。
2)消息透?jìng)?Worker 的時(shí)候,多種策略保障消息一定會(huì)下發(fā)到 Worker。
步驟五:
消息處理模塊將消息通過(guò) WebSocket 協(xié)議推送到客戶(hù)端。
1)在投遞的時(shí)候,接收者要有一個(gè) ACK(應(yīng)答) 信息來(lái)回饋給 Worker 服務(wù)器,告訴 Worker 服務(wù)器,下發(fā)的消息接收者已經(jīng)收到了。
2)如果接收者沒(méi)有發(fā)送這個(gè) ACK 來(lái)告訴 Worker 服務(wù)器,Worker 服務(wù)器會(huì)在一定的時(shí)間內(nèi)來(lái)重新把這個(gè)信息發(fā)送給消息接收者。
3)如果投遞的信息已經(jīng)發(fā)送給客戶(hù)端,客戶(hù)端也收到了,但是因?yàn)榫W(wǎng)絡(luò)抖動(dòng),沒(méi)有把 ACK 信息發(fā)送給服務(wù)器,那服務(wù)器會(huì)重復(fù)投遞給客戶(hù)端,這時(shí)候客戶(hù)端就通過(guò)投遞過(guò)來(lái)的消息 ID 來(lái)去重展示。
以上步驟的數(shù)據(jù)流轉(zhuǎn)大致如圖所示:

4.5、系統(tǒng)完整性設(shè)計(jì)
4.5.1 可靠性
(1)消息不丟失:
為了避免消息丟失,我們?cè)O(shè)置了超時(shí)重傳機(jī)制。服務(wù)端會(huì)在推送給客戶(hù)端消息后,等待客戶(hù)端的 ACK,如果客戶(hù)端沒(méi)有返回 ACK,服務(wù)端會(huì)嘗試多次推送。
目前默認(rèn) 18s 為超時(shí)時(shí)間,重傳 3 次不成功,斷開(kāi)連接,重新連接服務(wù)器。重新連接后,采用拉取歷史消息的機(jī)制來(lái)保證消息完整。
(2)多端消息同步:
客戶(hù)端現(xiàn)有 PC 瀏覽器、Windows 客戶(hù)端、H5、iOS/Android,系統(tǒng)允許用戶(hù)多端同時(shí)在線,且同一端可以多個(gè)狀態(tài),這就需要保證多端、多用戶(hù)、多狀態(tài)的消息是同步的。
我們用到了 Redis 的 Hash 存儲(chǔ),將用戶(hù)信息、唯一連接對(duì)應(yīng)值 、連接標(biāo)識(shí)、客戶(hù)端 IP、服務(wù)器標(biāo)識(shí)、角色、渠道等記錄下來(lái),這樣通過(guò) key(uid) 就能找到一個(gè)用戶(hù)在多個(gè)端的連接,通過(guò) key+field 能定位到一條連接。
4.5.2 可用性
上文我們已經(jīng)說(shuō)過(guò),因?yàn)槭请p層設(shè)計(jì),就涉及到兩個(gè) Server 間的通信,同進(jìn)程內(nèi)通信用 Channel,非同進(jìn)程用消息隊(duì)列或者 RPC。綜合性能和對(duì)服務(wù)器資源利用,我們最終選擇 RPC 的方式進(jìn)行 Server 間通信。
在對(duì)基于 Go 的 RPC 進(jìn)行選行時(shí),我們比較了以下比較主流的技術(shù)方案:
1)Go STDRPC:Go 標(biāo)準(zhǔn)庫(kù)的 RPC,性能最優(yōu),但是沒(méi)有治理;
2)RPCX:性能優(yōu)勢(shì) 2*GRPC + 服務(wù)治理;
3)GRPC:跨語(yǔ)言,但性能沒(méi)有 RPCX 好;
4)TarsGo:跨語(yǔ)言,性能 5*GRPC,缺點(diǎn)是框架較大,整合起來(lái)費(fèi)勁;
5)Dubbo-Go:性能稍遜一籌, 比較適合 Go 和 Java 間通信場(chǎng)景使用。
最后我們選擇了 RPCX,因?yàn)樾阅芤埠芎茫灿蟹?wù)的治理。
兩個(gè)進(jìn)程之間同樣需要通信,這里用到的是 ETCD 實(shí)現(xiàn)服務(wù)注冊(cè)發(fā)現(xiàn)機(jī)制。
當(dāng)我們新增一個(gè) Worker,如果沒(méi)有注冊(cè)中心,就要用到配置文件來(lái)管理這些配置信息,這挺麻煩的。而且你新增一個(gè)后,需要分發(fā)模塊立刻發(fā)現(xiàn),不能有延遲。
如果有新的服務(wù),分發(fā)模塊希望能快速感知到新的服務(wù)。利用 Key 的續(xù)租機(jī)制,如果在一定時(shí)間內(nèi),沒(méi)有監(jiān)聽(tīng)到 Key 有續(xù)租動(dòng)作,則認(rèn)為這個(gè)服務(wù)已經(jīng)掛掉,就會(huì)把該服務(wù)摘除。
在進(jìn)行注冊(cè)中心的選型時(shí),我們主要調(diào)研了 ETCD、ZooKeeper、Consul。
三者的壓測(cè)結(jié)果參考如下:


結(jié)果顯示,ETCD 的性能是最好的。另外,ETCD 背靠阿里巴巴,而且屬于 Go 生態(tài),我們公司內(nèi)部的 K8S 集群也在使用。
綜合考量后,我們選擇使用 ETCD 作為服務(wù)注冊(cè)和發(fā)現(xiàn)組件。并且我們使用的是 ETCD 的集群模式,如果一臺(tái)服務(wù)器出現(xiàn)故障,集群其他的服務(wù)器仍能正常提供服務(wù)。
小結(jié)一下:通過(guò)保證服務(wù)和進(jìn)程間的正常通訊,及 ETCD 集群模式的設(shè)計(jì),保證了 IM 服務(wù)整體具有極高的可用性。
4.5.3 擴(kuò)展性
消息分發(fā)模塊和消息處理模塊都能進(jìn)行水平擴(kuò)展。當(dāng)整體服務(wù)負(fù)載高時(shí),可以通過(guò)增加節(jié)點(diǎn)來(lái)分擔(dān)壓力,保證消息即時(shí)性和服務(wù)穩(wěn)定性。
4.5.4 安全性
處于安全性考慮,我們?cè)O(shè)置了黑名單機(jī)制,可以對(duì)單一 uid 或者 ip 進(jìn)行限制。比如在同一個(gè) uid 下,如果一段時(shí)間內(nèi)建立的連接次數(shù)超過(guò)設(shè)定的閾值,則認(rèn)為這個(gè) uid 可能存在風(fēng)險(xiǎn),暫停服務(wù)。如果暫停服務(wù)期間該 uid 繼續(xù)發(fā)送請(qǐng)求,則限制服務(wù)的時(shí)間相應(yīng)延長(zhǎng)。
4.6、性能優(yōu)化和踩過(guò)的坑
4.6.1 性能優(yōu)化
1)JSON 編解碼:
開(kāi)始我們使用官方的 JSON 編解碼工具,但由于對(duì)性能方面的追求,改為使用滴滴開(kāi)源的 Json-iterator,使在兼容原生 Golang 的 JSON 編解碼工具的同時(shí),效率上有比較明顯的提升。
以下是壓測(cè)對(duì)比的參考圖:

2)time.After:
在壓測(cè)的時(shí)候,我們發(fā)現(xiàn)內(nèi)存占用很高,于是使用 Go Tool PProf 分析 Golang 函數(shù)內(nèi)存申請(qǐng)情況,發(fā)現(xiàn)有不斷創(chuàng)建 time.After 定時(shí)器的問(wèn)題,定位到是心跳協(xié)程里面。
原來(lái)代碼如下:

優(yōu)化后的代碼為:

優(yōu)化點(diǎn)在于 for 循環(huán)里不要使用 select + time.After 的組合。
3)Map 的使用:
在保存連接信息的時(shí)候會(huì)用到 Map。因?yàn)橹白?TCP Socket 的項(xiàng)目的時(shí)候就遇到過(guò)一個(gè)坑,即 Map 在協(xié)程下是不安全的。當(dāng)多個(gè)協(xié)程同時(shí)對(duì)一個(gè) Map 進(jìn)行讀寫(xiě)時(shí),會(huì)拋出致命錯(cuò)誤:fetal error:concurrent map read and map write,有了這個(gè)經(jīng)驗(yàn)后,我們這里用的是 sync.Map
4.6.2 踩坑經(jīng)驗(yàn)
1)協(xié)程異常:
基于對(duì)開(kāi)發(fā)成本和服務(wù)穩(wěn)定性等問(wèn)題的考慮,我們的 WebSocket 服務(wù)基于 Gorilla/WebSocket 框架開(kāi)發(fā)。其中遇到一個(gè)問(wèn)題,就是當(dāng)讀協(xié)程發(fā)生異常退出時(shí),寫(xiě)協(xié)程并沒(méi)有感知到,結(jié)果就是導(dǎo)致讀協(xié)程已經(jīng)退出但是寫(xiě)協(xié)程還在運(yùn)行,直到觸發(fā)異常之后才退出。
這樣雖然從表面上看不影響業(yè)務(wù)邏輯,但是浪費(fèi)后端資源。在編碼時(shí)應(yīng)該注意要在讀協(xié)程退出后主動(dòng)通知寫(xiě)協(xié)程,這樣一個(gè)小的優(yōu)化可以這在高并發(fā)下能節(jié)省很多資源。
2)心跳設(shè)計(jì):
舉個(gè)例子:之前我們?cè)陂e時(shí)心跳功能的開(kāi)發(fā)中走了一些彎路。最初在服務(wù)器端的心跳發(fā)送是定時(shí)心跳,但后來(lái)在實(shí)際業(yè)務(wù)場(chǎng)景中使用時(shí)發(fā)現(xiàn),設(shè)計(jì)成服務(wù)器讀空閑時(shí)心跳更好。因?yàn)橛脩?hù)都在聊天呢,發(fā)一個(gè)心跳幀,浪費(fèi)感情也浪費(fèi)帶寬資源。
這時(shí)候,建議大家在業(yè)務(wù)開(kāi)發(fā)過(guò)程中如果代碼寫(xiě)不下去就暫時(shí)不要寫(xiě)了,先結(jié)合業(yè)務(wù)需求用文字梳理下邏輯,可能會(huì)發(fā)現(xiàn)之后再進(jìn)行會(huì)更順利。
3)每天分割日志:

日志模塊在起初調(diào)研的時(shí)候基于性能考慮,確定使用 Uber 開(kāi)源的 ZAP 庫(kù),而且滿(mǎn)足業(yè)務(wù)日志記錄的要求。日志庫(kù)選型很重要,選不好也是影響系統(tǒng)性能和穩(wěn)定性的。
ZAP 的優(yōu)點(diǎn)包括:
1)顯示代碼行號(hào)這個(gè)需求,ZAP 支持而 Logrus 不支持,這個(gè)屬于提效的。行號(hào)展示對(duì)于定位問(wèn)題很重要;
2)ZAP 相對(duì)于 Logrus 更為高效,體現(xiàn)在寫(xiě) JSON 格式日志時(shí),沒(méi)有使用反射,而是用內(nèi)建的 json encoder,通過(guò)明確的類(lèi)型調(diào)用,直接拼接字符串,最小化性能開(kāi)銷(xiāo)。
小坑:每天寫(xiě)一個(gè)日志文件的功能,目前 ZAP 不支持,需要自己寫(xiě)代碼支持,或者請(qǐng)求系統(tǒng)部支持。
五、性能表現(xiàn)
壓測(cè) 1:
上線生產(chǎn)環(huán)境并和業(yè)務(wù)方對(duì)接以及壓測(cè),目前定制業(yè)務(wù)已接通整個(gè)流程,寫(xiě)了一個(gè) Client。模擬定期發(fā)心跳幀,然后利用 Docker 環(huán)境。開(kāi)啟了 50 個(gè)容器,每個(gè)容器模擬并發(fā)起 2 萬(wàn)個(gè)連接。這樣就是百萬(wàn)連接打到單機(jī)的 Server 上。單機(jī)內(nèi)存占用 30G 左右。
壓測(cè) 2:
同時(shí)并發(fā) 3000、4000、5000 連接,以及調(diào)整發(fā)送頻率,分別對(duì)應(yīng)上行:60萬(wàn)、80 萬(wàn)、100 萬(wàn)、200 萬(wàn), 一個(gè) 6k 左右的日志結(jié)構(gòu)體。
其中有一半是心跳包 另一半是日志結(jié)構(gòu)體。在不同的壓力下的下行延遲數(shù)據(jù)如下:

結(jié)論:
隨著上行的并發(fā)變大,延遲控制在 24-66 毫秒之間。所以對(duì)于下行業(yè)務(wù)屬于輕微延遲。另外針對(duì) 60 萬(wàn) 5k 上行的同時(shí),用另一個(gè)腳本模擬開(kāi)啟 50 個(gè)協(xié)程并發(fā)下行 1k 的數(shù)據(jù)體,延遲是比沒(méi)有并發(fā)下行的時(shí)候是有所提高的,延遲提高了 40ms 左右。
六、本文小結(jié)
基于 Go 重構(gòu)的 IM 服務(wù)在 WebSocket 的基礎(chǔ)上,將業(yè)務(wù)層設(shè)計(jì)為配有消息分發(fā)模塊和消息處理模塊的雙層架構(gòu)模式,使業(yè)務(wù)邏輯的處理前置,保證了即時(shí)通訊服務(wù)的純粹性和穩(wěn)定性;同時(shí)消息分發(fā)模塊的 HTTP 服務(wù)方便多種編程語(yǔ)言快速對(duì)接,使各業(yè)務(wù)線能迅速接入即時(shí)通訊服務(wù)。
最后,我還想為 Go 搖旗吶喊一下。很多人都知道馬蜂窩技術(shù)體系主要是基于 PHP,有一些核心業(yè)務(wù)也在向 Java 遷移。與此同時(shí),Go 也在越來(lái)越多的項(xiàng)目中發(fā)揮作用。現(xiàn)在,云原生理念已經(jīng)逐漸成為主流趨勢(shì)之一,我們可以看到在很多構(gòu)建云原生應(yīng)用所需要的核心項(xiàng)目中,Go 都是主要的開(kāi)發(fā)語(yǔ)言,比如 Kubernetes,Docker,Istio,ETCD,Prometheus 等,包括第三代開(kāi)源分布式數(shù)據(jù)庫(kù) TiDB。
所以我們可以把 Go 稱(chēng)為云原生時(shí)代的母語(yǔ)。「云原生時(shí)代,是開(kāi)發(fā)者最好的時(shí)代」,在這股浪潮下,我們?cè)皆缱哌M(jìn) Go,就可能越早在這個(gè)新時(shí)代搶占關(guān)鍵賽道。希望更多小伙伴和我們一起,加入到 Go 的開(kāi)發(fā)和學(xué)習(xí)陣營(yíng)中來(lái),拓寬自己的技能圖譜,擁抱云原生。
附錄:更多IM架構(gòu)設(shè)計(jì)方面的文章
[1] 有關(guān)IM架構(gòu)設(shè)計(jì)的文章:
《淺談IM系統(tǒng)的架構(gòu)設(shè)計(jì)》
《簡(jiǎn)述移動(dòng)端IM開(kāi)發(fā)的那些坑:架構(gòu)設(shè)計(jì)、通信協(xié)議和客戶(hù)端》
《一套海量在線用戶(hù)的移動(dòng)端IM架構(gòu)設(shè)計(jì)實(shí)踐分享(含詳細(xì)圖文)》
《一套原創(chuàng)分布式即時(shí)通訊(IM)系統(tǒng)理論架構(gòu)方案》
《從零到卓越:京東客服即時(shí)通訊系統(tǒng)的技術(shù)架構(gòu)演進(jìn)歷程》
《蘑菇街即時(shí)通訊/IM服務(wù)器開(kāi)發(fā)之架構(gòu)選擇》
《騰訊QQ1.4億在線用戶(hù)的技術(shù)挑戰(zhàn)和架構(gòu)演進(jìn)之路PPT》
《微信后臺(tái)基于時(shí)間序的海量數(shù)據(jù)冷熱分級(jí)架構(gòu)設(shè)計(jì)實(shí)踐》
《微信技術(shù)總監(jiān)談架構(gòu):微信之道——大道至簡(jiǎn)(演講全文)》
《如何解讀《微信技術(shù)總監(jiān)談架構(gòu):微信之道——大道至簡(jiǎn)》》
《快速裂變:見(jiàn)證微信強(qiáng)大后臺(tái)架構(gòu)從0到1的演進(jìn)歷程(一)》
《17年的實(shí)踐:騰訊海量產(chǎn)品的技術(shù)方法論》
《移動(dòng)端IM中大規(guī)模群消息的推送如何保證效率、實(shí)時(shí)性?》
《現(xiàn)代IM系統(tǒng)中聊天消息的同步和存儲(chǔ)方案探討》
《IM開(kāi)發(fā)基礎(chǔ)知識(shí)補(bǔ)課(二):如何設(shè)計(jì)大量圖片文件的服務(wù)端存儲(chǔ)架構(gòu)?》
《IM開(kāi)發(fā)基礎(chǔ)知識(shí)補(bǔ)課(三):快速理解服務(wù)端數(shù)據(jù)庫(kù)讀寫(xiě)分離原理及實(shí)踐建議》
《IM開(kāi)發(fā)基礎(chǔ)知識(shí)補(bǔ)課(四):正確理解HTTP短連接中的Cookie、Session和Token》
《WhatsApp技術(shù)實(shí)踐分享:32人工程團(tuán)隊(duì)創(chuàng)造的技術(shù)神話》
《微信朋友圈千億訪問(wèn)量背后的技術(shù)挑戰(zhàn)和實(shí)踐總結(jié)》
《王者榮耀2億用戶(hù)量的背后:產(chǎn)品定位、技術(shù)架構(gòu)、網(wǎng)絡(luò)方案等》
《IM系統(tǒng)的MQ消息中間件選型:Kafka還是RabbitMQ?》
《騰訊資深架構(gòu)師干貨總結(jié):一文讀懂大型分布式系統(tǒng)設(shè)計(jì)的方方面面》
《以微博類(lèi)應(yīng)用場(chǎng)景為例,總結(jié)海量社交系統(tǒng)的架構(gòu)設(shè)計(jì)步驟》
《快速理解高性能HTTP服務(wù)端的負(fù)載均衡技術(shù)原理》
《子彈短信光鮮的背后:網(wǎng)易云信首席架構(gòu)師分享億級(jí)IM平臺(tái)的技術(shù)實(shí)踐》
《知乎技術(shù)分享:從單機(jī)到2000萬(wàn)QPS并發(fā)的Redis高性能緩存實(shí)踐之路》
《IM開(kāi)發(fā)基礎(chǔ)知識(shí)補(bǔ)課(五):通俗易懂,正確理解并用好MQ消息隊(duì)列》
《微信技術(shù)分享:微信的海量IM聊天消息序列號(hào)生成實(shí)踐(算法原理篇)》
《微信技術(shù)分享:微信的海量IM聊天消息序列號(hào)生成實(shí)踐(容災(zāi)方案篇)》
《新手入門(mén):零基礎(chǔ)理解大型分布式架構(gòu)的演進(jìn)歷史、技術(shù)原理、最佳實(shí)踐》
《一套高可用、易伸縮、高并發(fā)的IM群聊、單聊架構(gòu)方案設(shè)計(jì)實(shí)踐》
《阿里技術(shù)分享:深度揭秘阿里數(shù)據(jù)庫(kù)技術(shù)方案的10年變遷史》
《阿里技術(shù)分享:阿里自研金融級(jí)數(shù)據(jù)庫(kù)OceanBase的艱辛成長(zhǎng)之路》
《社交軟件紅包技術(shù)解密(一):全面解密QQ紅包技術(shù)方案——架構(gòu)、技術(shù)實(shí)現(xiàn)等》
《社交軟件紅包技術(shù)解密(二):解密微信搖一搖紅包從0到1的技術(shù)演進(jìn)》
《社交軟件紅包技術(shù)解密(三):微信搖一搖紅包雨背后的技術(shù)細(xì)節(jié)》
《社交軟件紅包技術(shù)解密(四):微信紅包系統(tǒng)是如何應(yīng)對(duì)高并發(fā)的》
《社交軟件紅包技術(shù)解密(五):微信紅包系統(tǒng)是如何實(shí)現(xiàn)高可用性的》
《社交軟件紅包技術(shù)解密(六):微信紅包系統(tǒng)的存儲(chǔ)層架構(gòu)演進(jìn)實(shí)踐》
《社交軟件紅包技術(shù)解密(七):支付寶紅包的海量高并發(fā)技術(shù)實(shí)踐》
《社交軟件紅包技術(shù)解密(八):全面解密微博紅包技術(shù)方案》
《社交軟件紅包技術(shù)解密(九):談?wù)勈諵紅包的功能邏輯、容災(zāi)、運(yùn)維、架構(gòu)等》
《即時(shí)通訊新手入門(mén):一文讀懂什么是Nginx?它能否實(shí)現(xiàn)IM的負(fù)載均衡?》
《即時(shí)通訊新手入門(mén):快速理解RPC技術(shù)——基本概念、原理和用途》
《多維度對(duì)比5款主流分布式MQ消息隊(duì)列,媽媽再也不擔(dān)心我的技術(shù)選型了》
《從游擊隊(duì)到正規(guī)軍(一):馬蜂窩旅游網(wǎng)的IM系統(tǒng)架構(gòu)演進(jìn)之路》
《從游擊隊(duì)到正規(guī)軍(二):馬蜂窩旅游網(wǎng)的IM客戶(hù)端架構(gòu)演進(jìn)和實(shí)踐總結(jié)》
《IM開(kāi)發(fā)基礎(chǔ)知識(shí)補(bǔ)課(六):數(shù)據(jù)庫(kù)用NoSQL還是SQL?讀這篇就夠了!》
《瓜子IM智能客服系統(tǒng)的數(shù)據(jù)架構(gòu)設(shè)計(jì)(整理自現(xiàn)場(chǎng)演講,有配套PPT)》
《阿里釘釘技術(shù)分享:企業(yè)級(jí)IM王者——釘釘在后端架構(gòu)上的過(guò)人之處》
[2] 更多其它架構(gòu)設(shè)計(jì)相關(guān)文章:
《騰訊資深架構(gòu)師干貨總結(jié):一文讀懂大型分布式系統(tǒng)設(shè)計(jì)的方方面面》
《快速理解高性能HTTP服務(wù)端的負(fù)載均衡技術(shù)原理》
《子彈短信光鮮的背后:網(wǎng)易云信首席架構(gòu)師分享億級(jí)IM平臺(tái)的技術(shù)實(shí)踐》
《知乎技術(shù)分享:從單機(jī)到2000萬(wàn)QPS并發(fā)的Redis高性能緩存實(shí)踐之路》
《新手入門(mén):零基礎(chǔ)理解大型分布式架構(gòu)的演進(jìn)歷史、技術(shù)原理、最佳實(shí)踐》
《阿里技術(shù)分享:深度揭秘阿里數(shù)據(jù)庫(kù)技術(shù)方案的10年變遷史》
《阿里技術(shù)分享:阿里自研金融級(jí)數(shù)據(jù)庫(kù)OceanBase的艱辛成長(zhǎng)之路》
《達(dá)達(dá)O2O后臺(tái)架構(gòu)演進(jìn)實(shí)踐:從0到4000高并發(fā)請(qǐng)求背后的努力》
《優(yōu)秀后端架構(gòu)師必會(huì)知識(shí):史上最全MySQL大表優(yōu)化方案總結(jié)》
《小米技術(shù)分享:解密小米搶購(gòu)系統(tǒng)千萬(wàn)高并發(fā)架構(gòu)的演進(jìn)和實(shí)踐》
《一篇讀懂分布式架構(gòu)下的負(fù)載均衡技術(shù):分類(lèi)、原理、算法、常見(jiàn)方案等》
《通俗易懂:如何設(shè)計(jì)能支撐百萬(wàn)并發(fā)的數(shù)據(jù)庫(kù)架構(gòu)?》
《多維度對(duì)比5款主流分布式MQ消息隊(duì)列,媽媽再也不擔(dān)心我的技術(shù)選型了》
《從新手到架構(gòu)師,一篇就夠:從100到1000萬(wàn)高并發(fā)的架構(gòu)演進(jìn)之路》
《美團(tuán)技術(shù)分享:深度解密美團(tuán)的分布式ID生成算法》
《12306搶票帶來(lái)的啟示:看我如何用Go實(shí)現(xiàn)百萬(wàn)QPS的秒殺系統(tǒng)(含源碼)》
(本文同步發(fā)布于:http://www.52im.net/thread-2909-1-1.html)
posted @ 2020-02-19 16:47 Jack Jiang 閱讀(284) | 評(píng)論 (0) | 編輯 收藏
posted @ 2020-01-14 14:33 Jack Jiang 閱讀(250) | 評(píng)論 (0) | 編輯 收藏
posted @ 2020-01-08 13:39 Jack Jiang 閱讀(128) | 評(píng)論 (0) | 編輯 收藏
posted @ 2020-01-02 20:54 Jack Jiang 閱讀(210) | 評(píng)論 (0) | 編輯 收藏
1、引言
對(duì)于移動(dòng)端IM應(yīng)用和消息推送應(yīng)用的開(kāi)發(fā)者來(lái)說(shuō),Android后臺(tái)保活這件事是再熟悉不過(guò)了。
自從Android P(即Android 8.0)出現(xiàn)以后,Android已經(jīng)從系統(tǒng)層面將后臺(tái)保活這條路給堵死了(詳見(jiàn):《Android P正式版即將到來(lái):后臺(tái)應(yīng)用保活、消息推送的真正噩夢(mèng)》),曾今那些層出不窮的保活黑科技能用的也越來(lái)越少了(詳見(jiàn):《全面盤(pán)點(diǎn)當(dāng)前Android后臺(tái)保活方案的真實(shí)運(yùn)行效果(截止2019年前)》。雖然可以自已對(duì)接廠商的ROOM級(jí)推送通道,但一方面各廠商的推送接口都不一樣(而且同一廠商不同的系統(tǒng)版本間也存在推送接口的兼容性問(wèn)題),很不方便。另一方面要一家家引入各自的推送服務(wù)SDK包會(huì)讓APP變的很大,這讓APP的下載變的很不友好。
總之,Android應(yīng)用的后臺(tái)保活在某些場(chǎng)景下,還是有持續(xù)的需求。除了之前那些耳熟能詳?shù)谋;詈诳萍家酝猓贏ndroid 9.0(甚至Android 10)時(shí)代,我們還有哪些保活方法可以用?那么,請(qǐng)跟著本文作者的思路,看看更優(yōu)雅的后臺(tái)保活實(shí)現(xiàn)方法吧。
(本文同步發(fā)布于:http://www.52im.net/thread-2881-1-1.html)

2、關(guān)于作者
網(wǎng)名NanBox:畢業(yè)于華中科技大學(xué),現(xiàn)為"悅跑圈APP”高級(jí)Android開(kāi)發(fā)工程師。主要負(fù)責(zé)公司 Android 項(xiàng)目,核心模塊的開(kāi)發(fā)。涉及 GPS 定位、地圖、圖片編輯等功能。獨(dú)立開(kāi)發(fā)了手表應(yīng)用項(xiàng)目。 在項(xiàng)目中應(yīng)入了 Flutter 跨平臺(tái)開(kāi)發(fā)技術(shù),實(shí)現(xiàn)了原生和 Flutter 的混合開(kāi)發(fā)。
本文作者樂(lè)于分享,平時(shí)會(huì)寫(xiě)技術(shù)文章并分享在多個(gè)平臺(tái),是掘金專(zhuān)欄作者的一員,文章總閱讀量超過(guò) 10 萬(wàn)。在 GitHub 上有多個(gè)開(kāi)源項(xiàng)目,多次在團(tuán)隊(duì)內(nèi)部進(jìn)行技術(shù)分享。是 Android 和 Flutter 官方中文文檔譯者。
3、相關(guān)文章
如果你想詳細(xì)了解目前Android平臺(tái)上后臺(tái)保活技術(shù)的挑戰(zhàn),請(qǐng)閱讀:
如果你想回顧那些曾今出現(xiàn)的Android保活黑科技,以下文章值得好好讀讀:
《全面盤(pán)點(diǎn)當(dāng)前Android后臺(tái)保活方案的真實(shí)運(yùn)行效果(截止2019年前)》
《應(yīng)用保活終極總結(jié)(一):Android6.0以下的雙進(jìn)程守護(hù)保活實(shí)踐》
《應(yīng)用保活終極總結(jié)(二):Android6.0及以上的保活實(shí)踐(進(jìn)程防殺篇)》
《應(yīng)用保活終極總結(jié)(三):Android6.0及以上的保活實(shí)踐(被殺復(fù)活篇)》
《Android進(jìn)程保活詳解:一篇文章解決你的所有疑問(wèn)》
《Android端消息推送總結(jié):實(shí)現(xiàn)原理、心跳保活、遇到的問(wèn)題等》
《為何基于TCP協(xié)議的移動(dòng)端IM仍然需要心跳保活機(jī)制?》
《微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺(tái)保活實(shí)戰(zhàn)分享(進(jìn)程保活篇)》
《融云技術(shù)分享:融云安卓端IM產(chǎn)品的網(wǎng)絡(luò)鏈路保活技術(shù)實(shí)踐》
4、Android保活現(xiàn)狀
我們知道,Android 系統(tǒng)會(huì)存在殺后臺(tái)進(jìn)程的情況,并且隨著系統(tǒng)版本的更新,殺進(jìn)程的力度還有越來(lái)越大的趨勢(shì)(見(jiàn):《Android P正式版即將到來(lái):后臺(tái)應(yīng)用保活、消息推送的真正噩夢(mèng)》)。
系統(tǒng)這種做法本身出發(fā)點(diǎn)是好的,因?yàn)榭梢怨?jié)省內(nèi)存,降低功耗,也避免了一些流氓行為。
但有一部分應(yīng)用,應(yīng)用本身的使用場(chǎng)景就需要在后臺(tái)運(yùn)行,用戶(hù)也是愿意讓它在后臺(tái)運(yùn)行的,比如跑步類(lèi)應(yīng)用、一些懶得對(duì)接廠商推送通道的IM應(yīng)用、消息推送資訊類(lèi)應(yīng)用等。
一方面流氓軟件用各種流氓手段進(jìn)行保活,另一方面系統(tǒng)加大殺后臺(tái)的力度,導(dǎo)致我們一些真正需要在后臺(tái)運(yùn)行的應(yīng)用被誤殺,苦不堪言。
5、優(yōu)雅的保活?
為了做到保活,出現(xiàn)了不少「黑科技」,比如 1 個(gè)像素的 Activity,播放無(wú)聲音頻,雙進(jìn)程互相守護(hù)等(可以讀讀這個(gè)系列:《應(yīng)用保活終極總結(jié)(一):Android6.0以下的雙進(jìn)程守護(hù)保活實(shí)踐》、《應(yīng)用保活終極總結(jié)(二):Android6.0及以上的保活實(shí)踐(進(jìn)程防殺篇)》、《應(yīng)用保活終極總結(jié)(三):Android6.0及以上的保活實(shí)踐(被殺復(fù)活篇)》)。
這些做法可以說(shuō)是很流氓了,甚至破壞了 Android 的生態(tài),好在隨著 Android 系統(tǒng)版本的更新,這些非常規(guī)的保活手段很多都已失效了。
對(duì)于那些確實(shí)需要在后臺(tái)運(yùn)行的應(yīng)用,我們?nèi)绾巫龅絻?yōu)雅的保活呢?
6、加入后臺(tái)運(yùn)行白名單,可以?xún)?yōu)雅的實(shí)現(xiàn)保活
從 Android 6.0 開(kāi)始,系統(tǒng)為了省電增加了休眠模式,系統(tǒng)待機(jī)一段時(shí)間后,會(huì)殺死后臺(tái)正在運(yùn)行的進(jìn)程。但系統(tǒng)會(huì)有一個(gè)后臺(tái)運(yùn)行白名單,白名單里的應(yīng)用將不會(huì)受到影響,在原生系統(tǒng)下,通過(guò):「設(shè)置」 - 「電池」 - 「電池優(yōu)化」 - 「未優(yōu)化應(yīng)用」,可以看到這個(gè)白名單。
通常會(huì)看到下面這兩位:

下次被產(chǎn)品說(shuō)「 XXX 都可以保活,為什么我們不行!」的時(shí)候,你就知道怎么懟回去了。大廠通過(guò)和手機(jī)廠商的合作,將自己的應(yīng)用默認(rèn)加入到白名單中。如果你在一個(gè)能談成這種合作的大廠,也就不用往下看了。
好在系統(tǒng)還沒(méi)有拋棄我們,允許我們申請(qǐng)把應(yīng)用加入白名單。
首先,在 AndroidManifest.xml 文件中配置一下權(quán)限:
<uses-permissionandroid:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
可以通過(guò)以下方法,判斷我們的應(yīng)用是否在白名單中:
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean isIgnoringBatteryOptimizations() {
boolean isIgnoring = false;
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if(powerManager != null) {
isIgnoring = powerManager.isIgnoringBatteryOptimizations(getPackageName());
}
return isIgnoring;
}
如果不在白名單中,可以通過(guò)以下代碼申請(qǐng)加入白名單:
@RequiresApi(api = Build.VERSION_CODES.M)
public void requestIgnoreBatteryOptimizations() {
try{
Intent intent = newIntent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:"+ getPackageName()));
startActivity(intent);
} catch(Exception e) {
e.printStackTrace();
}
}
申請(qǐng)時(shí),應(yīng)用上會(huì)出現(xiàn)這樣一個(gè)窗口:

可以看到,這個(gè)系統(tǒng)彈窗會(huì)有影響電池續(xù)航的提醒,所以如果想讓用戶(hù)點(diǎn)允許,必須要有相關(guān)的說(shuō)明。如果要判斷用戶(hù)是否點(diǎn)擊了允許,可以在申請(qǐng)的時(shí)候調(diào)用 startActivityForResult,在 onActivityResult 里再判斷一次是否在白名單中。
7、加入后臺(tái)運(yùn)行白名單的多廠商適配方法
7.1 基本說(shuō)明
Android 開(kāi)發(fā)的一個(gè)難點(diǎn)在于,各大手機(jī)廠商對(duì)原生系統(tǒng)進(jìn)行了不同的定制,導(dǎo)致我們需要進(jìn)行不同的適配,后臺(tái)管理就是一個(gè)很好的體現(xiàn)。幾乎各個(gè)廠商都有自己的后臺(tái)管理,就算應(yīng)用加入了后臺(tái)運(yùn)行白名單,仍然可能會(huì)被廠商自己的后臺(tái)管理干掉。
如果能把應(yīng)用加入廠商系統(tǒng)的后臺(tái)管理白名單,可以進(jìn)一步降低進(jìn)程被殺的概率。不同的廠商在不同的地方進(jìn)行設(shè)置,一般是在各自的「手機(jī)管家」,但更難的是,就算同一個(gè)廠商的系統(tǒng),不同的版本也可能是在不同地方設(shè)置。
最理想的做法是,我們根據(jù)不同手機(jī),甚至是不同的系統(tǒng)版本,給用戶(hù)呈現(xiàn)一個(gè)圖文操作步驟,并且提供一個(gè)按鈕,直接跳轉(zhuǎn)到指定頁(yè)面進(jìn)行設(shè)置。但需要對(duì)每個(gè)廠商每個(gè)版本進(jìn)行適配,工作量是比較大的。我使用真機(jī)測(cè)試了大部分主流 Android 廠商的手機(jī)后,整理出了部分手機(jī)的相關(guān)資料。
首先我們可以定義這樣兩個(gè)方法:
/**
* 跳轉(zhuǎn)到指定應(yīng)用的首頁(yè)
*/
private void showActivity(@NonNull String packageName) {
Intent intent = getPackageManager().getLaunchIntentForPackage(packageName);
startActivity(intent);
}
/**
* 跳轉(zhuǎn)到指定應(yīng)用的指定頁(yè)面
*/
private void showActivity(@NonNull String packageName, @NonNull String activityDir) {
Intent intent = new Intent();
intent.setComponent(newComponentName(packageName, activityDir));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
以下是部分手機(jī)的廠商判斷,跳轉(zhuǎn)方法及對(duì)應(yīng)設(shè)置步驟,跳轉(zhuǎn)方法不保證在所有版本上都能成功跳轉(zhuǎn),都需要加 try catch。
7.2 華為
廠商判斷:
public boolean isHuawei() {
if(Build.BRAND == null) {
return false;
} else{
return Build.BRAND.toLowerCase().equals("huawei") || Build.BRAND.toLowerCase().equals("honor");
}
}
跳轉(zhuǎn)華為手機(jī)管家的啟動(dòng)管理頁(yè):
private void goHuaweiSetting() {
try{
showActivity("com.huawei.systemmanager",
"com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
} catch(Exception e) {
showActivity("com.huawei.systemmanager",
"com.huawei.systemmanager.optimize.bootstart.BootStartActivity");
}
}
操作步驟:應(yīng)用啟動(dòng)管理 -> 關(guān)閉應(yīng)用開(kāi)關(guān) -> 打開(kāi)允許自啟動(dòng)。
7.3 小米
廠商判斷:
public static boolean isXiaomi() {
return Build.BRAND != null&& Build.BRAND.toLowerCase().equals("xiaomi");
}
跳轉(zhuǎn)小米安全中心的自啟動(dòng)管理頁(yè)面:
private void goXiaomiSetting() {
showActivity("com.miui.securitycenter",
"com.miui.permcenter.autostart.AutoStartManagementActivity");
}
操作步驟:授權(quán)管理 -> 自啟動(dòng)管理 -> 允許應(yīng)用自啟動(dòng)。
7.4 OPPO
廠商判斷:
public static boolean isOPPO() {
return Build.BRAND != null&& Build.BRAND.toLowerCase().equals("oppo");
}
跳轉(zhuǎn) OPPO 手機(jī)管家:
private void goOPPOSetting() {
try{
showActivity("com.coloros.phonemanager");
} catch(Exception e1) {
try{
showActivity("com.oppo.safe");
} catch(Exception e2) {
try{
showActivity("com.coloros.oppoguardelf");
} catch(Exception e3) {
showActivity("com.coloros.safecenter");
}
}
}
}
操作步驟:權(quán)限隱私 -> 自啟動(dòng)管理 -> 允許應(yīng)用自啟動(dòng)。
7.5 VIVO
廠商判斷:
public static boolean isVIVO() {
return Build.BRAND != null&& Build.BRAND.toLowerCase().equals("vivo");
}
跳轉(zhuǎn) VIVO 手機(jī)管家:
private void goVIVOSetting() {
showActivity("com.iqoo.secure");
}
操作步驟:權(quán)限管理 -> 自啟動(dòng) -> 允許應(yīng)用自啟動(dòng)。
7.6 魅族
廠商判斷:
public static boolean isMeizu() {
return Build.BRAND != null&& Build.BRAND.toLowerCase().equals("meizu");
}
跳轉(zhuǎn)魅族手機(jī)管家:
private void goMeizuSetting() {
showActivity("com.meizu.safe");
}
操作步驟:權(quán)限管理 -> 后臺(tái)管理 -> 點(diǎn)擊應(yīng)用 -> 允許后臺(tái)運(yùn)行。
7.7 三星
廠商判斷:
public static boolean isSamsung() {
return Build.BRAND != null&& Build.BRAND.toLowerCase().equals("samsung");
}
跳轉(zhuǎn)三星智能管理器:
private void goSamsungSetting() {
try{
showActivity("com.samsung.android.sm_cn");
} catch(Exception e) {
showActivity("com.samsung.android.sm");
}
}
操作步驟:自動(dòng)運(yùn)行應(yīng)用程序 -> 打開(kāi)應(yīng)用開(kāi)關(guān) -> 電池管理 -> 未監(jiān)視的應(yīng)用程序 -> 添加應(yīng)用。
7.8 樂(lè)視
廠商判斷:
public static boolean isLeTV() {
return Build.BRAND != null&& Build.BRAND.toLowerCase().equals("letv");
}
跳轉(zhuǎn)樂(lè)視手機(jī)管家:
private void goLetvSetting() {
showActivity("com.letv.android.letvsafe",
"com.letv.android.letvsafe.AutobootManageActivity");
}
操作步驟:自啟動(dòng)管理 -> 允許應(yīng)用自啟動(dòng)。
7.9 錘子
廠商判斷:
public static boolean isSmartisan() {
return Build.BRAND != null&& Build.BRAND.toLowerCase().equals("smartisan");
}
跳轉(zhuǎn)手機(jī)管理:
private void goSmartisanSetting() {
showActivity("com.smartisanos.security");
}
操作步驟:權(quán)限管理 -> 自啟動(dòng)權(quán)限管理 -> 點(diǎn)擊應(yīng)用 -> 允許被系統(tǒng)啟動(dòng)。
8、友商致敬?
在之前做的跑步應(yīng)用中,我在設(shè)置里增加了一個(gè)權(quán)限設(shè)置頁(yè)面,將上面提到的設(shè)置放在這里面。
最近發(fā)現(xiàn)友商某咚也跟進(jìn)了,圖 1 是我們做的,圖 2 是某咚做的:

某咚從設(shè)計(jì)、從我寫(xiě)的不夠好的文案,甚至是我從十幾臺(tái)手機(jī)上一張一張截下來(lái)的圖,進(jìn)行了全方位的致敬。感謝某咚的認(rèn)可,但最近在某個(gè)發(fā)布會(huì)上聽(tīng)到這么一句話:在致敬的同時(shí),能不能說(shuō)一句謝謝?
某咚的致敬,一方面說(shuō)明了目前確實(shí)存在進(jìn)程容易被殺,保活難度大的問(wèn)題,另一方面也說(shuō)明了這種引導(dǎo)用戶(hù)進(jìn)行白名單設(shè)置的手段是有效的。
附錄:更多相關(guān)技術(shù)文章
《應(yīng)用保活終極總結(jié)(一):Android6.0以下的雙進(jìn)程守護(hù)保活實(shí)踐》
《應(yīng)用保活終極總結(jié)(二):Android6.0及以上的保活實(shí)踐(進(jìn)程防殺篇)》
《應(yīng)用保活終極總結(jié)(三):Android6.0及以上的保活實(shí)踐(被殺復(fù)活篇)》
《Android進(jìn)程保活詳解:一篇文章解決你的所有疑問(wèn)》
《Android端消息推送總結(jié):實(shí)現(xiàn)原理、心跳保活、遇到的問(wèn)題等》
《為何基于TCP協(xié)議的移動(dòng)端IM仍然需要心跳保活機(jī)制?》
《微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺(tái)保活實(shí)戰(zhàn)分享(進(jìn)程保活篇)》
《微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信后臺(tái)保活實(shí)戰(zhàn)分享(網(wǎng)絡(luò)保活篇)》
《移動(dòng)端IM實(shí)踐:實(shí)現(xiàn)Android版微信的智能心跳機(jī)制》
《移動(dòng)端IM實(shí)踐:WhatsApp、Line、微信的心跳策略分析》
《Android P正式版即將到來(lái):后臺(tái)應(yīng)用保活、消息推送的真正噩夢(mèng)》
《全面盤(pán)點(diǎn)當(dāng)前Android后臺(tái)保活方案的真實(shí)運(yùn)行效果(截止2019年前)》
《一文讀懂即時(shí)通訊應(yīng)用中的網(wǎng)絡(luò)心跳包機(jī)制:作用、原理、實(shí)現(xiàn)思路等》
《融云技術(shù)分享:融云安卓端IM產(chǎn)品的網(wǎng)絡(luò)鏈路保活技術(shù)實(shí)踐》
《正確理解IM長(zhǎng)連接的心跳及重連機(jī)制,并動(dòng)手實(shí)現(xiàn)(有完整IM源碼)》
(本文同步發(fā)布于:http://www.52im.net/thread-2881-1-1.html)
posted @ 2019-12-27 14:51 Jack Jiang 閱讀(423) | 評(píng)論 (0) | 編輯 收藏
posted @ 2019-12-24 11:25 Jack Jiang 閱讀(299) | 評(píng)論 (0) | 編輯 收藏
posted @ 2019-12-19 20:22 Jack Jiang 閱讀(144) | 評(píng)論 (0) | 編輯 收藏
posted @ 2019-12-17 19:36 Jack Jiang 閱讀(183) | 評(píng)論 (0) | 編輯 收藏