《企業微信組織架構同步優化的思路與實操演練》要點:
本文介紹了企業微信組織架構同步優化的思路與實操演練,希望對您有用。如果有疑問,可以聯系我們。
作者:胡騰
編輯:小智
作為企業級的微信,在業務快速發展的背景下,迭代優化的要求也越發急迫.企業微信初版的全量同步方案在快速的業務增長面前已經捉襟見肘,針對其遇到的問題,怎樣做好組織架構同步優化?這是又一篇來自微信團隊的技術實戰.
企業微信在快速發展過程中,陸續有大企業加入使用,企業微信初版采用全量同步方案,該方案在大企業下存在流量和性能兩方面的問題,每次同步消耗大量流量,且在 iPhone 5s 上拉取 10w+ 成員架構包解壓時會提示 memory warning 而應用崩潰.
全量同步方案難以支撐業務的快速發展,優化同步方案越來越有必要.本文針對全量同步方案遇到的問題進行分析,提出組織架構增量同步方案,并對移動端實現增量同步方案的思路和重難點進行了講解.
在企業微信中,組織架構是非常重要的模塊,用戶可以在首頁的 tab 上選擇”通訊錄”查看到本公司的組織架構,并且可以通過”通訊錄”找到本公司的所有成員,并與其發起會話或者視頻語音通話.
組織架構是非常重要且敏感的信息,企業微信作為企業級產品,始終把用戶隱私和安全放在重要位置.針對組織架構信息,企業管理員具有高粒度隱私保護操作權限,不僅支持個人信息隱藏,也支持通訊錄查看權限等操作.
在企業微信中,組織架構特征有:
1、多叉樹結構.葉子節點代表成員,非葉子節點代表部門.部門最多只有一個父部門,但成員可屬于多個部門.
2、架構隱藏操作.企業管理員可以在管理后臺設置白名單和黑名單,白名單可以查看完整的組織架構,其他成員在組織架構里看不到他們.黑名單的成員只能看到自己所在小組和其所有的父部門,其余人可以看到黑名單的成員.
3、組織架構操作.企業管理員可以在 web 端和 app 端添加 / 刪除部門,添加 / 刪除 / 移動 / 編輯成員等操作,并且操作結果會及時同步給本公司所有成員.
本節大致講解下全量同步方案實現以及遇到的問題.
企業微信在 1.0 時代,從穩定性以及快速迭代的角度考慮,延用了企業郵通訊錄同步方案,采取了全量架構同步方案.
核心思想為服務端下發全量節點,客戶端對比本地數據找出變更節點.此處節點可以是用戶,也可以是部門,將組織架構視為二叉樹結構體,其下的用戶與部門均為節點,若同一個用戶存在多個部門下,被視為多個節點.
全量同步方案分為首次同步與非首次同步:
初版上線后,收到了大量的組織架構相關的 bug 投訴,主要集中在:
這些問題在大企業下更明顯.
深究全量同步方案難以支撐大企業同步的背后原因,皆是因為采取了服務端全量下發 hash 值方案的原因,方案存在以下問題:
優化組織架構同步方案越來越有必要.
尋求同步方案優化點,我們要找準原來方案的痛點以及不合理的地方,通過方案的調整來避免這個問題.
準確且耗費最少資源同步組織架構是一件很困難的事情,難點主要在:
上述提到的問題,在大型企業下會變得更明顯.在幾輪方案討論后,我們給原來的方案增加了兩個特性來實現增量更新:
在新方案中,服務端針對某個節點的存儲結構可簡化為:
vid 是指節點用戶的唯一標識 id,departmentid 是指節點的部門 id,is_delete 表示該節點是否已被刪除.
其中,seq 是自增的值,可以理解成版本號.每次組織架構的節點有更新,服務端增加相應節點的 seq 值.客戶端通過一個舊的 seq 向服務器請求,服務端返回這個 seq 和 最新的 seq 之間所有的變更給客戶端,完成增量更新.
圖示為:
通過提出增量同步方案,我們從技術選型層面解決了問題,但是在實際操作中會遇到許多問題,下文中我們將針對方案原理以及實際操作中遇到的問題進行講解.
本節主要講解客戶端中增量同步架構方案的原理與實現,以及基礎概念講解.
企業微信中,增量同步方案核心思想為:
服務端下發增量節點,且支持傳閾值來分片拉取增量節點,若服務端計算不出客戶端的差量,下發全量節點由客戶端來對比差異.
增量同步方案可抽象為四步完成:
忽略掉各種邊界條件和異常狀況,增量同步方案的流程圖可以抽象為:
接下來我們再看看增量同步方案中的關鍵概念以及完整流程是怎樣的.
同步的版本號是由多個版本號拼接成的字符串,版本號的具體含義對客戶端透明,但是對服務端非常重要.
版本號的組成部分為:
增量同步在實際操作過程中會遇到一些問題:
理想狀況下,若服務端下發全量節點,客戶端鏟掉舊數據,并且去拉全量節點的信息,并且用新數據覆蓋即可.但是移動端這樣做會消耗大量的用戶流量,這樣的做法是不可接受的.所以若服務端下發全量節點,客戶端需要本地對比出增刪改節點,再去拉變更節點的具體信息.
增量同步情況下,若服務端下發全量節點,我們在本文中稱這種情況為版本號回退,效果類似于客戶端用空版本號去同步架構.從統計結果來看,線上版本的同步中有 4% 的情況會出現版本號回退.
若客戶端的傳的 seq 過舊,增量數據可能很大.此時若一次性返回全部的更新數據,客戶端請求的數據量會很大,時間會很長,成功率很低.針對這種場景,客戶端和服務端需要約定閾值,若請求的更新數據總數超過這個閾值,服務端每次最多返回不超過該閾值的數據.若客戶端發現服務端返回的數據數量等于閾值,則再次到服務端請求數據,直到服務端下發的數據數量小于閾值.
在全量同步方案中,節點通過 hash 唯一標示.服務端下發的全量 hash 列表,客戶端對比本地存儲的全量 hash 列表,若有新的 hash 值則請求節點具體信息,若有刪除的 hash 值則客戶端刪除掉該節點信息.
在全量同步方案中,客戶端并不能理解 hash 值的具體含義,并且可能遇到 hash 碰撞這種極端情況導致客戶端無法正確處理下發的 hash 列表.
而增量同步方案中,使用 protobuf 結構體代替 hash 值,增量更新中節點的 proto 定義為:
在增量同步方案中,用 vid 和 partyid 來唯一標識節點,完全廢棄了 hash 值.這樣在增量同步的時候,客戶端完全理解了節點的具體含義,而且也從方案上避免了曾經在全量同步方案遇到的 hash 值重復的異常情況.
并且在節點結構體里帶上了 seq .節點上的 seq 來表示該節點的版本,每次節點的具體信息有更新,服務端會提高節點的 seq,客戶端發現服務端下發的節點 seq 比客戶端本地的 seq 大,則需要去請求節點的具體信息,避免無效的節點信息請求.
因為 svr 接口支持傳閾值批量拉取變更節點,一次網絡操作并不意味著架構同步已經完成.那么怎么判斷架構同步完成了呢?這里客戶端和服務端約定的方案是:
若服務端下發的(新增節點+刪除節點)小于客戶端傳的閾值,則認為架構同步結束.
當完整架構同步完成后,客戶端需要清除掉緩存,并進行一些額外的業務工作,譬如計算部門人數,計算成員搜索熱度等.
增量同步方案 – 完整流程圖
考慮到各種邊界條件和異常情況,增量同步方案的完整流程圖為:
在加入增量和分片特性后,針對幾十萬人的超大企業,在版本號回退的場景,怎樣保證架構同步的完整性和方案選擇成為了難點.
前文提到,隱藏規則變更以及后臺物理刪除無效節點后,客戶端若用很舊的版本同步,服務端算不出增量節點,此時服務端會下發全量節點,客戶端需要本地對比所有數據找出變更節點,該場景可以理解為版本號回退.在這種場景下,對于幾十萬節點的超大型企業,若服務端下發的增量節點過多,客戶端請求的時間會很長,成功率會很低,因此需要分片拉取增量節點.而且拉取下來的全量節點,客戶端處理不能請求全量節點的具體信息覆蓋舊數據,這樣的話每次版本號回退的場景流量消耗過大.
因此,針對幾十萬節點的超大型企業的增量同步,客戶端難點在于:
若服務端下發了全量節點,客戶端的處理時序圖為:
從時序圖中可以看出,服務端下發的版本號回退標記是很重要的信號.
而版本號回退這個標記,僅僅在同步的首次會隨著新的版本號而下發.在完整架構同步期間,客戶端需要將該標記緩存,并且跟著版本號一起存在數據庫中.在完整架構同步結束后,需要根據是否版本號回退來決定刪除掉數據庫中的待刪除節點.
架構樹備份最直接的方案是將 db 中數據 copy 一份,并存在新表里.如果在數據量很小的情況下,這樣做是完全沒有問題的,但是架構樹的節點往往很多,采取這樣簡單粗暴的方案在移動端是完全不可取的,在幾十萬人的企業里,這樣做會造成極大的性能問題.
經過考慮后,企業微信采取的方案是:
而且,在增量同步過程中,不應該影響正常的架構樹展示.所以在架構同步過程中,若有上層來請求 db 中的數據,則需要過濾掉有待刪除標記的節點.
方案決定客戶端避免不了全量節點對比,將重要的信息緩存到內存中會大大加快處理速度.內存中的架構樹節點體定義為:
此處我們用 std::map 來緩存架構樹,用 std::pair 作為 key.我們在比較節點的時候,會涉及到很多查詢操作,使用 map 查詢的時間復雜度僅為 O(logn).
本節單獨將優化同步方案中關鍵點拿出來寫,這些關鍵點不僅僅適用于本文架構同步,也適用于大多數同步邏輯.
保證數據處理完成后,再儲存版本號
在幾乎所有的同步中,版本號都是重中之重,一旦版本號亂掉,后果非常嚴重.
在架構同步中,最最重要的一點是:
保證數據處理完成后,再儲存版本號
在組織架構同步的場景下,為什么不能先存版本號,再存數據呢?
這涉及到組織架構同步數據的一個重要特征:架構節點數據是可重復拉取并覆蓋的.
考慮下實際操作中遇到的真實場景:
若一旦先存版本號再存具體數據,一定會有概率丟失架構更新數據.
正常情況下,一次同步的邏輯可以簡化為:
在企業微信的組織架構同步中存在異步操作,若進行同步的過程不保證原子性,極大可能出現下圖所示的情況:
該圖中,同步的途中插入了另外一次同步,很容易造成問題:
怎樣保證同步的原子性呢?
我們可以在開始同步的時候記一個 flag 表示正在同步,在結束同步時,清除掉該 flag.若另外一次同步到來時,發現正在同步,則可以直接舍棄掉本次同步,或者等本次同步成功后再進行一次同步.
此外也可將同步串行化,保證同步的時序,多次同步的時序應該是 FIFO 的.
移動端同步過程中的緩存多分為兩種:
內存緩存多緩存同步時的數據以及同步的中間狀態,磁盤緩存用于緩存同步的中間狀態防止緩存狀態丟失.
在整個同步過程中,我們都必須保證緩存中的數據和數據庫的數據的更改需要一一對應.在增量同步的情況中,我們每次需要更新 / 刪除數據庫中的節點,都需要更新相應的緩存信息,來保證數據的一致性.
測試方法:使用工具 Instrument,用同一賬號監控全量同步和增量同步分別在首次加載架構時的 App 內存峰值.
內存峰值測試結果
分析
隨著架構的節點增多,全量同步方案的內存峰值會一直攀升,在極限情況下,會出現內存不足應用程序 crash 的情況(實際測試中,30w 節點下,iPhone 6 全量同步方案必 crash).而增量同步方案中,總節點的多少并不會影響內存峰值,僅僅會增加同步分片的次數.
優化后,在騰訊域下,增量同步方案的 App 總內存使用僅為全量同步方案的 53.1%,且企業越大優化效果越明顯.并且不論架構的總節點數有多少,增量同步方案都能將完整架構同步下來,達到了預期的效果.
測試方法:在管理端對成員做增加操作五次,通過日志分析客戶端消耗流量,取其平均值.日志會打印出請求的 header 和 body 大小并估算出流量使用值.
測試結果
分析
增加成員操作,針對增量同步方案僅僅會新拉單個成員的信息,所以無論架構里有多少人,流量消耗都是相近的.同樣的操作針對全量同步方案,每次請求變更,服務端都會下發全量 hash 列表,企業越大消耗的流量越多.可以看到,當企業的節點數達到 20w 級別時,全量同步方案的流量消耗是增量同步方案的近 500 倍.
優化后,在騰訊域下,每次增量同步流量消耗僅為全量同步方案的 0.4%,且企業越大優化效果越明顯.
增量同步方案從方案上避免了架構同步不及時以及流量消耗過大的問題.通過用戶反饋和數據分析,增量架構同步上線后運行穩定,達到了理想的優化效果.
胡騰,騰訊工程師,參與企業微信從無到有的整個過程,目前主要負責企業微信移動端組織架構和外部聯系人等模塊的開發工作.
文章來自微信公眾號:InfoQ
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/3742.html