語雀桌面端技術架構實踐

語言: CN / TW / HK

語雀桌面端作為語雀為使用者提供的生產力工具,上線兩年多來一直保持高頻的迭代和健康的業務增長。本次主要介紹我們在做桌面端時的一些技術架構思考和實踐,同時也將分享我們沉澱的一些通用桌面應用解決方案和經驗。

文章會分為四部分,首先會簡單介紹語雀桌面端,然後介紹當前語雀桌面端的應用架構以及關鍵點,之後介紹架構中的幾個架構重點項,最後在進行總結。

語雀桌面端介紹

語雀是孵化自螞蟻體驗技術部的一款筆記與文件知識庫工具。我們在兩年前,針對語雀使用者特點,以及後續發展策略,旨在為創作者提供更好的創作體驗,推出語雀桌面客戶端。

相較於現有瀏覽器提供的產品服務而言,我們提供的桌面端產品主要考慮以下幾點:

  • 無干擾:給使用者一個沉浸式的創作體驗,而不像瀏覽器有其他視窗、tab 進行干擾,以及用完即走的使用者心智。

  • 系統級常駐:開啟速度更快,可以一鍵啟動或者利用各類快捷工具喚起。

  • 整合更多作業系統能力:提升創作效率的多視窗、系統選單和快捷鍵、對檔案讀寫、與系統軟體整合等。

  • 離線:期望能在離線或弱網的情況下,無障礙的進行創作。

桌面端架構概覽

研發測主要分為左邊三層,最底層是語雀的基礎設施,依賴了語雀後臺提供或封裝的大量雲服務,以及底層依賴的安全能力和儲存模組。

中間一層是比較偏應用架構的一些能力封裝,上面是程式碼層面用的的輔助能力,還有主程序的模組,然後有給應用提供的一些管理能力和一些跟 UI 相關的功能模組,最上層就是基於底層架構搭建的業務應用。包括桌面端應用比較核心的幾個模組,以及一些由子應用方式承接的業務模組(後面也會詳細介紹子應用這個概念)

同時最右側也有很多輔助研發的依賴能力,來完成語雀桌面端的釋出、質量和穩定性管理。

架構概覽 - 關鍵點

相比較普通 web 應用來說,我們覺得桌面端有以下幾個能力比較重要:包括安全、軟體升級、以及桌面端通用的的基礎能力:

架構概覽 - 安全

安全是一個生產力工具軟體的生命線,特別是語雀作為一款知識管理工具,對於安全是非常看重。

