企業(yè)微信針對百萬級組織架構(gòu)的客戶端性能優(yōu)化實(shí)踐

本文由騰訊WXG客戶端開發(fā)工程師yecong分享,本文做了修訂和改動。
1、引言
相對于傳統(tǒng)的消費(fèi)級IM應(yīng)用,企業(yè)級IM應(yīng)用的特殊之外在于它的用戶關(guān)系是按照所屬企業(yè)的組織架構(gòu)來關(guān)聯(lián)的起來,而組織架構(gòu)的大小是無法預(yù)設(shè)上限的,這也要求企業(yè)級IM應(yīng)用在遇到真正的超大規(guī)模組織架構(gòu)時(shí),如何保證它的應(yīng)用性能不受限于(或者說是盡可能不受限于)企業(yè)架構(gòu)規(guī)模,這是個(gè)比較有難度的技術(shù)問題。
本文主要分享的是企業(yè)微信在百對百萬級大規(guī)模組織架構(gòu)(后文簡稱大架構(gòu))時(shí),是如何對客戶端進(jìn)行性能優(yōu)化過程的,希望帶給你啟發(fā)。
內(nèi)容分成兩部分講述,第一部分是短線迭代的優(yōu)化,主要是并發(fā)性能的優(yōu)化。第二部分是長線迭代的優(yōu)化,主要是從業(yè)務(wù)模式上做了根本性優(yōu)化。
以下是相關(guān)文章,推薦一并閱讀:
《企業(yè)微信客戶端中組織架構(gòu)數(shù)據(jù)的同步更新方案優(yōu)化實(shí)戰(zhàn)》
《企業(yè)微信的IM架構(gòu)設(shè)計(jì)揭秘:消息模型、萬人群、已讀回執(zhí)、消息撤回等》
《釘釘——基于IM技術(shù)的新一代企業(yè)OA平臺的技術(shù)挑戰(zhàn)(視頻+PPT) [附件下載]》
《阿里釘釘技術(shù)分享:企業(yè)級IM王者——釘釘在后端架構(gòu)上的過人之處》
?
技術(shù)交流:
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點(diǎn)此)
(本文已同步發(fā)布于:http://www.52im.net/thread-4437-1-1.html)
2、100萬級組織架構(gòu)時(shí)的性能問題
當(dāng)私有化的組織架構(gòu)上升到100W的量級時(shí),出現(xiàn)了嚴(yán)重影響組織架構(gòu)使用的問題:打開二級部門時(shí),加載緩慢。
如圖所示,loading可能持續(xù)一分鐘以上:
3、100萬級組織架構(gòu)的問題分析
我們分析一下加載二級部門的流程。
下面是加載二級部門的流程圖:
1)如果從來沒加載過該部門,需要從服務(wù)端拉取部門下的節(jié)點(diǎn)詳情(這里是因?yàn)橹拔覀円呀?jīng)做了優(yōu)化,首次登錄時(shí)只拉取了部門的節(jié)點(diǎn)ID,沒有拉取詳情);
2)如果加載過該部門,就直接從DB讀取該部門的數(shù)據(jù),然后返回UI展示。
當(dāng)只有一條DB線程時(shí),組織架構(gòu)更新的任務(wù),可能會插入到加載二級部門的任務(wù)的前面。而在百萬級別的組織架構(gòu)中,全量更新的DB任務(wù)有可能比較久,全量更新的插入或者更新節(jié)點(diǎn)可能比較多,導(dǎo)致本來很快可以完成的二級部門加載任務(wù),要排隊(duì)比較久才能執(zhí)行完。
下面是組織架構(gòu)全量更新的流程圖:
在這里,讀寫并發(fā)上出現(xiàn)了明顯的瓶頸。
原因總結(jié)如下:
1)加載二級部門和全量更新共用一條DB線程;
2)當(dāng)全量更新大量節(jié)點(diǎn)時(shí),全量更新的低優(yōu)先級任務(wù)卡住加載二級部門的高優(yōu)先級任務(wù)。
4、針對100萬級組織架構(gòu)的優(yōu)化方案
4.1基本
讀寫分離為了提高組織架構(gòu)在大規(guī)模數(shù)據(jù)下的讀寫并發(fā)性能,我們開啟了wal模式,把讀寫任務(wù)分別放在不同的線程中執(zhí)行。
針對加載二級部門的流程,可以在讀線程中讀取部門的詳情節(jié)點(diǎn),而組織架構(gòu)更新可以在寫線程中單獨(dú)執(zhí)行。
由于加載二級部門的原流程是拉取數(shù)據(jù)、寫入DB、再從DB讀取數(shù)據(jù),而且wal只支持一寫多讀,因此我們調(diào)整了緩存策略,把保存節(jié)點(diǎn)詳情的寫任務(wù)延遲到流程最后,優(yōu)先構(gòu)造了cache返回UI。
這樣從DB中讀出數(shù)據(jù)的讀任務(wù),就不需要等待保存節(jié)點(diǎn)詳情的寫任務(wù)。避免了保存節(jié)點(diǎn)的寫任務(wù)再次被其他寫任務(wù)阻塞,讀任務(wù)又被保存節(jié)點(diǎn)的寫任務(wù)阻塞,退化成串行操作。
4.2WAL機(jī)制的原理
調(diào)用方修改的數(shù)據(jù)并不直接寫入到數(shù)據(jù)庫文件中,而是寫入到另外一個(gè)稱為WAL的文件中,然后在隨后的某個(gè)時(shí)間點(diǎn)被寫回到數(shù)據(jù)庫文件中。
在這個(gè)時(shí)間點(diǎn)的回寫操作,會降低數(shù)據(jù)庫當(dāng)時(shí)的讀寫性能。
但是通過設(shè)置對WAL文件大小的限制,這種性能影響是可控的。
實(shí)際上線后也沒有遇到由于checkpoint同步導(dǎo)致數(shù)據(jù)庫慢的反饋。
4.3緩存策略
寫策略的步驟:先更新緩存中的數(shù)據(jù),再更新數(shù)據(jù)庫中的數(shù)據(jù)。
讀策略的步驟:
1)如果讀取的數(shù)據(jù)命中了緩存,則直接返回?cái)?shù)據(jù);
2)如果讀取的數(shù)據(jù)沒有命中緩存,則從數(shù)據(jù)庫中讀取數(shù)據(jù),然后將數(shù)據(jù)寫入到緩存,并且返回給UI。
4.4方案總結(jié)
5、100萬級優(yōu)化后的效果
?
在優(yōu)化前,只有52%的用戶能在1s內(nèi)加載完二級部門。上線之后,93%的用戶都能在1s內(nèi)打開二級部門。耗時(shí)小于1s的用戶占比提升40%!
6、當(dāng)對面300萬組織架構(gòu)時(shí)的問題
6.1概述
當(dāng)業(yè)務(wù)進(jìn)一步發(fā)展時(shí),我們預(yù)估未來將要到達(dá)300W量級的組織架構(gòu)。于是我們就開始提前規(guī)劃如何能在組織架構(gòu)數(shù)量一直增長的情況下,還能讓組織架構(gòu)流暢好用。
6.2問題
主要是:
1)選人控件閃退和ANR;
2)組織架構(gòu)全量更新閃退。
在300w的組織架構(gòu)環(huán)境中,舊的組織架構(gòu)加載方案,在全量更新、選人控件中均出現(xiàn)了占用內(nèi)存過大甚至閃退的問題。而且舊方案的加載時(shí)間會隨著節(jié)點(diǎn)數(shù)量的增加,不可避免地成正比增長。
6.3分析
當(dāng)前方案的耗時(shí)、內(nèi)存占用與用戶組織架構(gòu)的大小成正比,單點(diǎn)優(yōu)化無法滿足組織架構(gòu)持續(xù)增長的需求。
具體來說,會造成下面的一些問題:
1)選人控件會加載全量的組織架構(gòu)ID樹,數(shù)量過多時(shí)容易發(fā)生閃退和ANR;
2)組織架構(gòu)全量更新占用內(nèi)存過大,造成閃退。
因此,我們需要一個(gè)新的業(yè)務(wù)模式,即便總的組織架構(gòu)規(guī)模一直上漲的情況下,也能維持較好的性能。
7、針對300萬組織架構(gòu)的優(yōu)化方案
比較容易想到的一個(gè)方案是web加載的模式,不保存本地?cái)?shù)據(jù),但是體驗(yàn)比較差,每層都會出loading。
聯(lián)系到我們的具體業(yè)務(wù),由于私有化對不同的部門,劃分出了具有意義的獨(dú)立組織機(jī)構(gòu)——單位。
單位是具有管理意義的部門,不同單位可以獨(dú)立加載。而每個(gè)人,也擁有主單位和兼崗單位。所以可以按照單位加載的方式,從根本上解決目前組織架構(gòu)面臨的瓶頸。
按單位加載,可以簡單理解為按部門加載:
概念定義:
1)單位:政府行政組織結(jié)構(gòu)中的職能部門,組建架構(gòu)并承擔(dān)對應(yīng)責(zé)任;
2)主單位:“我”所在的單位;
3)其他單位:除了“我”所在的其他單位;
4)骨架:通訊錄骨架包含了所有的單位節(jié)點(diǎn);
5)普通部門:不屬于任何單位的部門節(jié)點(diǎn)。
下圖是組織架構(gòu)樹的示意圖:
如上圖所示:藍(lán)色節(jié)點(diǎn)是優(yōu)先加載的本單位,灰色節(jié)點(diǎn)是其他單位,紅色節(jié)點(diǎn)是骨架。不同的單位獨(dú)立加載。
8、300萬優(yōu)化方案中的“按單位加載”技術(shù)思路
8.1加載策略
接下來我們看看加載策略。
第一:是對自己所在的主單位(藍(lán)色節(jié)點(diǎn)),每次喚醒時(shí)就會更新,跟舊組織架構(gòu)的邏輯類似,但是會限制拉取節(jié)點(diǎn)的數(shù)量。
第二:對于其他單位(灰色節(jié)點(diǎn)),點(diǎn)擊到該單位時(shí)才會拉取,2個(gè)小時(shí)后會淘汰刪除,避免數(shù)據(jù)表過大。
第三:對于骨架(紅色節(jié)點(diǎn)),會全量加載節(jié)點(diǎn)ID,再拉取節(jié)點(diǎn)詳情。
拉取策略限制了能夠拉取的節(jié)點(diǎn)詳情數(shù)量,如果單位節(jié)點(diǎn)數(shù)量超過了限制,首先拉取全量ID,再按照優(yōu)先規(guī)則,拉取配置的節(jié)點(diǎn)詳請數(shù)量。
8.2加載流程
加載的流程是先拉取自己的單位列表,然后拉取每個(gè)單位的全量通訊錄ID,再按照后臺策略,拉取所需的詳細(xì)節(jié)點(diǎn),最后拉取骨架。
如果點(diǎn)擊到主單位:
1)如果只有ID沒有節(jié)點(diǎn),會立刻拉取節(jié)點(diǎn)詳情返回界面;
2)如果ID和節(jié)點(diǎn)詳情都有,可以直接返回UI展示,然后延遲刷新節(jié)點(diǎn)。
如果是點(diǎn)擊到其他單位:可能出現(xiàn)ID和詳情都沒有的情況,需要拉取其他單位的節(jié)點(diǎn),界面loading等待。
如果是骨架:就一定有節(jié)點(diǎn)和詳情,只需要延遲刷新。
9、300萬優(yōu)化方案的分層設(shè)計(jì)思路
接下來我們看看如何分層。
在300萬量級的大規(guī)模組織架構(gòu)下,移動端和pc端都出現(xiàn)了組織架構(gòu)卡頓、閃退的問題,所以我們希望能夠開發(fā)一套各端共用的邏輯,統(tǒng)一維護(hù)。
第一:是要抽取公共的基礎(chǔ)庫,包括boost庫、任務(wù)框架、線程管理框架等。
第二:是設(shè)計(jì)公共的數(shù)據(jù)結(jié)構(gòu)。
第三:因?yàn)椴煌说木W(wǎng)絡(luò)庫差異比較大,這里不好完全共用,所以需要抽取網(wǎng)絡(luò)任務(wù)接口,由各端獨(dú)立實(shí)現(xiàn)。
具體到框架圖,我們從下往上看:
1)底層是基礎(chǔ)庫;
2)接著是C++實(shí)現(xiàn)的跨平臺業(yè)務(wù)層;
3)Service層是移動端和pc端分開實(shí)現(xiàn),主要是做接口調(diào)用和回調(diào)的簡單封裝;
4)上層則各端界面實(shí)現(xiàn)。
上層界面為了兼容新舊兩套組織架構(gòu),也做了接口抽象,可以通過開關(guān)自由切換。這樣優(yōu)點(diǎn)就是有統(tǒng)一的業(yè)務(wù)邏輯代碼、DB設(shè)計(jì)和線程管理。
關(guān)鍵點(diǎn):
1)抽取公共基礎(chǔ)庫;
2)抽象公共的數(shù)據(jù)結(jié)構(gòu);
3)抽象網(wǎng)絡(luò)層和數(shù)據(jù)庫層接口。
優(yōu)點(diǎn):統(tǒng)一的業(yè)務(wù)邏輯代碼、DB設(shè)計(jì)、線程管理。
10、300萬優(yōu)化方案的整體架構(gòu)設(shè)計(jì)思路
在具體實(shí)現(xiàn)之前,我們來看看架構(gòu)設(shè)計(jì)的一些概念。
10.1架構(gòu)整潔之道
1)業(yè)務(wù)實(shí)體和用例:
關(guān)鍵業(yè)務(wù)邏輯和關(guān)鍵業(yè)務(wù)數(shù)據(jù)是緊密相關(guān)的,所以它們很適合被放在同一個(gè)對象中處理。
我們將這種對象稱為“業(yè)務(wù)實(shí)體”。業(yè)務(wù)實(shí)體這個(gè)概念中應(yīng)該只有業(yè)務(wù)邏輯,沒有別的,與數(shù)據(jù)庫、用戶界面、第三方框架等內(nèi)容無關(guān)。
用例所描述的是某種特定應(yīng)用情景下的業(yè)務(wù)邏輯,可以理解為:輸入 + 業(yè)務(wù)實(shí)體 + 輸出 = 用例。
2)軟件架構(gòu):
軟件的系統(tǒng)架構(gòu)應(yīng)該為該系統(tǒng)的用例提供支持。
一個(gè)良好的架構(gòu)設(shè)計(jì)應(yīng)該圍繞著用例來展開,這樣的架構(gòu)設(shè)計(jì)可以在脫離框架、工具以及使用環(huán)境的情況下完整地描述用例。
3)整潔架構(gòu):
下圖的同心圓分別代表了軟件系統(tǒng)中的不同層次,越靠近中心,其所在的軟件層次就越高?;旧希鈱訄A代表的是機(jī)制,內(nèi)層圓代表的是策略。
這其中有一條貫穿整個(gè)架構(gòu)設(shè)計(jì)的規(guī)則,即依賴關(guān)系規(guī)則:
10.2我們的架構(gòu)
我們的類圖與架構(gòu)設(shè)計(jì)概念的對應(yīng)關(guān)系如下:
1)業(yè)務(wù)實(shí)體:ArchTask;
2)用例:ArchProto;
3)模型層:即最外層,各種第三方框架,如DbInterface(數(shù)據(jù)庫模塊)、ArchLogicHandler(網(wǎng)絡(luò)模塊)等。
我們從一次具體的業(yè)務(wù)調(diào)用流程來看看這樣設(shè)計(jì)的意義。
下面是從UI發(fā)起的一次架構(gòu)更新流程,大家可以主要關(guān)注控制流是怎么穿越各層的邊界:控制流從最外層的用戶界面開始,穿過用例(Arch),最后調(diào)用最外層的組件:網(wǎng)絡(luò)模塊和數(shù)據(jù)庫模塊。但是我們源碼中的依賴方向卻都是向內(nèi)指向用例的。
這里,我們采用的是依賴反轉(zhuǎn)原則(DIP)來解決這種相反性。我們可以通過調(diào)整代碼中的接口和繼承關(guān)系,利用源碼中的依賴關(guān)系,限制控制流只能在正確的地方跨域架構(gòu)邊界。
在上面的流程圖中,主要有兩個(gè)應(yīng)用依賴反轉(zhuǎn)原則的地方:
1)CalcPreLoadArchIDs是從SyncUnitArchTask(業(yè)務(wù)實(shí)體)調(diào)用調(diào)用到ArchProto(用例)。
業(yè)務(wù)實(shí)體這樣的高層概念,是無須了解像用例這樣的底層概念的。反之,底層業(yè)務(wù)用例卻需要了解高層的業(yè)務(wù)實(shí)體。
所以在SyncUnitArchTask中,其實(shí)是通過調(diào)用ArchProto的接口來調(diào)用CalcPreLoadArchIDs。
SyncUnitArchTask中的調(diào)用代碼如下:
arch_service_context_->CalcPreLoadArchIDs(unit_id_, arch_service_context_->GetCurrentVid(), other_unit_click_partyid_, vecHashNode, all_tmp_ids, arch_ids, ptr_map_);
ArchProto會在Task初始化時(shí),把自己設(shè)置進(jìn)Task中,給各類型的Task反向調(diào)用。
classArchProto : publicArchServiceContext
{
...
};
2)最外層的模型層一般是由工具、數(shù)據(jù)庫、網(wǎng)絡(luò)框架等組成的。
框架與驅(qū)動程序?qū)又邪怂械膶?shí)現(xiàn)細(xì)節(jié)。
從系統(tǒng)架構(gòu)的角度看,工具通常是無關(guān)緊要的,因?yàn)檫@只是一個(gè)底層的實(shí)現(xiàn)細(xì)節(jié),一種達(dá)成目標(biāo)的手段。
當(dāng)Task需要調(diào)用網(wǎng)絡(luò)模塊收發(fā)請求或者調(diào)用數(shù)據(jù)庫模塊獲取數(shù)據(jù)時(shí),為了避免內(nèi)層策略依賴外層機(jī)制,Task只會調(diào)用外層工具的接口層,而不會依賴實(shí)現(xiàn)細(xì)節(jié)。
這樣的架構(gòu)設(shè)計(jì)給我們帶來的好處是,我們可以輕松替換框架,而不影響內(nèi)層策略。比如在桌面端,我們會有另外一套完全不同的網(wǎng)絡(luò)模塊實(shí)現(xiàn),只需要掛接不同的網(wǎng)絡(luò)實(shí)現(xiàn)子類,我們就可以在桌面端復(fù)用新的大架構(gòu)模塊。
良好的架構(gòu)設(shè)計(jì)應(yīng)該盡可能地允許用戶推遲和延后決定采用什么框架、數(shù)據(jù)庫、網(wǎng)絡(luò)框架以及其他與環(huán)境相關(guān)的工具。
總之,良好的架構(gòu)設(shè)計(jì)應(yīng)該只關(guān)注用例,并能將它們與其他的周邊因素隔離。
10.3新舊組織架構(gòu)模塊的交互
大架構(gòu)跨平臺層,跟原來的組織架構(gòu)模塊是怎么交互的呢?
原來的組織架構(gòu)的數(shù)據(jù)表主要分成三部分:
1)部門表;
2)人員信息表;
3)部門人員關(guān)系表。
而出現(xiàn)性能問題的主要在于關(guān)系表上。所以數(shù)據(jù)設(shè)計(jì)上,人員信息保留在原組織架構(gòu)底層,部門人員關(guān)系表、部門表在大架構(gòu)底層。
表結(jié)構(gòu)設(shè)計(jì):
1)主要組成:人員信息表、部門表、部門人員關(guān)系表;
2)大架構(gòu)底層保存部門和部門人員關(guān)系表,人員信息保留在原組織架構(gòu)底層。
大架構(gòu)底層與原組織架構(gòu)底層的業(yè)務(wù)關(guān)聯(lián):
1)人員展示的部門鏈路如何獲取?從大架構(gòu)底層獲取,因?yàn)殛P(guān)系表存放在大架構(gòu)底層;
2)搜索如何做?部門名字保存到原組織架構(gòu)底層,復(fù)用原組織架構(gòu)底層的索引建立邏輯。
11、300萬優(yōu)化方案的雙DB切換模式
11.1舊的讀寫表切換方式
舊方案里組織架構(gòu)的全量更新流程:
當(dāng)后臺告訴客戶端需要全量更新時(shí),客戶端會將所有節(jié)點(diǎn)標(biāo)為待刪除,然后同步后臺的節(jié)點(diǎn),清除待刪除標(biāo)記。同步完成后,將寫表的數(shù)據(jù)同步到讀表,更新版本號。最后UI就可以從讀表中讀取到最新的數(shù)據(jù)。
而之前通過用戶日志案例分析,最長的耗時(shí)主要是在將寫表的數(shù)據(jù)拷貝到讀表上面。在這個(gè)過程中,大架構(gòu)下部分用戶的日志里有更新57w節(jié)點(diǎn)的數(shù)據(jù)用了2個(gè)半小時(shí)的情況,而且這個(gè)步驟是原子操作,如果不能夠一次完成,下次還得重新執(zhí)行。
原有流程里,讀表和寫表是固定的,導(dǎo)致全量更新需要等讀表同步完數(shù)據(jù),界面才能讀到新數(shù)據(jù)。
分析:寫表同步數(shù)據(jù)到讀表耗時(shí)很久,當(dāng)全量更新時(shí),如果有大量節(jié)點(diǎn)需要更新,會耗時(shí)很長。
缺點(diǎn):寫表和讀表固定,全量更新需要等數(shù)據(jù)同步完成,界面才能讀取到新數(shù)據(jù)。
11.2新的雙DB切換方式
針對舊方案中讀寫表同步過久的問題,大架構(gòu)方案里我們換成了雙DB切換的模式。下面是我們的狀態(tài)機(jī)設(shè)計(jì)和業(yè)務(wù)代碼獲取表名的邏輯。
這樣修改之后,不需要等讀寫表同步完,UI就可以讀取到最新數(shù)據(jù)。而同步的過程可以在后臺慢慢完成,并且不會受原子性操作的限制。業(yè)務(wù)代碼獲取讀表的邏輯,也收攏到了一個(gè)函數(shù)。
?
因?yàn)閱挝荒J较?,每個(gè)單位的節(jié)點(diǎn)數(shù)量都不會很多,而且大多數(shù)用戶只會加載日常有交流的幾個(gè)單位,所以讀寫表同步這里,我們采用了把原表刪掉,全量拷貝的方式。
12、200萬級優(yōu)化后的效果
對于耗時(shí),優(yōu)化前使用全量加載的方式使得耗時(shí)很長,而優(yōu)化后采用的“本單位+骨架”的預(yù)加載邏輯使得加載耗時(shí)大幅度減小。優(yōu)化后的內(nèi)存占用大小在各場景下均有減小,通訊錄頁面的流暢度也得到了一定的提升。
耗時(shí):
CPU占用率:
內(nèi)存占用大?。?/strong>
卡頓:
13、相關(guān)資料
[1]?企業(yè)微信客戶端中組織架構(gòu)數(shù)據(jù)的同步更新方案優(yōu)化實(shí)戰(zhàn)
[2]?企業(yè)微信的IM架構(gòu)設(shè)計(jì)揭秘:消息模型、萬人群、已讀回執(zhí)、消息撤回等
[3]?釘釘——基于IM技術(shù)的新一代企業(yè)OA平臺的技術(shù)挑戰(zhàn)(視頻+PPT) [附件下載]》
[4]?阿里釘釘技術(shù)分享:企業(yè)級IM王者——釘釘在后端架構(gòu)上的過人之處》
[5]?深度解密釘釘即時(shí)消息服務(wù)DTIM的技術(shù)設(shè)計(jì)
[6]?深度揭密RocketMQ在釘釘IM系統(tǒng)中的應(yīng)用實(shí)踐
[7]?IM開發(fā)干貨分享:萬字長文,詳解IM“消息“列表卡頓優(yōu)化實(shí)踐
[8]?手Q客戶端針對2020年春節(jié)紅包的技術(shù)實(shí)踐
[9]?移動端IM實(shí)踐:Android版微信如何大幅提升交互性能(一)
[10]?移動端IM實(shí)踐:Android版微信如何大幅提升交互性能(二)
[11]?移動端IM實(shí)踐:iOS版微信的多設(shè)備字體適配方案探討
[12]?愛奇藝技術(shù)分享:愛奇藝Android客戶端啟動速度優(yōu)化實(shí)踐總結(jié)
[13]?微信團(tuán)隊(duì)分享:微信支付代碼重構(gòu)帶來的移動端軟件架構(gòu)上的思考
(本文已同步發(fā)布于:http://www.52im.net/thread-4437-1-1.html)