當前位置:
首頁 > 科技 > 企業微信組織架構同步優化的思路與實操演練

企業微信組織架構同步優化的思路與實操演練

作者|胡騰

編輯|小智

作為企業級的微信,在業務快速發展的背景下,迭代優化的要求也越發急迫。企業微信初版的全量同步方案在快速的業務增長面前已經捉襟見肘,針對其遇到的問題,怎樣做好組織架構同步優化?這是又一篇來自微信團隊的技術實戰。

寫在前面

企業微信在快速發展過程中,陸續有大企業加入使用,企業微信初版採用全量同步方案,該方案在大企業下存在流量和性能兩方面的問題,每次同步消耗大量流量,且在 iPhone 5s 上拉取 10w+ 成員架構包解壓時會提示 memory warning 而應用崩潰。

全量同步方案難以支撐業務的快速發展,優化同步方案越來越有必要。本文針對全量同步方案遇到的問題進行分析,提出組織架構增量同步方案,並對移動端實現增量同步方案的思路和重難點進行了講解。

企業微信業務背景

在企業微信中,組織架構是非常重要的模塊,用戶可以在首頁的 tab 上選擇"通訊錄"查看到本公司的組織架構,並且可以通過"通訊錄"找到本公司的所有成員,並與其發起會話或者視頻語音通話。

組織架構是非常重要且敏感的信息,企業微信作為企業級產品,始終把用戶隱私和安全放在重要位置。針對組織架構信息,企業管理員具有高粒度隱私保護操作許可權,不僅支持個人信息隱藏,也支持通訊錄查看許可權等操作。

在企業微信中,組織架構特徵有:

1、多叉樹結構。葉子節點代表成員,非葉子節點代表部門。部門最多只有一個父部門,但成員可屬於多個部門。

2、架構隱藏操作。企業管理員可以在管理後台設置白名單和黑名單,白名單可以查看完整的組織架構,其他成員在組織架構里看不到他們。黑名單的成員只能看到自己所在小組和其所有的父部門,其餘人可以看到黑名單的成員。

3、組織架構操作。企業管理員可以在 web 端和 app 端添加 / 刪除部門,添加 / 刪除 / 移動 / 編輯成員等操作,並且操作結果會及時同步給本公司所有成員。

全量同步方案的問題

本節大致講解下全量同步方案實現以及遇到的問題。

全量同步方案原理

企業微信在 1.0 時代,從穩定性以及快速迭代的角度考慮,延用了企業郵通訊錄同步方案,採取了全量架構同步方案。

核心思想為服務端下發全量節點,客戶端對比本地數據找出變更節點。此處節點可以是用戶,也可以是部門,將組織架構視為二叉樹結構體,其下的用戶與部門均為節點,若同一個用戶存在多個部門下,被視為多個節點。

全量同步方案分為首次同步與非首次同步:

首次同步服務端會下發全量的節點信息的壓縮包,客戶端解壓後得到全量的架構樹並展示。

非首次同步分為兩步:

服務端下發全量節點的 hash 值。客戶端對比本地數據找到刪除的節點保存在內存中,對比找到新增的節點待請求具體信息。

客戶端請求新增節點的具體信息。請求具體信息成功後,再進行本地資料庫的插入 / 更新 / 刪除處理,保證同步流程的原子性。

用戶反饋

初版上線後,收到了大量的組織架構相關的 bug 投訴,主要集中在:

流量消耗過大。

客戶端架構與 web 端架構不一致。

組織架構同步不及時。

這些問題在大企業下更明顯。

問題剖析

深究全量同步方案難以支撐大企業同步的背後原因,皆是因為採取了服務端全量下發 hash 值方案的原因,方案存在以下問題:

拉取大量冗餘信息。即使只有一個成員信息的變化,服務端也會下發全量的 hash 節點。針對幾十萬人的大企業,這樣的流量消耗是相當大的,因此在大企業要儘可能的減少更新的頻率,但是卻會導致架構數據更新不及時。

大企業拉取信息容易失敗。全量同步方案中首次同步架構會一次性拉取全量架構樹的壓縮包,而超大企業這個包的數據有幾十兆,解壓後幾百兆,對內存不足的低端設備,首次載入架構可能會出現內存不足而 crash。非首次同步在對比出新增的節點,請求具體信息時,可能遇到數據量過大而請求超時的情況。