基礎安全

  • 下載安裝包時,需要有安全管理機制,避免下載過程中被惡意替換;

  • 升級到最新的 Electron 版本(語雀目前緊跟官方大版本,同時也會參考微軟的頭部應用 (vscode),避免有沒考慮到的場景;

  • 同時使用者離線下載到本地的檔案,包括圖片,附件等,也需要經過加密。

啟動安全

  • 防毒軟體:啟動安全主要是在啟動軟體時的一些安全問題,例如二進位制模組是否有加簽名,避免被防毒軟體查殺導致無法啟動,也可以聯絡安全廠商加白名單,同時還能提升啟動速度。

  • 禁止除錯:因為軟體程式碼都會下載到客戶端,可以禁止軟體在客戶端瀏覽器進行除錯。

  • 資料庫祕鑰管理:本地資料庫檔案需要保證即使被惡意拿到,也要保證無法檢視到裡面的內容。我們通過生成記憶體安全級別的方案,確保其他非當前電腦的語雀軟體即使拿到資料庫檔案後也無法開啟。

應用安全

  • 渲染容器設定白名單來控制不被引入惡意頁面;

  • 渲染程序關閉 Node 功能以及開啟隔離模式,避免渲染程序許可權過高;

  • Electron 本身是 web 開發模式,所以 web 中遇到的安全問題,在 Electron 同樣會遇到,可以統一處理。

架構概覽 - 軟體升級

客戶端軟體相比較於 web 來說,還有一個非常大的區別就是有功能更新時,有一個升級過程,不像 web 直接重新整理頁面即可。語雀桌面端作為迭代迅速的產品,對於升級這塊也是踩了不少坑。

語雀桌面端由兩大部分組成:包括 Electron 和 Node.js 等基礎模組的軟體包 + 以及自己的業務程式碼。

Mac OS:Mac 下的升級流程比較簡單,軟體下載完成後,利用 hdiutil來模擬使用者手動安裝流程,使用者重啟即可完成安裝。

Windows:Windows 下因為環境特殊性,需要下載安裝包後,通過主程序自動開啟安裝介面,引導使用者進行手動一步一步安裝。

其實這種方案很好的滿足的我們早期的功能迭代,但是隨著使用者量上漲,也遇到了很多問題。

比如:

  • 每次升級頻寬消耗巨大:對於每次安裝每個 UV 都有近 100M 的下載,每次推送版本時,都會遇到 OSS 流量告警,這背後都對應著成本;

  • 安裝體驗差:Windows 下因為每次升級幾乎都是一次新的安裝流程,所以體驗也是比較差的,經常收到使用者吐槽。

所以我們就調研了一種增量更新的方案,一個 Electron 程式包括 Electron 核心包以及業務程式碼,其實每次變更的僅僅是業務程式碼,所以理論上每次更新只需要增量更新業務程式碼即可。

Mac 下增量直接下載到增量程式碼後,替換掉即可。

Windows 下比較複雜,我們主要遇到兩個問題:

  1. 檔案佔用問題:由於 Windows 系統特性,如果某個檔案在使用,會無法刪除。所以說如果要替換,肯定關閉程式,然後進行刪除操作後再啟動。所以我們寫了一個  .exe 可執行檔案來做關閉程式、更新檔案、啟動語雀。

  2. UAB 許可權控制:檔案寫入另一個問題是 C 盤檔案一般是需要授權才可以操作的。我們軟體啟動沒辦法拿到這麼高許可權。不過還在 Windows 7 及以後新增了一個 PowerShell 功能,通過這個功能執行,能引導使用者授權,拿到更高階的許可權。

當然過程中還碰到不少細節問題,比如替換過程中路徑中英文問題、使用者自定義過環境變數位置問題等等

架構概覽 - 基礎能力沉澱

另外我們在做軟體的過程中,也沉澱了一些與業務無耦合的元件:

  • 多視窗管理:當給使用者提供提供更方便的多視窗編輯能力時,如何去管理這些視窗開啟,關閉,效能監控等;

  • Webview:不同編輯器以及子應用都是通過 Webview 來承載的,需要有一個通用的模組來維護系統中用到的各個 webview 的生命週期等;

  • 離線線上:電腦離線和線上狀態獲取,雖然瀏覽器有提供這個狀態的獲取和事件監聽,但是 Windows 下不太準確。我們封裝了一個比較通用的模組。

桌面端架構重點

從架構上來說,相較於用了多酷炫的技術,更重要的是研發交付效率高不高,效能怎麼樣,穩定性高不高。我們認為以下三點是架構好壞評判的重要標準。

架構重點 - 交付效率

從桌面端功能上來說,包括編輯器在內,有超過 60% 的功能模組都是與 web 一致的,所以開始是用到了同構的方案。

同構過程中的一些經驗:

  • 通用程式碼移動到 Common 目錄:語雀程式碼倉庫是 monorepo模式,如果沒有很清晰的目錄劃分,很容易造成跨端相容問題。有了這個約定以後,業務研發同學就會注意到這個會用在移動端或者桌面端

  • 通過 webpack alias 適配多端:這個方式比較常見,比如各端有不同的網路請求庫,在元件層面使用 adapter/request。webpack 在構建時,adapter 對映到不同的端實現。

  • 定義多端程式碼研發規範:梳理出不同端的一些差異點,在研發時避免採坑。

交付效率(同構問題)

雖然同構模式可以解決我們當時遇到的一些問題,但是隨著業務規模和功能增加,陸續有些問題暴露出來:

  • 遷移到 Common 目錄,各種依賴問題:很多功能在桌面端之前已經上線,有些複雜元件遷移到 Common 下本身也需要耗費不少時間。

  • 缺少動態化能力,迭代滯後(web 與桌面端功能不一致):元件在 web 釋出上線後,桌面端需要釋出才支援,所以經常會碰到 web 和桌面端不一致的吐槽。

  • 容易出現多端相容問題(環境依賴等):雖然我們定義了一些規範,但很難徹底避免出現環境依賴問題。

  • 缺少獨立沙箱,容易影響主應用(記憶體洩露、樣式):web 可能用完即走,可以重新整理等,不太容易遇到問題,但是桌面端因為是常駐的,如果有些記憶體洩露或者樣式汙染問題,就直接影響到整體可用性。

交付效率(子應用)

考慮到上述原因,我們將程式碼複用架構升級成子應用模式,利用桌面端容器,巢狀一個 html 線上或者離線頁面。

簡單來說,子應用模式可以理解為支付寶九宮格進去的各個小程式模組。

  • 快速迭代:提供獨立的釋出迭代能力,所以無需跟隨桌面端整體釋出。而且直接由業務同學跟進整個交付流程,無需桌面端同學參與,提升交付效率。

  • 具備端相關能力:每個子應用預設就能使用桌面端提供的 JSBridge 能力,天然能做到跟桌面端模組一樣的能力。

  • 獨立沙箱:獨立於桌面端主視窗,利用桌面端的容器來完成渲染,所以能完全做到程序級別隔離,相互之間的記憶體開銷一目瞭然,更好的做到管控,保證整體應用穩定性。

  • 載入初始化:除了上述的一些優勢,也會到來一些問題,比如載入速度慢等,我們通過 webview 預熱、快取等方式,一定程度上解決了這個問題。

架構重點 - 效能

效能是一個桌面端軟體必須要面對的問題,是需要持續在不同角度進行優化的

主要包括這幾個方面:

  • 啟動速度優化:啟動速度是使用者第一映像,我們主要是將主程序程式碼進行快取,儘早展示 loading 避免白屏,主視窗和渲染程序的部分任務同時執行,達到併發效果;

  • 主程序優化:主程序和渲染程序執行是同步的,如果主程序做了太多工,會導致使用者使用起來卡頓,甚至閃退。所以儘量減少主程序所做的事情;

  • Web 優化:同時,之前很多在 web 上做的優化,一樣可以拿過來使用。例如懶載入,合併模組等(組合多個模組本身也有開銷);

  • Webview 優化:例如預熱 webview、並進行一些效能管控措施,避免失控。

效能(持續性任務)

效能優化其實不是說做完哪些事情就可以徹底解決,而是一個長期過程。可能我們後續新增功能時,程式碼裡有某一個記憶體洩露問題,就很容易導致效能拉胯下來。所以我們也建立了一些持續性的機制:

主要包括:

  • 日常觀測:在開發模式下,建立觀測效能指標能力,做到心中有數;

  • 自動化任務:日常也會有自動化任務,模擬真實使用者長時間使用,及時發現記憶體洩露等問題;

  • 效能大盤:對於線上效能水位,能有一個全盤的感知能力,灰度釋出過程中可重點關注。

架構重點 - 穩定性

相比較於 web 而言,桌面端的穩定性也是要求更高的。

從研發流程上看,我們主要有兩塊事情:

  1. 單測、整合測試:利用程式碼測試來輔助整體穩定性

  2. UIA:通過模擬使用者行為的 UIA 自動化測試迴歸來提升穩定性,及時發現異常。

注:UIA 是語雀工程師自研的自動化方案,詳見 :Macaca MacOS:https://github.com/macacajs/macaca-macos

另外建立了穩定性大盤和實時告警,來感知到線上效能情況。

為了保證每次釋出的穩定性,減少迴歸成本,我們利用每週一次預覽版釋出的敏捷研發模式,來分解大版本釋出帶來的整合風險。

總結

  • 針對當前的業務體量和團隊經驗,選擇合適的技術架構;

    • 現在肯定有比 Electron 更新的桌面端架構,比如 flutter、tauri 等,要綜合看比如團隊積累以及穩定性,是否有成熟的商業化產品等;

  • 效能和穩定性優化是持續性的過程,先建立度量和感知;

  • 交付效率和交付質量最容易被忽視,但卻是架構方案的重要考量;

    • 架構好壞的評判標準一定是由業務效果決定的,交付效率和交付質量是衡量業務效果的手段之一。