每日最新頭條.有趣資訊

支付寶首次披露其小程序技術架構

作者 | 白招拒(支付寶小程序首席架構師)

編輯 | 覃雲

在輕應用混戰的當下,小程序已經成為巨頭們角逐的焦點,阿里自然也不甘落後。據阿里官方的數據,截止到今年 1 月 28 日為止,支付寶小程序應用數已經達到 12 萬,總用戶數突破 5 億,日活躍用戶數突破 2.3 億,用戶通過支付寶首頁下拉入口進入小程序的日人均打開次數為 4 次,支付寶小程序也因此被稱為“螞蟻金服未來三年最重要的戰略之一”。InfoQ 採訪了支付寶小程序首席架構師白招拒,為大家解讀支付寶小程序的技術架構和開發特點,以下是採訪的全部內容。

支付寶小程序從 2016 年開始立項算起,到現在也快 3 年的時間,在這 3 年的過程中,小程序的技術架構也是不斷的升級和演進,在滿足業務發展的同時對於小程序整體的高可用、性能優化、多端輸出方面做了大量的工作。今天給大家分享下我們在支付寶小程序技術這塊所做的一些工作。

小程序技術架構主要分成四個方面來講:

系統架構,主要給大家說下小程序的架構,以及其中的一些關鍵技術;

性能體驗,講下我們在性能體驗這塊做的幾個 case;

開發者工具,怎麽更好的幫助開發者開發和管理小程序,和保障線上小程序的質量;

多端 inside,將支付寶小程序的技術輸出給集團和外部的商戶,讓他們具備運行小程序的能力。

系統架構

支付寶小程序不是從零開始建設的一個產品,而是依托於螞蟻技術部多年來的技術沉澱,再結合小程序的業務場景,逐步的發展起來的。

以上是支付寶小程序架構的示意圖,最上面是支付寶錢包提供的主要的七個場景入口,開發者可以根據自己的業務場景運營這些場景入口,把這些入口的流量充分利用起來。中間框內的是小程序的核心引擎,上面是對開發者提供的基礎組件和基礎 API 能力,開發者根據這些組件和 API 來開發自己的小程序,滿足用戶的需求。

小程序前端框架這塊借鑒了主流前端框架 React 的設計思路,從小程序的應用形態,提供了簡潔的編程模型,定義了一套組件和 API 接口的規範,降低了學習門檻,方便開發者快速開發小程序。在小程序框架內部提供了小程序的生命周期管理,通過事件的方式把小程序每個階段都注入到小程序裡面,開發者可以通過這些事件來處理小程序每個階段需要完成的業務邏輯。同時框架內部使用了虛擬 DOM 來處理頁面的每次更新,提升了頁面的渲染性能。

前端框架下面是小程序 native 引擎,包括了小程序容器、渲染引擎和 JavaScript 引擎,這塊主要是把客戶端 native 的能力和前端框架結合起來,給開發者提供系統底層能力的接口。在渲染引擎上面,支付寶小程序不僅提供 JavaScript+Webview 的方式,還提供 JavaScript+Native 的方式,在對性能要求較高的場景,可以選擇 Native 的渲染模式,給用戶更好的體驗。

示意圖左邊和右邊分別是面對開發者提供的研發支撐和運維支撐服務,可以幫助開發者更有效率的開發小程序,在上線後也提供眾多的工具幫助開發者管理和運營線上的小程序。

運行時架構

小程序編程模型是分為多個頁面,每個頁面有自己的 template、CSS 和 JS,實際在運行的時候,業務邏輯的 JS 代碼是運行在獨立的 JavaScript 引擎中,每個頁面的 template 和 CSS 是運行在各自獨立的 webview 裡面,頁面之間是通過函數 navigateTo 進行頁面的切換。

每個 webview 裡面的頁面和公共的 JavaScript 引擎裡面的邏輯的互動方式是通過消息服務,頁面的一些事件都會通過這個消息通道傳給 JavaScript 引擎運行環境,這個運行環境會響應這個事件,做一些 API 調用,可調到客戶端支付寶小程序提供的一些能力,處理之後會把這個數據再重新發送給對應的頁面渲染容器來處理,把數據和模板結合在一起來,在產生最終的用戶界面。