客戶端無法過濾無效數據。客戶端不理解 hash 值的具體含義,導致在本地對比 hash 值時不能過濾掉無效 hash 的情況,可能出現組織架構展示錯誤。

優化組織架構同步方案越來越有必要。

尋找優化思路

尋求同步方案優化點,我們要找准原來方案的痛點以及不合理的地方,通過方案的調整來避免這個問題。

組織架構同步難點

準確且耗費最少資源同步組織架構是一件很困難的事情,難點主要在:

組織架構架構數據量大。消息 / 聯繫人同步一次的數據量一般情況不會過百,而企業微信活躍企業中有許多上萬甚至幾十萬節點的企業,意味著架構一次同步的數據量很輕鬆就會上千上萬。移動端的流量消耗是用戶非常在乎的,且內存有限,減少流量的消耗以及減少內存使用並保證架構樹的完整同步是企業微信追求的目標。

架構規則複雜。組織架構必須同步到完整的架構樹才能展示,而且企業微信里的涉及到複雜的隱藏規則,為了安全考慮,客戶端不應該拿到隱藏的成員。

修改頻繁且改動大。組織架構的調整存在著新建部門且移動若干成員到新部門的情況,也存在解散某個部門的情況。而員工離職也會通過組織架構同步下來,意味著超大型企業基本上每天都會有改動。

技術選型-提出增量更新方案

上述提到的問題,在大型企業下會變得更明顯。在幾輪方案討論後,我們給原來的方案增加了兩個特性來實現增量更新:

增量。服務端記錄組織架構修改的歷史,客戶端通過版本號來增量同步架構。

分片。同步組織架構的介面支持傳閾值來分片拉取。

在新方案中,服務端針對某個節點的存儲結構可簡化為:

vid 是指節點用戶的唯一標識 id,departmentid 是指節點的部門 id,is_delete 表示該節點是否已被刪除。

若節點被刪除了,服務端不會真正的刪除該節點,而將 is_delete 標為 true。

若節點被更新了,服務端會增大記錄的 seq,下次客戶端來進行同步便能同步到。

其中,seq 是自增的值,可以理解成版本號。每次組織架構的節點有更新,服務端增加相應節點的 seq 值。客戶端通過一個舊的 seq 向伺服器請求,服務端返回這個 seq 和 最新的 seq 之間所有的變更給客戶端,完成增量更新。

圖示為:

通過提出增量同步方案,我們從技術選型層面解決了問題,但是在實際操作中會遇到許多問題,下文中我們將針對方案原理以及實際操作中遇到的問題進行講解。

增量同步方案

本節主要講解客戶端中增量同步架構方案的原理與實現,以及基礎概念講解。

增量同步方案原理

企業微信中,增量同步方案核心思想為:

服務端下發增量節點,且支持傳閾值來分片拉取增量節點,若服務端計算不出客戶端的差量,下發全量節點由客戶端來對比差異。

增量同步方案可抽象為四步完成:

客戶端傳入本地版本號,拉取變更節點。

客戶端找到變更節點並拉取節點的具體信息。

客戶端處理數據並存儲版本號。

判斷完整架構同步是否完成,若尚未完成,重複步驟 1,若完成了完整組織架構同步,清除掉本地的同步狀態。

忽略掉各種邊界條件和異常狀況,增量同步方案的流程圖可以抽象為:

接下來我們再看看增量同步方案中的關鍵概念以及完整流程是怎樣的。

版本號

同步的版本號是由多個版本號拼接成的字元串,版本號的具體含義對客戶端透明,但是對服務端非常重要。

版本號的組成部分為:

版本號回退

增量同步在實際操作過程中會遇到一些問題:

服務端不可能永久存儲刪除的記錄,刪除的記錄對服務端是毫無意義的而且永久存儲會佔用大量的硬碟空間。而且無效數據過多也會影響架構讀取速度。當 is_delete 節點的數目超過一定的閾值後,服務端會物理刪除掉所有的 is_delete 為 true 的節點。此時客戶端會重新拉取全量的數據進行本地對比。

一旦架構隱藏規則變化後,服務端很難計算出增量節點,此時會下發全量節點由客戶端對比出差異。

