作者:易芝林(維駿)
語雀桌面端作為語雀為用戶提供的生產力工具,上線兩年多來一直保持高頻的迭代和健康的業(yè)務增長。本次主要介紹我們在做桌面端時的一些技術架構思考和實踐,同時也將分享我們沉淀的一些通用桌面應用解決方案和經驗。
文章會分為四部分,首先會簡單介紹語雀桌面端,然后介紹當前語雀桌面端的應用架構以及關鍵點,之后介紹架構中的幾個架構重點項,最后在進行總結。
語雀桌面端介紹
語雀是孵化自螞蟻體驗技術部的一款筆記與文檔知識庫工具。我們在兩年前,針對語雀用戶特點,以及后續(xù)發(fā)展策略,旨在為創(chuàng)作者提供更好的創(chuàng)作體驗,推出語雀桌面客戶端。
相較于現有瀏覽器提供的產品服務而言,我們提供的桌面端產品主要考慮以下幾點:
- 無干擾 :給用戶一個沉浸式的創(chuàng)作體驗,而不像瀏覽器有其他窗口、tab 進行干擾,以及用完即走的用戶心智。
- 系統(tǒng)級常駐 :打開速度更快,可以一鍵啟動或者利用各類快捷工具喚起。
- 集成更多操作系統(tǒng)能力 :提升創(chuàng)作效率的多窗口、系統(tǒng)菜單和快捷鍵、對文件讀寫、與系統(tǒng)軟件集成等。
- 離線 :期望能在離線或弱網的情況下,無障礙的進行創(chuàng)作。
桌面端架構概覽
研發(fā)測主要分為左邊三層,最底層是語雀的基礎設施,依賴了語雀后臺提供或封裝的大量云服務,以及底層依賴的安全能力和存儲模塊。
中間一層是比較偏應用架構的一些能力封裝,上面是代碼層面用的的輔助能力,還有主進程的模塊,然后有給應用提供的一些管理能力和一些跟 UI 相關的功能模塊,最上層就是基于底層架構搭建的業(yè)務應用。包括桌面端應用比較核心的幾個模塊,以及一些由子應用方式承接的業(yè)務模塊(后面也會詳細介紹子應用這個概念)
同時最右側也有很多輔助研發(fā)的依賴能力,來完成語雀桌面端的發(fā)布、質量和穩(wěn)定性管理。
架構概覽 - 關鍵點
相比較普通 web 應用來說,我們覺得桌面端有以下幾個能力比較重要:包括安全、軟件升級、以及桌面端通用的的基礎能力:
架構概覽 - 安全
安全是一個生產力工具軟件的生命線,特別是語雀作為一款知識管理工具,對于安全是非常看重。
基礎安全
- 下載安裝包時,需要有安全管理機制,避免下載過程中被惡意替換;
- 升級到最新的 Electron 版本(語雀目前緊跟官方大版本,同時也會參考微軟的頭部應用 (vscode),避免有沒考慮到的場景;
- 同時用戶離線下載到本地的文件,包括圖片,附件等,也需要經過加密。
- 殺毒軟件:啟動安全主要是在啟動軟件時的一些安全問題,例如二進制模塊是否有加簽名,避免被殺毒軟件查殺導致無法啟動,也可以聯(lián)系安全廠商加白名單,同時還能提升啟動速度。
- 禁止調試:因為軟件代碼都會下載到客戶端,可以禁止軟件在客戶端瀏覽器進行調試。
- 數據庫秘鑰管理:本地數據庫文件需要保證即使被惡意拿到,也要保證無法查看到里面的內容。我們通過生成內存安全級別的方案,確保其他非當前電腦的語雀軟件即使拿到數據庫文件后也無法打開。
- 渲染容器設置白名單來控制不被引入惡意頁面;
- 渲染進程關閉 Node 功能以及開啟隔離模式,避免渲染進程權限過高;
- Electron 本身是 web 開發(fā)模式,所以 web 中遇到的安全問題,在 Electron 同樣會遇到,可以統(tǒng)一處理。
客戶端軟件相比較于 web 來說,還有一個非常大的區(qū)別就是有功能更新時,有一個升級過程,不像 web 直接刷新頁面即可。語雀桌面端作為迭代迅速的產品,對于升級這塊也是踩了不少坑。
語雀桌面端由兩大部分組成:包括 Electron 和 Node.js 等基礎模塊的軟件包 + 以及自己的業(yè)務代碼。
mac OS:Mac 下的升級流程比較簡單,軟件下載完成后,利用 hdiutil來模擬用戶手動安裝流程,用戶重啟即可完成安裝。
windows:Windows 下因為環(huán)境特殊性,需要下載安裝包后,通過主進程自動打開安裝界面,引導用戶進行手動一步一步安裝。
其實這種方案很好的滿足的我們早期的功能迭代,但是隨著用戶量上漲,也遇到了很多問題。
比如:
- 每次升級帶寬消耗巨大:對于每次安裝每個 UV 都有近 100M 的下載,每次推送版本時,都會遇到 OSS 流量告警,這背后都對應著成本;
- 安裝體驗差:Windows 下因為每次升級幾乎都是一次新的安裝流程,所以體驗也是比較差的,經常收到用戶吐槽。
所以我們就調研了一種增量更新的方案,一個 Electron 程序包括 Electron 核心包以及業(yè)務代碼,其實每次變更的僅僅是業(yè)務代碼,所以理論上每次更新只需要增量更新業(yè)務代碼即可。
Mac 下增量直接下載到增量代碼后,替換掉即可。
Windows 下比較復雜,我們主要遇到兩個問題:
- 文件占用問題:由于 Windows 系統(tǒng)特性,如果某個文件在使用,會無法刪除。所以說如果要替換,肯定關閉程序,然后進行刪除操作后再啟動。所以我們寫了一個 .exe 可執(zhí)行文件來做關閉程序、更新文件、啟動語雀。
- UAB 權限控制:文件寫入另一個問題是 C 盤文件一般是需要授權才可以操作的。我們軟件啟動沒辦法拿到這么高權限。不過還在 Windows 7 及以后新增了一個 PowerShell 功能,通過這個功能執(zhí)行,能引導用戶授權,拿到更高級的權限。
當然過程中還碰到不少細節(jié)問題,比如替換過程中路徑中英文問題、用戶自定義過環(huán)境變量位置問題等等
架構概覽 - 基礎能力沉淀
另外我們在做軟件的過程中,也沉淀了一些與業(yè)務無耦合的組件:
- 多窗口管理:當給用戶提供提供更方便的多窗口編輯能力時,如何去管理這些窗口打開,關閉,性能監(jiān)控等;
- Webview:不同編輯器以及子應用都是通過 Webview 來承載的,需要有一個通用的模塊來維護系統(tǒng)中用到的各個 webview 的生命周期等;
- 離線在線:電腦離線和在線狀態(tài)獲取,雖然瀏覽器有提供這個狀態(tài)的獲取和事件監(jiān)聽,但是 Windows 下不太準確。我們封裝了一個比較通用的模塊。
從架構上來說,相較于用了多酷炫的技術,更重要的是研發(fā)交付效率高不高,性能怎么樣,穩(wěn)定性高不高。我們認為以下三點是架構好壞評判的重要標準。
架構重點 - 交付效率
從桌面端功能上來說,包括編輯器在內,有超過 60% 的功能模塊都是與 web 一致的,所以開始是用到了同構的方案。
同構過程中的一些經驗:
- 通用代碼移動到 Common 目錄:語雀代碼倉庫是 monorepo模式,如果沒有很清晰的目錄劃分,很容易造成跨端兼容問題。有了這個約定以后,業(yè)務研發(fā)同學就會注意到這個會用在移動端或者桌面端
- 通過 webpack alias 適配多端:這個方式比較常見,比如各端有不同的網絡請求庫,在組件層面使用 adapter/request。webpack 在構建時,adapter 映射到不同的端實現。
- 定義多端代碼研發(fā)規(guī)范:梳理出不同端的一些差異點,在研發(fā)時避免采坑。
雖然同構模式可以解決我們當時遇到的一些問題,但是隨著業(yè)務規(guī)模和功能增加,陸續(xù)有些問題暴露出來:
- 遷移到 Common 目錄,各種依賴問題:很多功能在桌面端之前已經上線,有些復雜組件遷移到 Common 下本身也需要耗費不少時間。
- 缺少動態(tài)化能力,迭代滯后(web 與桌面端功能不一致):組件在 web 發(fā)布上線后,桌面端需要發(fā)布才支持,所以經常會碰到 web 和桌面端不一致的吐槽。
- 容易出現多端兼容問題(環(huán)境依賴等):雖然我們定義了一些規(guī)范,但很難徹底避免出現環(huán)境依賴問題。
- 缺少獨立沙箱,容易影響主應用(內存泄露、樣式) :web 可能用完即走,可以刷新等,不太容易遇到問題,但是桌面端因為是常駐的,如果有些內存泄露或者樣式污染問題,就直接影響到整體可用性。
考慮到上述原因,我們將代碼復用架構升級成子應用模式,利用桌面端容器,嵌套一個 html 在線或者離線頁面。
簡單來說,子應用模式可以理解為支付寶九宮格進去的各個小程序模塊。
- 快速迭代:提供獨立的發(fā)布迭代能力,所以無需跟隨桌面端整體發(fā)布。而且直接由業(yè)務同學跟進整個交付流程,無需桌面端同學參與,提升交付效率。
- 具備端相關能力:每個子應用默認就能使用桌面端提供的 JSBridge 能力,天然能做到跟桌面端模塊一樣的能力。
- 獨立沙箱:獨立于桌面端主窗口,利用桌面端的容器來完成渲染,所以能完全做到進程級別隔離,相互之間的內存開銷一目了然,更好的做到管控,保證整體應用穩(wěn)定性。
- 加載初始化:除了上述的一些優(yōu)勢,也會到來一些問題,比如加載速度慢等,我們通過 webview 預熱、緩存等方式,一定程度上解決了這個問題。
性能是一個桌面端軟件必須要面對的問題,是需要持續(xù)在不同角度進行優(yōu)化的
主要包括這幾個方面:
- 啟動速度優(yōu)化:啟動速度是用戶第一映像,我們主要是將主進程代碼進行緩存,盡早展示 loading 避免白屏,主窗口和渲染進程的部分任務同時執(zhí)行,達到并發(fā)效果;
- 主進程優(yōu)化:主進程和渲染進程執(zhí)行是同步的,如果主進程做了太多任務,會導致用戶使用起來卡頓,甚至閃退。所以盡量減少主進程所做的事情;
- Web 優(yōu)化:同時,之前很多在 web 上做的優(yōu)化,一樣可以拿過來使用。例如懶加載,合并模塊等(組合多個模塊本身也有開銷);
- Webview 優(yōu)化:例如預熱 webview、并進行一些性能管控措施,避免失控。
性能優(yōu)化其實不是說做完哪些事情就可以徹底解決,而是一個長期過程。可能我們后續(xù)新增功能時,代碼里有某一個內存泄露問題,就很容易導致性能拉胯下來。所以我們也建立了一些持續(xù)性的機制:
主要包括:
- 日常觀測:在開發(fā)模式下,建立觀測性能指標能力,做到心中有數;
- 自動化任務:日常也會有自動化任務,模擬真實用戶長時間使用,及時發(fā)現內存泄露等問題;
- 性能大盤:對于線上性能水位,能有一個全盤的感知能力,灰度發(fā)布過程中可重點關注。
相比較于 web 而言,桌面端的穩(wěn)定性也是要求更高的。
從研發(fā)流程上看,我們主要有兩塊事情:
- 單測、集成測試:利用代碼測試來輔助整體穩(wěn)定性
- UIA:通過模擬用戶行為的 UIA 自動化測試回歸來提升穩(wěn)定性,及時發(fā)現異常。
注:UIA 是語雀工程師自研的自動化方案,詳見 :Macaca MacOS:https://github.com/macacajs/macaca-macos
另外建立了穩(wěn)定性大盤和實時告警,來感知到線上性能情況。
為了保證每次發(fā)布的穩(wěn)定性,減少回歸成本,我們利用每周一次預覽版發(fā)布的敏捷研發(fā)模式,來分解大版本發(fā)布帶來的集成風險。
總結
- 針對當前的業(yè)務體量和團隊經驗,選擇合適的技術架構;
-
- 現在肯定有比 Electron 更新的桌面端架構,比如 flutter、tauri 等,要綜合看比如團隊積累以及穩(wěn)定性,是否有成熟的商業(yè)化產品等;
- 性能和穩(wěn)定性優(yōu)化是持續(xù)性的過程,先建立度量和感知;
- 交付效率和交付質量最容易被忽視,但卻是架構方案的重要考量;
-
- 架構好壞的評判標準一定是由業(yè)務效果決定的,交付效率和交付質量是衡量業(yè)務效果的手段之一。