瀏覽器內核

小程序在 web 上的渲染引擎是瀏覽器內核,作為小程序的核心組件,經過多方面的考慮,我們採用的是 UC 提供的瀏覽器內核,UC 的同學在瀏覽器內核的性能、穩定性和兼容性上做了大量的工作,比系統提供的 webview 提升了不少。

穩定性:crash 率只有系統 webview 的三分之一到五分之一;

兼容性:不存在各種系統 webview 上的兼容性問題;

性能:針對內核啟動邏輯,v8 引擎 codecache 深度優化,使得 js 代碼解析和編譯的時間減少 40% 左右;

工具:提供了豐富的工具保障 UC 內核的穩定性和性能;

下圖是 UC 內核的穩定性保障體系:

同時 UC 內核針對記憶體做了大量的優化,主要分為幾方面:

圖片記憶體:針對低端機,做了更嚴格的圖片緩存限制,在保持性能體驗的情況下,進一步限制圖片緩存的使用;多個 webview 共用圖片緩存池;全面支持 webp、apng 這種更節省記憶體和 size 的圖片格式。

渲染記憶體:Webview 在不可見的狀態下,原生的記憶體管理沒有特殊處理,UC 內核會將不可見 webview 的渲染記憶體釋放;渲染記憶體的合理設置與調優,避免滾動性能的下降和佔用過多記憶體。

JS 記憶體:更合理地處理 v8 記憶體 gc,在啟動時延時執行 full gc,避免影響啟動的耗時。

峰值記憶體管理:系統在記憶體緊張時,會通知內核,UC 內核能夠在系統低記憶體時釋放非關鍵記憶體佔用的模塊,避免出現 oom,也避免過度釋放帶來的渲染黑塊;在部分 oom 的情況,規避原生內核主動崩潰的邏輯,在記憶體極低的情況,部分功能不可用,而不是崩潰。

性能體驗

Google 的統計表明,頁面打開時間超過 3 秒用戶會流失 13%,超過 6 秒用戶會流失 60%。反過來,打開時間每減少 1 秒可提升 27% 的轉化率,給用戶帶來更好的用戶體驗一直是支付寶努力在做的事情。

支付寶 app 不同於社交類的 app,屬於低頻類的應用,所以在小程序的優化方式上會不同於高頻的應用,由於高頻的應用長期在系統層面是活躍的狀態,所以高效的優化方式就是預加載,在後台把小程序相關的資源盡可能的提前加載好,在用戶使用小程序時可以快速的啟動起來。

而對於低頻應用,更多的是冷啟動,所以在這種情況下,我們更多的是從技術的角度來優化每一個環節的性能,在小程序用戶體驗上可以達到高頻應用,下面我會分享幾個我們性能優化方面的工作。

render 和 worker 互動優化

為了優化小程序的互動體驗,目前傳統的做法是把 render 層和 woker 層在兩個不同的線程裡面執行,可以讓頁面在渲染的時候不會因為業務邏輯的執行而產生卡頓,提升了渲染的速度。

通常的做法是在 webview 裡面運行 render 的代碼,然後另起一個線程運行 serviceworker,當 serviceworker 需要更新 dom 的時候把事件和數據通過 messagechannel 發送給 render 線程來執行,當業務需要傳遞到 render 層數據量較大,對象較複雜時,互動的性能就會比較差,因此針對這種情況我們提出一個優化的解決方案。

該方案將原始的 JS 虛擬機實例 (即 Isolate) 重新設計成了兩個部分:Global Runtime 和 Local Runtime。

Global Runtime 部分是存放共享的裝置和數據,全局一個實例。

Local Runtime 是存放實例自身相關的模塊和私有數據,這些不會被共享。

在小程序裡面需要做的事情包含兩個部分:

輕量級的 js 線程替換 serviceworker 來執行小程序業務邏輯的代碼;

更高效的 worker 層和 render 層互動方式。