理想狀況下,若服務端下發全量節點,客戶端鏟掉舊數據,並且去拉全量節點的信息,並且用新數據覆蓋即可。但是移動端這樣做會消耗大量的用戶流量,這樣的做法是不可接受的。所以若服務端下發全量節點,客戶端需要本地對比出增刪改節點,再去拉變更節點的具體信息。

增量同步情況下,若服務端下發全量節點,我們在本文中稱這種情況為版本號回退,效果類似於客戶端用空版本號去同步架構。從統計結果來看,線上版本的同步中有 4% 的情況會出現版本號回退。

閾值分片拉取

若客戶端的傳的 seq 過舊,增量數據可能很大。此時若一次性返回全部的更新數據,客戶端請求的數據量會很大,時間會很長,成功率很低。針對這種場景,客戶端和服務端需要約定閾值,若請求的更新數據總數超過這個閾值,服務端每次最多返回不超過該閾值的數據。若客戶端發現服務端返回的數據數量等於閾值,則再次到服務端請求數據,直到服務端下發的數據數量小於閾值。

節點結構體優化

在全量同步方案中,節點通過 hash 唯一標示。服務端下發的全量 hash 列表,客戶端對比本地存儲的全量 hash 列表,若有新的 hash 值則請求節點具體信息,若有刪除的 hash 值則客戶端刪除掉該節點信息。

在全量同步方案中,客戶端並不能理解 hash 值的具體含義,並且可能遇到 hash 碰撞這種極端情況導致客戶端無法正確處理下發的 hash 列表。

而增量同步方案中,使用 protobuf 結構體代替 hash 值,增量更新中節點的 proto 定義為:

在增量同步方案中,用 vid 和 partyid 來唯一標識節點,完全廢棄了 hash 值。這樣在增量同步的時候,客戶端完全理解了節點的具體含義,而且也從方案上避免了曾經在全量同步方案遇到的 hash 值重複的異常情況。

並且在節點結構體裡帶上了 seq 。節點上的 seq 來表示該節點的版本,每次節點的具體信息有更新,服務端會提高節點的 seq,客戶端發現服務端下發的節點 seq 比客戶端本地的 seq 大,則需要去請求節點的具體信息,避免無效的節點信息請求。

判斷完整架構同步完成

因為 svr 介面支持傳閾值批量拉取變更節點,一次網路操作並不意味著架構同步已經完成。那麼怎麼判斷架構同步完成了呢?這裡客戶端和服務端約定的方案是:

若服務端下發的(新增節點+刪除節點)小於客戶端傳的閾值,則認為架構同步結束。

當完整架構同步完成後,客戶端需要清除掉緩存,並進行一些額外的業務工作,譬如計算部門人數,計算成員搜索熱度等。

增量同步方案 - 完整流程圖

考慮到各種邊界條件和異常情況,增量同步方案的完整流程圖為:

增量同步方案難點

在加入增量和分片特性後,針對幾十萬人的超大企業,在版本號回退的場景,怎樣保證架構同步的完整性和方案選擇成為了難點。

前文提到,隱藏規則變更以及後台物理刪除無效節點後,客戶端若用很舊的版本同步,服務端算不出增量節點,此時服務端會下發全量節點,客戶端需要本地對比所有數據找出變更節點,該場景可以理解為版本號回退。在這種場景下,對於幾十萬節點的超大型企業,若服務端下發的增量節點過多,客戶端請求的時間會很長,成功率會很低,因此需要分片拉取增量節點。而且拉取下來的全量節點,客戶端處理不能請求全量節點的具體信息覆蓋舊數據,這樣的話每次版本號回退的場景流量消耗過大。

因此,針對幾十萬節點的超大型企業的增量同步,客戶端難點在於:

斷點續傳。增量同步過程中,若客戶端遇到網路問題或應用中止了,在下次網路或應用恢復時,能夠接著上次同步的進度繼續同步。

同步過程中不影響正常展示。超大型企業同步的耗時可能較長,同步的時候不應影響正常的組織架構展示。

控制同步耗時。超大型企業版本號回退的場景同步非常耗時,但是我們需要想辦法加快處理速度,減少同步的消耗時間。

思路