對於這兩個目標我們重新設計了現有的 JS 虛擬機 V8,提出了一種優化的隔離模型(Optimized isolation model, OIM)。OIM 的主要思路是共享 JS 虛擬機實例中與線程執行環境無關的數據和基礎設施,以及不可變或不易變的 JS 對象,使得在保持 JS 層邏輯隔離的前提下,節省多實例場景下在記憶體和功耗上的開銷。儘管有些實例間共享的數據會帶來同步的開銷,但是在隔離模型下,本方案所共享的數據、對象、代碼和虛擬機基礎設施都是不可變或者不易變的,所以很少發生競爭。

在新的隔離模型下,webview 裡面的 v8 實例就是一個 Local Runtime,worker 線程裡面的 v8 實例也是一個 Local Runtime,在 worker 層和 render 層互動時,setData 對象的會直接創建在 Shared Heap 裡面,因此 render 層的 Local Runtime 可以直接讀到該對象,並且用於 render 層的渲染,減少了對象的序列化和網絡傳輸,極大的提升了啟動性能和渲染性能。

首頁離線緩存優化

首頁的加載和渲染對於冷啟動是非常關鍵的,為了減少用戶在首頁顯示前的等待時間,我們採用離線緩存的方式來優化加載的流程。對於正常的加載邏輯,用戶在點擊小程序圖標後就開始啟動的過程,下載並解壓小程序離線包,找到入口的頁面 index.html,作為參數傳給瀏覽器內核開始加載小程序頁面。

在瀏覽器開始加載小程序頁面時會先出現三個圓點的 Loading 頁,然後在開始加載小程序的前端框架,在前端框架加載過程中會啟動異步的 worker 線程加載業務的 js 邏輯代碼,前端框架則繼續加載小程序的頁面,並渲染出首頁展現給用戶。

為了盡快的把首頁展現給用戶,在用戶首次展現首頁後我們會把首頁的 UI 頁面保存下來,在用戶下次重新打開小程序的時候,會首先渲染上次保存下面的首頁 UI 頁面,把首頁展現給用戶,然後在後台繼續加載前端框架和業務的代碼,加載完成後再和離線緩存的首頁 UI 進行合並,給用戶展現動態的首頁。

由於在渲染完離線緩存的首頁 UI 到真正的業務代碼加載完成,這個之間的時間大概在 1 秒左右,所以在用戶看到首頁並做出反應時動態的首頁已經合並完成,並可以對用戶的操作做出響應。

在實現首頁離線緩存這個特性中,我們面臨兩個技術上的挑戰:

1. 首頁離線緩存頁面保存的時機

由於小程序啟動是受到生命周期的控制,從 onLaunch -> onLoad -> onShow -> onReady -> 用戶操作 -> 離開首頁這個流程,在這個過程中的任意一個環節都有可能被客觀或者主觀的原因打斷,也就有可能導致保存的離線頁面不準確,在啟動的時候給用戶呈現錯誤的頁面。

所以對於首頁離線緩存渲染的效果,保存頁面的時機很重要,我們提供讓開發者可以配置的時機,配置的時機有兩個:渲染完成和離開首頁前。對於渲染完成就是首頁渲染完成,用戶還未執行任何的操作前把頁面保存下來作為離線緩存的頁面。離開首頁前就是指用戶在首頁執行了一系列的操作後,跳轉到其他頁面前用戶看到的頁面保存下來作為離線緩存的頁面。

針對離開首頁前保存頁面的問題,我們設計了一個事件的隊列,小程序生命周期中可能對首頁改動的事件都會被捕捉,同時放入到一個隊列裡面,異步線程會定時的從隊列裡面拿事件,然後延遲執行保存首頁的操作,由於經常對瀏覽器內核執行保存操作,對性能是有影響的,所以會對這些事件進行合並處理,最終會以最後一個正確保存的首頁為準。

2. 離線緩存首頁和動態渲染首頁替換時的閃屏

對於閃屏問題發生的場景是因為緩存頁面和真實渲染的頁面是分離的,是兩個獨立的頁面,緩存頁面是靜態的頁面,真實的頁面是通過 js 動態創建的頁面,所以常規的做法就是當真實頁面創建完成後替換緩存的頁面,這樣的情況下就會發生閃屏。

針對這個問題,我們是採用虛擬 dom 來解決,在加載緩存頁面的時候把緩存頁面放入初始的虛擬 dom 裡面,真實頁面創建後產生的虛擬 dom 跟緩存頁面的虛擬 dom 進行 dom diff,把變化的內容通過 patch 傳給瀏覽器內核,渲染對應的頁面,這樣就可以隻更新局部有變化的頁面內容,避免了整個頁面的更新,也保證內容的準確性和實時性。

通過實測數據顯示,這個優化可以將小程序的冷啟動實現秒開。

虛擬 dom 優化

小程序的頁面渲染採用的也是業界普遍在使用的虛擬 dom 技術,該技術可以保障在更新頁面時隻更新變動的部分,提升了更新的效率。不足的地方就是虛擬 dom 也是用 js 來實現,在運算時會大量消耗 cpu,執行的效率不高。

JavaScript 是一種弱動態類型的語言,不同於靜態類型的 C 和 Java 語言,相較而言 JS 的運行性能會差一些,因為類型的不確定性限制了 JIT 優化編譯器生成代碼的質量。

針對這種情況,我們選擇 WebAssembly 作為虛擬 dom 的實現方向,WebAssembly 是一個新的 Web 標準,它定義了網頁中的可執行代碼的二進製格式和相應的類似匯編語言格式。他的目標是使執行代碼幾乎與本地機器代碼一樣快,它被用來作為 JavaScript 的補充,以加速 Web 應用程序的性能關鍵部分,所以我們使用 WebAssembly 技術重新實現了虛擬 dom 這塊的核心代碼,提升了小程序的頁面渲染。

在做這個優化的時候,我們面臨 js 代碼橋接到 WebAssembly 的性能較差的挑戰,因為 js 引擎和 WebAssembly 是兩個獨立的引擎,他們之間的互動比 js 到 js 的性能要差了不少,針對這個問題,我們參考了業界的一些實現,對 V8 的代碼進行了優化,解決 js WebAssembly 互動性能差的問題。

在做這個優化前,我們需要先了解下到底是什麽原因導致了 js 和 WebAssembly 互動性能差。由於 JS 和 WebAssembly 是兩種不同類型的語言,所以引擎執行過程中遇到語言切換的時候,需要做一些“翻譯”工作。而這些翻譯工作需要考慮各種情況,需要跳轉到一個專門的 trampoline stub 處理。

由於在小程序前端框架的實現代碼是 TypeScript 來開發的,所以框架在調用虛擬 dom 的 WebAssembly 的函數時是可以傳入具體的參數類型,並且參數的順序也是固定的,但是這些參數類型和參數順序在到 js 引擎的時候就丟失了,所以需要做一些額外的“翻譯”工作,降低了互動的性能。

我們的思路就是精簡這些翻譯的工作,在開發層面把框架和 WebAssembly 的互動代碼的參數類型和順序都固定下來,不讓其變動。同時我們讓 js 引擎支持了參數類型和參數順序的傳入,在編譯期把代碼的參數類型和參數順序保存下來,運行期把 js 代碼和類型文件一起傳給 js 引擎,讓 js 引擎可以直接識別該函數的參數類型,這樣就可以直接進行參數轉化的工作然後調用 WebAssembly 的方法,避免跳轉到一個通用的參數轉換的 trampoline stub 上。

通過實測數據表明,相比於以前的實現,新的實現代碼執行效率有 50% 的提升。

開發者工具

支付寶小程序的目標就是為用戶提供高品質的服務,這些服務是靠我們的開發者來實現的,所以如何幫助開發者提供提供高品質的小程序,如何保障線上小程序的質量,就是我們一直努力在做的事情。支付寶小程序提供從開發、調試、發布到運維整個鏈路的工具,這些工具也在不斷的完善和增強,讓開發者可以更高效的開發出高品質的小程序。

開發者工具 IDE 支持 mac 和 windows 兩個平台的運行,通過打通接入研發平台、數據監控、日誌收集等系統,進一步為桌面客戶端的穩定性提供保障。提供多端開發能力,通過整合通用能力,適配各端差異,幫助開發者實現代碼的多端調試運行,同時可以一鍵發布到多端。