架構同步開始,將架構樹緩存在內存中,加快處理速度。

若服務端端下發了需要版本號回退的 flag,本地將 db 中的節點信息做一次備份操作。

將服務端端下發的所有 update 節點,在架構樹中查詢,若找到了,則將備份數據轉為正式數據。若找不到,則為新增節點,需要拉取具體信息並保存在架構樹中。

當完整架構同步結束後,在 db 中找到並刪除掉所有備份節點,清除掉緩存和同步狀態。

若服務端下發了全量節點,客戶端的處理時序圖為:

服務端下發版本號回退標記

從時序圖中可以看出,服務端下發的版本號回退標記是很重要的信號。

而版本號回退這個標記,僅僅在同步的首次會隨著新的版本號而下發。在完整架構同步期間,客戶端需要將該標記緩存,並且跟著版本號一起存在資料庫中。在完整架構同步結束後,需要根據是否版本號回退來決定刪除掉資料庫中的待刪除節點。

備份架構樹方案

架構樹備份最直接的方案是將 db 中數據 copy 一份,並存在新表裡。如果在數據量很小的情況下,這樣做是完全沒有問題的,但是架構樹的節點往往很多,採取這樣簡單粗暴的方案在移動端是完全不可取的,在幾十萬人的企業里,這樣做會造成極大的性能問題。

經過考慮後,企業微信採取的方案是:

若同步架構時,後台下發了需要版本號回退的 flag,客戶端將緩存和 db 中的所有節點標為待刪除(時序圖中 8,9 步)。

針對服務端下發的更新節點,在架構樹中清除掉節點的待刪除標記(時序圖中 10,11 步)。

在完整架構同步結束後,在 db 中找到並刪除掉所有標為待刪除的節點(時序圖中 13 步),並且清除掉所有緩存數據。

而且,在增量同步過程中,不應該影響正常的架構樹展示。所以在架構同步過程中,若有上層來請求 db 中的數據,則需要過濾掉有待刪除標記的節點。

緩存架構樹

方案決定客戶端避免不了全量節點對比,將重要的信息緩存到內存中會大大加快處理速度。內存中的架構樹節點體定義為:

此處我們用 std::map 來緩存架構樹,用 std::pair 作為 key。我們在比較節點的時候,會涉及到很多查詢操作,使用 map 查詢的時間複雜度僅為 O(logn)。

增量同步方案關鍵點

本節單獨將優化同步方案中關鍵點拿出來寫,這些關鍵點不僅僅適用於本文架構同步,也適用於大多數同步邏輯。

保證數據處理完成後,再儲存版本號

在幾乎所有的同步中,版本號都是重中之重,一旦版本號亂掉,後果非常嚴重。

在架構同步中,最最重要的一點是:

保證數據處理完成後,再儲存版本號

在組織架構同步的場景下,為什麼不能先存版本號,再存數據呢?

這涉及到組織架構同步數據的一個重要特徵:架構節點數據是可重複拉取並覆蓋的。

考慮下實際操作中遇到的真實場景:

若客戶端已經向服務端請求了新增節點信息,客戶端此時剛剛插入了新增節點,還未儲存版本號,客戶端應用中止了。

此時客戶端重新啟動,又會用相同版本號拉下剛剛已經處理過的節點,而這些節點跟本地數據對比後,會發現節點的 seq 並未更新而不會再去拉節點信息,也不會造成節點重複。

若一旦先存版本號再存具體數據,一定會有概率丟失架構更新數據。

同步的原子性

正常情況下,一次同步的邏輯可以簡化為:

在企業微信的組織架構同步中存在非同步操作,若進行同步的過程不保證原子性,極大可能出現下圖所示的情況:

該圖中,同步的途中插入了另外一次同步,很容易造成問題:

輸出結果不穩定。若兩次同步幾乎同時開始,但因為存在網路波動等情況,返回結果可能不同,給調試造成極大的困擾。

中間狀態錯亂。若同步中處理服務端返回的結果會依賴於請求同步時的某個中間狀態,而新的同步發起時又會重置這個狀態,很可能會引起匪夷所思的異常。

時序錯亂。整個同步流程應該是原子的,若中間插入了其他同步的流程會造成整個同步流程時序混亂,引發異常。