對於開發新手來說搭建一套完整的後端應用過於複雜,涉及到伺服器的購買,域名購買,環境配置等等一系列問題,每一個問題都可能阻礙開發者進行下一步操作。為此我們提供了以下兩套一站式雲服務方案讓開發者能夠快速高效搭建一套完整的後端服務:

雲函數,將伺服器購買,配置,發布,運維等完全解決,讓開發者隻用關心自己的代碼邏輯部分的編寫,並且開發語言是 js,對於前端開發者非常友好。相比雲應用,更適合編寫輕量級的小程序,但是每個雲函數只能在綁定的小程序中調用。

雲應用,將伺服器購買,配置,發布的問題解決,相比雲函數,雲應用更加靈活,適合編寫較複雜的後端應用,並且一個雲應用可以支撐多個小程序同時調用。我們提供了兩種後端語言 nodeJs 和 java,用戶可以自行選擇。

小程序雲測試服務,可以幫助開發者更全面的檢測小程序缺陷,評估產品質量,提高審核通過率。我們提供了一套完整的小程序雲真機自動化檢測方案,在 IDE 申請雲測試服務,執行完成後自動生成測試報告。

雲測服務提供“快速檢測”、“深度檢測” 兩種檢測模式,滿足多緯度測試需求,並且提供性能檢測及優化建議,開發者可根據優化建議優化小程序代碼,提供更好的用戶體驗。

線上巡檢

目前支付寶小程序擁有幾十萬生態合作夥伴,隨著小程序生態的不斷壯大,合作夥伴的數量也在急劇增加,如何對生態夥伴提供的服務形成有效的管控,如何對小程序的質量進行保障,這是我們面臨的新挑戰。面對這個問題,我們在制定相應技術標準和運營規範的同時,對小程序從入駐到運營,從質量、體驗、安全、合規、效能等維度建設了平台化的質量與風險管控能力。

巡檢是開發者生態質量與風險保障重要的一環,是識別問題的重要手段。小程序為開發者提供的服務場景非常豐富而且複雜,為解決這一系列問題,我們通過自建識別引擎,並整合螞蟻、阿里雲等多項基礎檢測單元的服務能力,以“技術 + 一體化 + 平台化”的方式,建設主動巡檢(稽查)的能力,即巡檢平台。

在平台建設過程中,我們面臨的挑戰有:

開發者提供的服務場景非常豐富且複雜,如:繳費、醫療、保險、旅行等服務,產品呈現多樣化;

小程序提供的是一套前端框架,服務內容是由服務端動態呈現,隨時變化,甚至並且千人千面;

小程序技術的靈活性因素,比如允許內嵌 webview,Js 動態加載等;

小程序體量龐大,百萬應用,數千萬 page 並且不斷增長。

巡檢平台具有以下特點:

功能全面:可用性、內容合規、信息洩露、圖片識別、資源流耗問題的稽查;

主動檢測:主動訪問,非被動監控;先於用戶發現,盡可能提前將問題暴露;

動態渲染:支動態加載和頁面渲染;

高頻巡檢:分鐘級高頻巡檢,快速發現問題;

多重保障機制:雙引擎檢測、智能複查、智能恢復;

多渠道靈活的預警決策:多渠道、多階梯預警,工單決策、故障熔斷、事後處罰等完備的業務閉環能力;

實時數據大屏:巡檢、故障、預警決策實時監控;

多維數據度量:多視角、多維護數據大盤;

智能高效:預警決策環節,加入大數據 + 算法運用,更智能和高效。

小程序巡檢平台從上線以來,實現智能化提效 94%,將小程序審核平均時長從 70.59 小時下降到 4.27 小時並實現 0 積壓。根據業務訴求進行不同頻率的巡檢,目前已累計發現和處理了上萬個有問題的小程序,提升了小程序線上服務的品質。

多端 inside

在支付寶小程序發展的過程中,集團內的 BU 也有很強的訴求需要在他們的 app 端運行小程序,擴展他們的商業場景,增加用戶的活躍度。為了避免重複造輪子,大家共享小程序生態,也就需要我們從業務和技術上打通小程序的技術棧,輸出支付寶小程序技術,幫助集團內的 BU 具備小程序的運行能力。