怎樣保證同步的原子性呢?

我們可以在開始同步的時候記一個 flag 表示正在同步,在結束同步時,清除掉該 flag。若另外一次同步到來時,發現正在同步,則可以直接捨棄掉本次同步,或者等本次同步成功後再進行一次同步。

此外也可將同步串列化,保證同步的時序,多次同步的時序應該是 FIFO 的。

緩存數據一致性

移動端同步過程中的緩存多分為兩種:

內存緩存。加入內存緩存的目的是減少文件 IO 操作,加快程序處理速度。

磁碟緩存。加入磁碟緩存是為了防止程序中止時丟失掉同步狀態。

內存緩存多緩存同步時的數據以及同步的中間狀態,磁碟緩存用於緩存同步的中間狀態防止緩存狀態丟失。

在整個同步過程中,我們都必須保證緩存中的數據和資料庫的數據的更改需要一一對應。在增量同步的情況中,我們每次需要更新 / 刪除資料庫中的節點,都需要更新相應的緩存信息,來保證數據的一致性。

優化數據對比

內存使用

測試方法:使用工具 Instrument,用同一賬號監控全量同步和增量同步分別在首次載入架構時的 App 內存峰值。

內存峰值測試結果

分析

隨著架構的節點增多,全量同步方案的內存峰值會一直攀升,在極限情況下,會出現內存不足應用程序 crash 的情況(實際測試中,30w 節點下,iPhone 6 全量同步方案必 crash)。而增量同步方案中,總節點的多少並不會影響內存峰值,僅僅會增加同步分片的次數。

優化後,在騰訊域下,增量同步方案的 App 總內存使用僅為全量同步方案的 53.1%,且企業越大優化效果越明顯。並且不論架構的總節點數有多少,增量同步方案都能將完整架構同步下來,達到了預期的效果。

流量使用

測試方法:在管理端對成員做增加操作五次,通過日誌分析客戶端消耗流量,取其平均值。日誌會列印出請求的 header 和 body 大小並估算出流量使用值。

測試結果

分析

增加成員操作,針對增量同步方案僅僅會新拉單個成員的信息,所以無論架構里有多少人,流量消耗都是相近的。同樣的操作針對全量同步方案,每次請求變更,服務端都會下發全量 hash 列表,企業越大消耗的流量越多。可以看到,當企業的節點數達到 20w 級別時,全量同步方案的流量消耗是增量同步方案的近 500 倍。

優化後,在騰訊域下,每次增量同步流量消耗僅為全量同步方案的 0.4%,且企業越大優化效果越明顯。

寫在最後

增量同步方案從方案上避免了架構同步不及時以及流量消耗過大的問題。通過用戶反饋和數據分析,增量架構同步上線後運行穩定,達到了理想的優化效果。

作者介紹

胡騰,騰訊工程師,參與企業微信從無到有的整個過程,目前主要負責企業微信移動端組織架構和外部聯繫人等模塊的開發工作。

今日薦文

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 InfoQ 的精彩文章:

未來的App技術、當前的大前端實踐,一次都給你了
想成為搶手的數據科學家,你需要掌握這些進階技能
從大公司到創業公司,技術人轉型怎樣轉變思路與處事之道?
CTO不得不面對的9大困境

TAG:InfoQ |

您可能感興趣

系統架構、網站架構的演進變化
荔枝架構實踐與演進歷程
小程序·雲服務的系統架構和運維實現
小米調整組織架構 從戰略高度和組織保障強化技術引領
架構師必讀,實用架構的搭建思路
業務高速增長,途牛旅遊系統架構的優化實踐
開放心態 企業如何構建業務驅動的混合雲架構?
核心交易鏈路架構設計與演進
基於運行時組件化/模塊化的架構實踐
實踐—框架構圖
OFO小黃車微服務架構演進實踐
基於雙態IT架構的混合雲助力企業數字化轉型之路
大型互聯網公司微服務架構進化史
如何構建符合國內技術環境的微服務架構?
產品平台與模塊化架構規劃與實施
微服務架構技術棧
知乎調整組織架構:發展組織的戰鬥力
架構和設計領域技術演變詳解
網易考拉的服務架構如何從單體應用走向微服務化?|技術頭條
魅族組織架構調整