目前支付寶小程序正逐步打通阿里生態,開發者可一次開發,阿里各大 app 多端運行,通過小程序連接阿里經濟體。小程序對外輸出的技術主要包含兩個部分,一個是小程序運行時的 SDK,這個需要集成到接入的客戶端裡面,另一個是小程序的互通,這塊需要接入的平台和小程序平台打通,大家共享同一個小程序生態。

小程序 SDK

小程序輸出的 SDK 包含兩個部分,基礎引擎和能力插件,基礎引擎是必須的,不可替換的,它承載了小程序的基礎能力,包括前端框架和容器的核心能力,以及提供渲染的內核。它提供了小程序核心的運行時和基礎的核心組件和 JSAPI,同時提供了能力插件的插件容器,插件容器有良好的隔離性,不會因為插件的 crash 導致容器的 crash,保障了小程序核心運行時的穩定性。

小程序互通

小程序的技術棧除了前端的框架和客戶端的運行時,還包括開發者的入駐,小程序的創建,開發和上線,以及後續的運維和運營等管理,為了給用戶和開發者較好的體驗,小程序的互通是小程序技術輸出的必須環節。

平台互通:開發者可以在入駐的開放平台管理投放到所有端的小程序,包括小程序的開發、調試、測試、發布、運維和管理等一系列的工作。

研發平台互通基於支付寶的開放平台能力,統一開發和發布流程,通過門戶接入互通、開發者體系接入互通、審核能力接入互通、小程序研發鏈路接入互通、小程序運行鏈路接入互通,實現開發者一次開發、多端投放的能力。

運營管理平台通過統一埋點 SDK 提供多端小程序自動化埋點能力,輸出標準化行為、異常與性能數據模型,通過數據分析平台,提供小程序在各端實時數據分析能力,並進一步提供用戶特徵分析、頁面分析、用戶留存分析支持小程序研發可視化數據自運營的能力。同時也支持小程序研發自定義數據采集點配置,並開放分析管理支持小程序內的用戶行為做精細化跟蹤、分析,滿足除頁面訪問等標準統計以外的個性化分析需求。

工具平台提供給開發者統一的開發者工具,幫助開發者更好的開發和測試小程序,同時接入的端可以擴展開發者工具的模擬器和特色的 jsapi 接口,方便開發者做端內的特色能力調試。

能力互通:支付寶特色能力支付、會員、卡券、信用等能力可以通過擴展 jsapi 或者插件的方式輸出到接入的客戶端裡面,同樣的,接入的端也可以把自己的特色能力輸出到小程序聯盟的其他端裡面,為更多的用戶服務。基礎能力所有端保持一致,客戶端特色能力可以通過擴展 jsapi 的方式集成到小程序 api 裡面,也可以通過插件的形式發布到插件市場,用戶在使用的時候動態下載插件,屏蔽端上的差異。

用戶互通:投放到多端的小程序,需要账號綁定,用戶無需登錄,給用戶提供一致的用戶體驗。账戶通 SDK 通過提供一套完整的注冊、登錄、授權、账號綁定管理等基礎功能來完成多個 APP 間账戶互通的功能,並保障整個過程安全可控。通過账戶通可以拓展小程序服務覆蓋邊界,將支付能力、X 服務能力覆蓋更多的客戶,讓服務通行便利、多端權益打通、多端體驗統一。

小程序的 inside 技術棧不只是針對阿里集團內輸出,也可以輸出到外部的 app 商戶,幫助 app 商戶豐富業務場景,給用戶提供更多有價值的服務。歡迎加入支付寶小程序聯盟,通過小程序連接到阿里經濟體,共同壯大小程序生態。

福利時刻:

小程序、Flutter、移動 AI、工程化、性能優化... 大前端的下一站在哪裡?GMTC 2019 全球大前端技術大會將於 6 月北京盛大開幕,來自 Google、BAT、美團、京東、滴滴等一線前端大牛將與你面對面共話前端那些事,聊聊大前端的最新技術趨勢和最佳實踐案例。

獲得更多的PTT最新消息
按讚加入粉絲團